Docker修炼之旅(二)

Docker镜像和仓库,写写Docker镜像的一些相关命令,还有如何通过编写Dockerfile文件构建自己的镜像


继续Docker学习,这一篇主要写Docker镜像

Docker镜像是什么

Docker镜像是由文件系统叠加而成。最底端是一个文件引导系统,即bootfs。Docker用户不会与引导文件系统有直接的交互。Docker镜像的第二层是root文件系统rootfs,通常是一种或多种操作系统,例如ubuntu等。
在Docker中,文件系统永远都是只读的,在每次修改时,都是进行拷贝叠加从而形成最终的文件系统。Docker称这样的文件为镜像。一个镜像可以迭代在另一个镜像的顶部。位于下方的镜像称之为父镜像,最底层的镜像称之为基础镜像。最后,当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器。

Docker文件系统层图:




Docker镜像一些相关命令

列出本地镜像

我们可以用docker images列出所有本地的镜像,可以看到列出的镜像有一个TAG标签属性,像图中的Ubuntu镜像,就有三种标签14.04、18.04、latest,分别代表Ubuntu的不同版本,这种标签属性机制使得在同一个仓库里可以存储多个镜像。可以在拉取镜像的时候通过冒号:指定tag,如:docker run ubuntu:14.04或docker pull Ubuntu:14.04

本地的镜像保存的位置在/var/lib/docker/目录下。每个镜像都保存在Docker所采用的存储驱动目录下。本地容器都保存在/var/lib/docker/containers/目录下。

查找、拉取镜像

可以用docker search <镜像名>命令在Docker Hub上面查找镜像。
比如我们这里查找一下fedora基础镜像,可以看到列出的Docker Hub上面的所以fedora镜像,包括每个镜像的描述、收藏量、是否是官方镜像等属性,然后供我们自己选择进行拉取。

我们这里就选择fedora官方镜像进行拉取,使用docker pull命令,如下图。

拉取了镜像后,就可以用这个镜像来构造我们的容器,容器的操作具体见上一篇文章。

删除镜像

可以通过docker rmi命令删除镜像,前提是这个镜像没有被容器使用(包括运行或停止的容器)
如下图示,我们尝试删除被运行容器和已停止容器使用的镜像,均失败。

只有把使用了该镜像的容器删除后,才能删除镜像。


构建镜像

一般来说,我们不是真正的”创建”新镜像,而是基于一个已有的基础镜像,如Ubuntu或fedora等,构建新镜像而已。

构建镜像一般有两种方法:

  1. 通过docker commit命令
  2. 使用docker build命令和Dockerfile文件

使用docker commit命令

先说第一种方法。docker commit命令,可以把这种方法视为我们在往版本控制系统里提交变更。
我们首先先用Ubuntu镜像构建一个容器,如下图,我们可以看到,创建的容器是没有安装vim的,我们可以用apt-get install命令安装一下

可以看到,我们对最基础的Ubuntu镜像做了一些改变,安装了vim软件,不过这只是这个容器安装了而已,再用Ubuntu镜像创建另一个新容器,还得再重新装一遍vim,这未免太过麻烦。于是为了解决这个问题,我们可以把安装了vim的容器的状态保存下来,生成一个新镜像,这样就不必重新安装vim了。

为了完成这项工作,我们用到了docker commit命令,首先先用exit命令退出容器,然后获取刚刚运行的容器的ID(通过docker ps -l -q命令获取),然后用docker commit <容器ID> <新镜像名>命令创建新镜像。

我们这里使用docker commit成功创建了一个新镜像ubuntu:installed-vim,可以通过docker images查看是否创建成功。

这里关于docker commit命令在说两句,这个命令应该和git commit差不多,可以使用-m参数添加标注,而且提交的部分只是创建容器的镜像和容器的当前状态之间有差异的部分,这使得该更新十分轻量

使用Dockerfile文件

实际构建镜像中,docker commit命令方法用的较少,大多都是编写Dockerfile文件,然后用docker build命令创建新镜像。Dockerfile文件基于DSL语法

流程如下:

  1. 首先先创建一个目录,这个目录就是我们的构建环境,要把Dockerfile放在该目录下,然后再在该目录直接运行docker build命令创建镜像。
  2. 然后在该目录下创建编写Dockerfile文件

我们这里创建了一个Docker_study文件夹,然后开始编写Dockerfile文件,文件具体代码如下。

1
2
3
4
5
6
7
8
# version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Miracle778 "Yiitao@163.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in Your Container'\
>/usr/share/nginx/html/index.html
EXPOSE 80

Dockerfile文件由一系列指令和参数组成。每天指令,如FROM,都必须是大写字符,且后边要跟随一个参数,如:FROM ubuntu:14.04。Dockerfile文件中的指令会按顺序从上到下执行,所以要合理安排顺序

每条指令都会创建一个新的镜像层并对镜像进行提交,大致按照如下流程执行。

  1. Docker从基础镜像运行一个容器
  2. 执行一条指令,对容器进行修改
  3. 执行类似于docker commit的指令,提交一个新的镜像层
  4. Docker基于新提交的镜像运行一个新容器
  5. 执行Dockerfile中的下一条指令,重复上面2-4步过程,直至所有指令结束

