Docker构建镜像5

docker构建镜像

对于Docker用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的Docker官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用.

使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为Docker的工程师知道如何更好地在容器中运行软件。

当然,某些情况下我们也不得不自己构建镜像, 比如

(1)找不到现成的镜像,比如自己开发的应用程序。

(2)需要在镜像中加入特定的功能,比如官方镜像几乎都不提供ssh。

所以本节我们将介绍构建镜像的方法。

同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。

Docker 提供了两种构建镜像的方法:docker commit命令与Dockerfile构建文件。

docker commit

docker commit命令是创建新镜像最直观的方法,其过程包含三个步骤

• 运行容器。

• 修改容器。

• 将容器保存为新的镜像。

举个例子:在centos base镜像中安装vim并保存为新镜像。

第一步:运行容器,如图所示。

-it 参数的作用是以交互模式进入容器,并打开终端。9f937c7b4534 是容器的内部 ID。

第二步:修改容器,即安装vim

确认 vim 没有安装,如图所示。

安装vim, 如图所示。

Docker构建镜像5_第1张图片

第三步:保存为新镜像

在新窗口中查看当前运行的容器。

stoic_liskov是Docker为我们的容器随机分配的名字。

执行docker commit命令将容器保存为镜像, 如图所示。

新镜像命名为centos-with-vim。查看新镜像的属性,如图所示。

从size上看到镜像因为安装了软件而变大了。从新镜像启动容器,验证vim已经可以使用,如图所示。

Docker构建镜像5_第2张图片

以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:

(1)这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 centos base 镜像中也加入 vim,还得重复前面的所有步骤。

更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。

既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?

原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。

下一节我们学习如何通过 Dockerfile 构建镜像。

Dockerfile

Dockerfile是一个文本文件,记录了镜像构建的所有步骤。

(1)第一个Dockefile

用Dockerfile创建新的镜像ubuntu-with-vim,其内容如图所示(以ubuntu base镜像为例)。

下面我们运行 docker build 命令构建镜像并详细分析每个细节。

[root@localhost ~]# pwd 1

/root

[root@localhost ~]# ls 2

Dockerfile

[root@localhost ~]# docker build -t ubuntu-with-vim-dockerfile . 3

Sending build context to Docker daemon 18.94kB 4

Step 1/2 : FROM ubuntu 5

---> 93fd78260bd1

Step 2/2 : RUN apt-get update && apt-get install -y vim 6

---> Running in a0a85b228957 7

Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]

Get:2 http://security.ubuntu.com/ubuntu bionic-security/multiverse amd64 Packages [1367 B]

…………

Removing intermediate container a0a85b228957 8

---> a2ba3cbb7b97 9

Successfully built a2ba3cbb7b97 10

Successfully tagged ubuntu-with-vim-dockerfile:latest

① 当前目录为 /root。

② Dockerfile 准备就绪。

③ 运行 docker build 命令,-t 将新镜像命名为ubuntu-with-vim-dockerfile,命令末尾的 . 指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。

④ 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。

Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /root,该目录下的所有文件和子目录都会被发送给 Docker daemon。

所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 /、/usr 作为 build context,否则构建过程会相当缓慢甚至失败。

⑤ Step 1:执行 FROM,将 ubuntu 作为 base 镜像。

ubuntu 镜像 ID 为 93fd78260bd1。

⑥ Step 2:执行 RUN,安装 vim,具体步骤为 ⑦、⑧、⑨。

⑦ 启动 ID 为a0a85b228957的临时容器,在容器中通过 apt-get 安装 vim。

⑧ 删除临时容器 a0a85b228957。

⑨ 安装成功后,将容器保存为镜像,其 ID 为 a2ba3cbb7b97。这一步底层使用的是类似 docker commit 的命令。

⑩ 镜像构建成功。

通过 docker images 查看镜像信息。

镜像 ID 为a2ba3cbb7b97,与构建时的输出一致。

在上面的构建过程中,我们要特别注意指令RUN的执行过程 ⑦、⑧、⑨。Docker会在启动的临时容器中执行操作,并通过 commit 保存为新的镜像。

(2)查看镜像分层结构

ubuntu-with-vim-dockerfile 是通过在 base 镜像的顶部添加一个新的镜像层而得到的。

Docker构建镜像5_第3张图片

这个新镜像层的内容由 RUN apt-get update && apt-get install -y vim 生成。这一点我们可以通过 docker history 命令验证。

Docker构建镜像5_第4张图片

docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。

ubuntu-with-vim-dockerfile 与 ubuntu 镜像相比,确实只是多了顶部的一层a0a85b228957,由 apt-get 命令创建,大小为 83.7MB。docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。

注:missing表示无法获取 IMAGE ID,通常从 Docker Hub 下载的镜像会有这个问题。

(3)镜像的缓存特性

我们接下来看Docker镜像的缓存特性。

Docker会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无须重新创建。下面举例说明。

在前面的Dockerfile中添加一点新内容,往镜像中复制一个文件,如图所示。

Docker构建镜像5_第5张图片

[root@localhost ~]# pwd
/root
[root@localhost ~]# ls                           
Dockerfile  testfile
[root@localhost ~]# docker build -t ubuntu-with-vim-dockerfile-2  .
Sending build context to Docker daemon  19.97kB
Step 1/3 : FROM ubuntu
 ---> 93fd78260bd1
Step 2/3 : RUN apt-get update && apt-get install -y vim
 ---> Using cache                                             
 ---> a2ba3cbb7b97
Step 3/3 : COPY testfile /                                    
 ---> 93902aee704e
Successfully built 93902aee704e
Successfully tagged ubuntu-with-vim-dockerfile-2:latest
① 确保 testfile 已存在。
② 重点在这里:之前已经运行过相同的 RUN 指令,这次直接使用缓存中的镜像层a2ba3cbb7b97。
③ 执行 COPY 指令。
其过程是启动临时容器,复制 testfile,提交新的镜像层93902aee704e,删除临时容器。
在 ubuntu-with-vim-dockerfile 镜像上直接添加一层就得到了新的镜像 ubuntu-with-vim-dockerfile-2。

Docker构建镜像5_第6张图片

如果我们希望在构建镜像时不使用缓存,可以在 docker build 命令中加上 --no-cache 参数。

Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。

也就是说,如果我们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。举例说明,比如交换前面 RUN 和 COPY 的顺序,如图所示:

Docker构建镜像5_第7张图片

虽然在逻辑上这种改动对镜像的内容没有影响,但由于分层的结构特性,Docker 必须重建受影响的镜像层。

# docker build -t ubuntu-with-vim-dockerfile-3 .

Sending build context to Docker daemon 21.5kB

Step 1/3 : FROM ubuntu

---> 93fd78260bd1

Step 2/3 : COPY testfile /

---> db6a42f8c8a0

Step 3/3 : RUN apt-get update && apt-get install -y vim

---> Running in 9029d097e2fb

Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [83.2 kB]

Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]

…………

从上面的输出可以看到生成了新的镜像层db6a42f8c8a0,缓存已经失效。

除了构建时使用缓存,Docker 在下载镜像时也会使用。例如我们下载 httpd 镜像,如图所示。

Docker构建镜像5_第8张图片

docker pull 命令输出显示第一层(base 镜像)已经存在,不需要下载。

由 Dockerfile 可知 httpd 的 base 镜像为 debian,正好之前已经下载过 debian 镜像,所以有缓存可用。通过 docker history 可以进一步验证,如图所示。

Docker构建镜像5_第9张图片

(4)调试dockerfile文件

包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,所以本节讨论如何 debug Dockerfile。

先回顾一下通过 Dockerfile 构建镜像的过程:

① 从 base 镜像运行一个容器。

② 执行一条指令,对容器做修改。

③ 执行类似 docker commit 的操作,生成一个新的镜像层。

④ Docker 再基于刚刚提交的镜像运行一个新容器。

⑤ 重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。

从这个过程可以看出,如果 Dockerfile 由于某种原因执行到某个指令失败了,我们也将能够得到前一个指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。我们可以运行最新的这个镜像定位指令失败的原因。

我们来看一个调试的例子。Dockerfile 内容如图所示:

Docker构建镜像5_第10张图片

执行 docker build:

Docker构建镜像5_第11张图片

执行docker images查看镜像

Dockerfile在执行第三步 RUN 指令时失败。我们可以利用第二步创建的镜像737d6765cb5e进行调试,方式是通过 docker run -it 启动镜像的一个容器,如图所示。

手工执行 RUN 指令很容易定位失败的原因是 busybox 镜像中没有 bash。虽然这是个极其简单的例子,但它很好地展示了调试 Dockerfile 的方法。

用通俗一点的话来讲:dockerfile就是根据自己的需要自定义一个镜像,就像你写shell脚本一样,把一连串的过程或步骤全部写进dockerfile文件中,一步一步的执行dockerfile文件中你写的内容。

到这里相信大家对 Dockerfile 的功能和使用流程有了比较完整的印象.

你可能感兴趣的:(docker,运维,容器)