从上面流程中可以看出,如果Dockerfile由于某一条指令执行失败了,那么你可以得到一个执行完上一条指令后的可以使用的镜像,这对调试非常有用,可以利用这个镜像运行一个具备交互功能的容器,然后执行你Dockerfile文件中失败的指令,查看失败原因。
比如我们这里把上面Dockerfile文件中的第五行内容改一下,把nginx改为nignx,然后运行下docker build命令,关于docker build命令我们等下在后面再讲。这里先关注如何调试。

如上图,如我们所预期那样,改变了Dockerfile文件的第五行的命令后,build过程果然报错,而且得到了上一条命令运行完的容器ID,接下来我们运用这个容器ID进行调试。
我们使用docker run -i -t命令以交互式模式进入该容器,然后执行下Dockerfile文件中报错的指令,这里是apt-get install -y nignx,然后观察下结果。

如上图示,确定了出错原因,并且可以通过在容器中执行改正指令达到调试效果,最终我们确定是apt-get install -y nignx命令的nginx拼写错了,于是可以在Dockerfile文件中修改过来。这就是调试过程。

下面我们用改正过后的正确的Dockerfile文件进行镜像创建。执行过程如下图

我们来解释解释该步骤里的一些过程。

看到创建命令:docker build -t="miracle778/dockerfile_study" .
该命令执行过程为先在本地目录中寻找Dockerfile文件(也可以指定github地址上的Dockerfile文件),找到后安装Dockerfile命令一步步执行。

-t 参数是指定镜像名字,一般为:<仓库名>/<镜像名:标签>,这里名字不能有大写字母
最后要注意的是,命令后面那个点 .,不要忘记了

然后是Dockerfile文件里的指令解释

#代表注释
FROM指令指定一个已经存在的镜像,后续指令都将基于该镜像进行,这个镜像被称为基础镜像
MAINTAINER 指令会告诉Docker该镜像的作者是谁,以及作者的电子邮件地址
EXPOSE指令告诉Docker该容器内的应用程序将会使用容器的指定端口
RUN 命令会在当前镜像中运行指定的命令
默认情况下,RUN指令会在shell中使用命令包装器/bin/sh -c来执行。
如果是在一个不支持shell的平台上运行或者不希望在shell中运行
也可以使用exec格式的RUN指令
RUN [“apt-get”, “install” ,”-y” , “nginx”]
这种方式中,要使用一个数组来指定要运行的命令和传递给该命令的参数

Dockerfile还有其他一些命令,后面的学习使用中会逐步用到,到时候再现学吧。
这里给个链接,随便百度Dockerfile命令大全得来的,https://www.cnblogs.com/dazhoushuoceshi/p/7066041.html

然后再谈谈Dockerfile和构建缓存

我们可以从上面的Dockerfile执行流程和那个调试例子看到,Docker镜像构建过程中,每一步都会将结果提交为镜像。换句话说,就是会把之前的镜像层看做缓存。这样会使得构建镜像节省很多时间。
但有时候镜像构建过程中可能不需要使用缓存,这时就需要在docker build命令里加入--no-cache标志

讲了这么多,快来使用下新镜像吧

使用命令docker run --name Docker_study -d -p 80 miracle778/dockerfile_study nginx -g "daemon off;"创建一个新容器,这句命令稍后再来解析,现在先看到命令执行后的结果

可以看到,容器创建好后,成功运行,建立了一个宿主机32772端口到docker容器80端口的映射,也就是说,我们访问本地的32772端口,就能访问到容器的80端口,也就是这里的nginx服务器。
我们再回到Dockerfile文件中的最后一条RUN命令,是向/usr/share/nginx/html/index.html文件里写入了Hi, I am in Your Container这句内容。所以我们访问docker容器80端口预期的结果应该是返回Hi, I am in Your Container
我们通过curl命令测试一下,如下图,结果符合预期。

现在我们再来看到使用新镜像生成新容器的命令,主要是看到-p参数,这个参数用来控制Docker在运行时应该公开哪些网络端口给外部,这个参数会覆盖掉Dockerfile中EXPOSE指定的端口。而且有两种分配方法,一种是Docker随机分配一个比较大的端口号来映射到容器的80端口上,也就是我们图中这里使用的方法;另一种方法是可以指定宿主机端口来映射到容器的指定端口,如:-p 7777:80就是把宿主机的7777端口映射给Docker容器的80端口。

然后我们在看到命令最后部分的nginx -g "daemon off;"这里,这样写会以前台运行的方式启动nginx,这里可能有点好奇为什么要这样写,如果不这么写的话,可能会导致run命令执行完创建好容器后,容器自动退出的情况,这里我也查了查资料,出现这种情况的原因见下。

Docker 容器启动时,默认会把容器内部第一个进程,也就是pid=1的程序,作为docker容器是否正在运行的依据,如果 docker 容器pid=1的进程挂了,那么docker容器便会直接退出
Docker未执行自定义的CMD之前,nginx的pid是1,执行到CMD之后,nginx就在后台运行,bash或sh脚本的pid变成了1。
所以一旦执行完自定义CMD,nginx容器也就退出了。

具体见:https://blog.csdn.net/youcijibi/article/details/88781014


总结

这篇文章主要是讲些Docker镜像相关的命令以及构造自己的Docker镜像的方法,重点是通过Dockerfile文件加docker build命令构建自己的镜像,其中Dockerfile文件的编写语法这里只讲到一点,其他的ADD | ENTRYPOINT | VOLUME | CMD等命令这里都没有提到,应该会在后面的搭建具体应用时用到,到时候再写。

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------