从Docker零基础到懂一点实践教程(六)

Docker镜像与仓库(二)

Dockerfile指令(上)

指令格式

在Dockerfile中,注释以“#”开头,后面接上注释的内容,指令以大写的指令名开头,后面跟着指令的参数。

以前面用到的dockerfile为例,它包含第一行的一条注释,以及后面的五条指令:

schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$ 

这五条指令中,包含了FROMMAINTAINERRUNEXPOSE这四种最常用的指令。

FROM

FROM指令指定了基础镜像,它有两种格式:
- FROM <image>
- FROM <image>:<tag>

其中“image”必须是一个已经存在的镜像,后续指令都会基于这个镜像运行,因此它也被叫做“基础镜像”,而FROM指令也必须是dockerfile中第一条非注释的指令。

MAINTAINER

MAINTAINER指令通常包含了镜像的维护者及其联系方式,这与docker commit命令中的-a参数有着相同的作用。

RUN

RUN指令用来指定构建镜像所需的命令,它有两种格式:
- RUN <command param1 param2> (shell模式)
- RUN ["executable", "param1", "param2"] (exec模式)

在“shell模式”下,RUN指令是以/bin/sh -c command的方式运行命令的;而在“exec模式”下,RUN指令可以用指定的shell来运行指令。它们的区别就像是:/bin/sh -c echo helloRUN["/bin/bash", "-c", "echo hello"]

这个Dockerfile中包含了两条RUN指令,我们知道镜像有分层的概念,每一条RUN指令都会在当前镜像之上创建一个容器来运行指定的命令,然后再生成一个新的镜像。所以,当我们把这两条RUN指令合并成为一条后,镜像构建过程中原有的两步构建就会合并成为一步,而中间层镜像也会少生成一个:

schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$ 
schen@scvmu01:~/dockerfile/build_test$ docker build -t="shichen/df_test1" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : EXPOSE 80
 ---> Using cache
 ---> 38555e155ef6
Successfully built 38555e155ef6
schen@scvmu01:~/dockerfile/build_test$
schen@scvmu01:~/dockerfile/build_test$ vi Dockerfile
schen@scvmu01:~/dockerfile/build_test$ 
schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update && apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$ docker build -t="shichen/df_test2" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update && apt-get install -y nginx
 ---> Running in 52d52d5775f0
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Processing triggers for systemd (229-4ubuntu7) ...
 ---> 1706a4656c05
Removing intermediate container 52d52d5775f0
Step 4 : EXPOSE 80
 ---> Running in 7fa0ece5ed40
 ---> c4efda0154f7
Removing intermediate container 7fa0ece5ed40
Successfully built c4efda0154f7
schen@scvmu01:~/dockerfile/build_test$ 

从上面新的Dockerfile构建过程中,我们还注意到“Step 2”直接使用了之前构建好的中间层镜像,这就是我们会在后面详细介绍的“构建缓存”技术。

EXPOSE

通过EXPOSE指令我们可以指定运行该镜像的容器所使用的端口,我们即可以在一条EXPOSE指令中指定多个端口号,也可以在一个Dockerfile中使用多条EXPOSE指令。

但要注意的是,虽然我们在镜像的构建过程中指定了容器所使用的端口号,但是我们仍然需要在容器运行时手动地指定端口映射(docker run -p 80)。也就是说,EXPOSE指令只是告诉Docker该容器内的应用将会使用哪些端口,但是出于安全考虑,Docker并不会自动打开它们,而是需要我们在docker run命令中指定对应的端口号。

Dockerfile指令(下)

除了之前讲到的一些常用指令外,Dockerfile还支持一些其他指令来完成更加复杂的功能,它们是:
1. CMDENTRYPOINT指令用于设置容器运行时所执行的命令;
2. ADDCOPYVOLUME指令用于设置镜像的目录和文件;
3. WORKDIRENVUSER指令用于进行镜像构建和容器运行时的环境设置;
4. ONBUILD指令是一个类似于触发器的指令;

CMD

CMD指令用于指定容器默认的启动命令,它有三种格式:
- CMD <command param1 param2> (shell模式)
- CMD ["executable", "param1", "param2"] (exec模式)
- CMD ["param1", "param2"] (作为ENTRYPOINT指令的默认参数)

与之前讲过的RUN指令非常相似,它们都是执行一条命令,不同的是RUN指令提供的命令是在镜像构建过程中执行的,而CMD指令提供的命令则是在容器运行时执行的。并且,如果我们在运行容器时指定了命令,则CMD指令提供的命令将被覆盖,不会被执行,也就是说CMD指令是用来指定容器运行的默认行为。

我们仍然沿用之前的Dockerfile,只是在文件的末尾加上一行CMD指令,让容器运行时默认启动nginx服务:

schen@scvmu01:~/dockerfile/df_test1$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test1$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test1$ docker build -t="shichen/df_test1" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : EXPOSE 80
 ---> Using cache
 ---> 38555e155ef6
Step 6 : CMD /usr/sbin/nginx -g daemon off;
 ---> Running in 939da67cdae7
 ---> d8b6e8741805
Removing intermediate container 939da67cdae7
Successfully built d8b6e8741805
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker run -p 80 --name cmd_test1 -d shichen/df_test1
439bd4ca203c040ac306e6bec33f513ca664071ab14d24482fe8dfdcecb61f4f
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
439bd4ca203c        shichen/df_test1    "/usr/sbin/nginx -g '"   7 seconds ago       Up 4 seconds        0.0.0.0:32771->80/tcp   cmd_test1
schen@scvmu01:~/dockerfile/df_test1$ 

注意我们并没有在docker run命令的末尾指定启动容器后要执行的命令,这时Docker就会为我们执行CMD指令提供的命令。因此,当使用docker top命令查看容器中的进程时,我们可以发现Nginx正在运行。

schen@scvmu01:~/dockerfile/df_test1$ docker top cmd_test1 
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                2701                2673                0                   20:20               ?                   00:00:00            nginx: master process /usr/sbin/nginx -g daemon off;
www-data            2737                2701                0                   20:21               ?                   00:00:00            nginx: worker process
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ curl http://127.0.0.1:32771
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test1$ 

而当我们在docker run命令中指定容器启动后需要运行的命令时,CMD指令提供的命令就不会被执行:

schen@scvmu01:~/dockerfile/df_test1$ docker run -p 80 --name cmd_test2 -d shichen/df_test1 sleep 1m
5e0a8a1587f50d6c46613871abce2640d4dc166707043e448c60722d9cebdb15
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
5e0a8a1587f5        shichen/df_test1    "sleep 1m"               9 seconds ago       Up 5 seconds        0.0.0.0:32772->80/tcp   cmd_test2
439bd4ca203c        shichen/df_test1    "/usr/sbin/nginx -g '"   45 minutes ago      Up 45 minutes       0.0.0.0:32771->80/tcp   cmd_test1
schen@scvmu01:~/dockerfile/df_test1$ 

ENTRYPOINT

ENTRYPOINT指令用于指定容器默认的启动命令,它有两种格式:
- ENTRYPOINT <command param1 param2> (shell模式)
- ENTRYPOINT ["executable", "param1", "param2"] (exec模式)

ENTRYPOINT指令与我们刚刚介绍的CMD指令非常类似,唯一的不同就是ENTRYPOINT指令提供的命令不会被docker run命令中指定的启动命令所覆盖。如果要覆盖ENTRYPOINT提供的命令,则需要在docker run命令中使用--entrypoint参数。

我们将刚才的Dockerfile稍作改动,用ENTRYPOINT指令代替最后一行的CMD指令:

schen@scvmu01:~/dockerfile/df_test2$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test2$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test2$ docker build -t="shichen/df_test2" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : EXPOSE 80
 ---> Using cache
 ---> 38555e155ef6
Step 6 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 2e2e75998bb0
 ---> 740efa125bc6
Removing intermediate container 2e2e75998bb0
Successfully built 740efa125bc6
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker run -p 80 --name ep_test1 -d shichen/df_test2 sleep 1m
459000a92dbef209633b05a5f431ba1c16488ffe38ebcb088b18c018c07eea25
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker ps -l
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
459000a92dbe        shichen/df_test2    "/usr/sbin/nginx -g '"   24 seconds ago      Exited (1) 19 seconds ago                       ep_test1
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test3$ docker logs ep_test1
nginx: invalid option: "sleep"
schen@scvmu01:~/dockerfile/df_test2$ 

注意我们在docker run命令的末尾指定了启动容器后要执行的命令,但容器依旧执行的是ENTRYPOINT提供的命令。也就是说,我们在docker run命令中指定的sleep 1m并没有当成命令被执行,这就是ENTRYPOINT指令和CMD指令的区别。

由于ENTRYPOINTCMD各自的特点,我们通常会将它们组合使用,通过ENTRYPOINT指定命令,再通过CMD指定默认的参数:

schen@scvmu01:~/dockerfile/df_test3$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
schen@scvmu01:~/dockerfile/df_test3$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test3$ docker build -t="shichen/df_test3" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : EXPOSE 80
 ---> Using cache
 ---> 38555e155ef6
Step 6 : ENTRYPOINT /usr/sbin/nginx
 ---> Running in eab87fcdc85f
 ---> 4cd7f1c9a201
Removing intermediate container eab87fcdc85f
Step 7 : CMD -h
 ---> Running in 30a75a7afe69
 ---> 73def694b2a9
Removing intermediate container 30a75a7afe69
Successfully built 73def694b2a9
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker run -p 80 --name ep_test2 -d shichen/df_test3 -g "daemon off;"
e75917bf7a853f077ad2fdba9046ff60919c54f9b3578d68e3980e091f68f70a
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
e75917bf7a85        shichen/df_test3    "/usr/sbin/nginx -g '"   5 seconds ago       Up 1 seconds        0.0.0.0:32778->80/tcp   ep_test2
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker top ep_test2
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                4185                4159                1                   22:37               ?                   00:00:00            nginx: master process /usr/sbin/nginx -g daemon off;
www-data            4204                4185                0                   22:37               ?                   00:00:00            nginx: worker process
schen@scvmu01:~/dockerfile/df_test3$ 

我们写在docker run命令末尾的-g "daemon off;"覆盖了由CMD提供的默认参数,传递给了由ENTRYPOINT指定的/usr/sbin/nginx命令,容器得以顺利运行。

ADD and COPY

ADDCOPY指令的格式完全一致,因此可以简单记为:
- ADD/COPY <src>...<dest>
- `ADD/COPY [“”…””] (适用于文件路径中有空格的情况)

这是两条非常相似的指令,它们都是用来将文件或目录复制到使用Dockerfile构建的镜像当中,并且它们都支持两种参数:来源地址和目标地址。

文件或目录的来源可以是本地地址,也可以是远程URL。如果是本地地址,则必须是构建目录中的相对地址。对于远程URL,Docker并不推荐使用,而是更建议使用curlwget之类的命令来获取文件。而目标地址则需要指定为镜像中的绝对路径。

这两条指令的区别在于ADD包含类似tar的解压功能,在安装一些使用tar压缩的软件包时会有所帮助。而如果只是单纯地复制文件,Docker更推荐使用COPY指令。

作为演示,我们将之前的Dockerfile稍加改动,在安装完Nginx之后,使用构建目录中的网页替换掉Nginx的默认页面:

schen@scvmu01:~/dockerfile/df_test4$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test4$ 
schen@scvmu01:~/dockerfile/df_test4$ cat index.html 
<html>
        <head>
                <title>Nginx in Docker</title>
        </head>
        <body>
                <h1>Hello, I'm website in df_test4!</h1>
        </body>
</html>
schen@scvmu01:~/dockerfile/df_test4$ 
schen@scvmu01:~/dockerfile/df_test4$ ls
Dockerfile  index.html
schen@scvmu01:~/dockerfile/df_test4$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test4$ docker build -t="shichen/df_test4" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : COPY index.html /var/www/html
 ---> a4f2026f3619
Removing intermediate container d187a7639151
Step 6 : EXPOSE 80
 ---> Running in 1a342bc29284
 ---> c6175193a094
Removing intermediate container 1a342bc29284
Step 7 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 52e744f99e46
 ---> 2d71b8743a26
Removing intermediate container 52e744f99e46
Successfully built 2d71b8743a26
schen@scvmu01:~/dockerfile/df_test4$ 
schen@scvmu01:~/dockerfile/df_test4$ docker run -p 80 --name cp_test -d shichen/df_test4
c9cdb455f3d91552509d685d9a543368cc76ba8ae447be40783567319397893f
schen@scvmu01:~/dockerfile/df_test4$ 
schen@scvmu01:~/dockerfile/df_test4$ docker ps -l
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
c9cdb455f3d9        shichen/df_test4    "/usr/sbin/nginx -g '"   12 seconds ago      Up 7 seconds        0.0.0.0:32785->80/tcp   cp_test
schen@scvmu01:~/dockerfile/df_test4$ 
schen@scvmu01:~/dockerfile/df_test4$ curl http://127.0.0.1:32785
<html>
        <head>
                <title>Nginx in Docker</title>
        </head>
        <body>
                <h1>Hello, I'm website in df_test4!</h1>
        </body>
</html>
schen@scvmu01:~/dockerfile/df_test4$ 

VOLUME

VOLUME指令用于向镜像创建的容器添加卷,一个卷是可以存在于一个或多个容器的特定目录,这个目录可以绕过联合文件系统,并提供如共享数据或数据持久化等功能。关于卷的概念,我们在后续介绍容器的相关章节中再来做详细说明。这里仅给出其参数的基本格式:
- VOLUME ["/data"]

WORKDIR

WORKDIR指令用于设置工作目录,其参数格式为:
- WORKDIR /path/to/workdir

这条指令通常用于在容器启动时设置工作目录,从而让CMDENTRYPOINT指令指定的命令在特定的目录下执行;它还可以用于镜像构建过程中,为后续的指令指定工作目录。

需要注意的是,我们通常会使用全路径作为WORKDIR指令的参数,如果指定的是相对路径,那么工作路径会一直传递下去,即每次设定工作目录时,都会基于上次的WORKDIR指令来计算路径。

ENV

ENV指令用于设定环境变量,其参数格式为:
- ENV <key> <value>
- ENV <key>=<value>

WORKDIR指令类似,ENV指令既可以用于容器启动时的环境变量设置,也可以用于镜像构建过程中,为后续的指令设定环境变量。

USER

USER指令用于设定用户身份,其参数格式为:
- USER daemon

WORKDIR指令类似,USER指令既可以用于容器启动时的用户身份设置,也可以用于镜像构建过程中,为后续的指令指定用户身份。

USER指令的参数可以是用户名、用户ID、组名、组ID甚至是它们的各种组合。在指定用户之前,Docker会一直使用root作为默认用户。

ONBUILD

ONBUILD指令用于为镜像添加触发器,其参数格式为:
- ONBUILD [INSTRUCTION]

当一个镜像被其他镜像用作基础镜像时,这个触发器将被执行,触发器所包含的指令将被插入到“其他镜像”的构建过程中。从效果上看,就好像是在“其他镜像”的Dockerfile中,FROM语句的后面直接嵌入了这个触发器所包含的指令。

继续沿用我们之前的Dockerfile,只是这次我们将COPY指令放到了ONBUILD指令中,这时触发器所包含的指令为COPY index.html /var/www/html

schen@scvmu01:~/dockerfile/df_test5$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
ONBUILD COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test5$ 
schen@scvmu01:~/dockerfile/df_test5$ cat index.html 
<html>
        <head>
                <title>Nginx in Docker</title>
        </head>
        <body>
                <h1>Hello, I'm website in df_test5!</h1>
        </body>
</html>
schen@scvmu01:~/dockerfile/df_test5$ 
schen@scvmu01:~/dockerfile/df_test5$ ls
Dockerfile  index.html
schen@scvmu01:~/dockerfile/df_test5$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test5$ docker build -t="shichen/df_test5" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : RUN apt-get update
 ---> Using cache
 ---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> 989af64bca25
Step 5 : ONBUILD copy index.html /var/www/html
 ---> Running in 251ccfb03bc9
 ---> e15a26b3b376
Removing intermediate container 251ccfb03bc9
Step 6 : EXPOSE 80
 ---> Running in 8f9b102b1bb5
 ---> 5faf5a1cbff0
Removing intermediate container 8f9b102b1bb5
Step 7 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in d79dcd30cfbd
 ---> ba60f4a95da8
Removing intermediate container d79dcd30cfbd
Successfully built ba60f4a95da8
schen@scvmu01:~/dockerfile/df_test5$ 
schen@scvmu01:~/dockerfile/df_test5$ docker run -p 80 --name ob_test1 -d shichen/df_test5
3e4fe100ca4d1f71f7258b6983081b6ab69bcd52836c60680be7ca8f9d856729
schen@scvmu01:~/dockerfile/df_test5$ 
schen@scvmu01:~/dockerfile/df_test5$ docker ps -l
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
3e4fe100ca4d        shichen/df_test5    "/usr/sbin/nginx -g '"   7 seconds ago       Up 3 seconds        0.0.0.0:32786->80/tcp   ob_test1
schen@scvmu01:~/dockerfile/df_test5$ 
schen@scvmu01:~/dockerfile/df_test5$ curl http://127.0.0.1:32786
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test5$ 

我们注意到镜像构建过程中“Step 5”执行的就是ONBUILD指令,但从结果来看,触发器所包含的指令并没有被执行。而当我们新建一个Dockerfile并指定当前镜像为我们的基础镜像时,结果就会有所不同:

schen@scvmu01:~/dockerfile/df_test6$ cat Dockerfile 
# First Dockerfile
FROM shichen/df_test5
MAINTAINER Shichen "shichen@website.com"
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test6$ 
schen@scvmu01:~/dockerfile/df_test6$ cat index.html 
<html>
        <head>
                <title>Nginx in Docker</title>
        </head>
        <body>
                <h1>Hello, I'm website in df_test6!</h1>
        </body>
</html>
schen@scvmu01:~/dockerfile/df_test6$ 
schen@scvmu01:~/dockerfile/df_test6$ ls
Dockerfile  index.html
schen@scvmu01:~/dockerfile/df_test6$ 

我们使用这个Dockerfile构建一个镜像并运行它:

schen@scvmu01:~/dockerfile/df_test6$ docker build -t="shichen/df_test6" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM shichen/df_test5
# Executing 1 build trigger...
Step 1 : COPY index.html /var/www/html
 ---> 0aed0a28670b
Removing intermediate container d4f664d78b2d
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Running in af9301f38a8c
 ---> 5c84c8bffff7
Removing intermediate container af9301f38a8c
Step 3 : EXPOSE 80
 ---> Running in 6b79f6587f77
 ---> a6d2f16d6e32
Removing intermediate container 6b79f6587f77
Step 4 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 3619ea94eee7
 ---> 047dfbddc117
Removing intermediate container 3619ea94eee7
Successfully built 047dfbddc117
schen@scvmu01:~/dockerfile/df_test6$ 
schen@scvmu01:~/dockerfile/df_test6$ docker run -p 80 --name ob_test2 -d shichen/df_test6
b6fd0526e9231310f77082dbe55f8c49eac78d22cbf5c62c9ec614ab4eac0c78
schen@scvmu01:~/dockerfile/df_test6$ 
schen@scvmu01:~/dockerfile/df_test6$ docker ps -l
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
b6fd0526e923        shichen/df_test6    "/usr/sbin/nginx -g '"   8 seconds ago       Up 5 seconds        0.0.0.0:32787->80/tcp   ob_test2
schen@scvmu01:~/dockerfile/df_test6$ 
schen@scvmu01:~/dockerfile/df_test6$ curl http://127.0.0.1:32787
<html>
        <head>
                <title>Nginx in Docker</title>
        </head>
        <body>
                <h1>Hello, I'm website in df_test6!</h1>
        </body>
</html>
schen@scvmu01:~/dockerfile/df_test6$ 

我们不难发现,在镜像构建的一开始,紧接着FROM指令,触发器所包含的那条COPY指令便被执行了。从最后的结果来看,我们也印证了这个事实。这就是ONBUILD指令的用武之地,它可以将特定的指令延迟到“子镜像”构建时再去执行。

小结

更多关于Dockerfile指令的说明,请参考官方文档或通过man dockerfile查看。

Dockerfile构建过程

构建过程

从Dockerfile构建镜像的过程可以分为如下几个步骤:
1. 从基础镜像运行一个容器
2. 执行一条指令,对容器做出修改
3. 执行类似docker commit的操作,提交一个新的镜像层
4. 再基于刚提交的镜像运行一个新容器
5. 执行Dockerfile中的下一条指令,直到所有指令执行完毕

为了进行演示,我们对之前的Dockerfile进行修改,为其添加了一条ENV指令:

schen@scvmu01:~/dockerfile/df_test7$ cat Dockerfile 
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
ENV REFRESH_DATE 2016-10-27
RUN apt-get update
RUN apt-get install -y nginx
COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test7$ 

我们使用这个Dockerfile构建一个镜像,并注意它的构建过程:

schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : ENV REFRESH_DATE 2016-10-27
 ---> Running in b2ce8f95e9fb
 ---> cae2e21fcad8
Removing intermediate container b2ce8f95e9fb
Step 4 : RUN apt-get update
 ---> Running in 2401276b0a02
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
 ---> ccf581514525
Removing intermediate container 2401276b0a02
Step 5 : RUN apt-get install -y nginx
 ---> Running in 8f18912c1e8f
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
 ---> bced4541e253
Removing intermediate container 8f18912c1e8f
Step 6 : COPY index.html /var/www/html
 ---> 81e30a820046
Removing intermediate container be18c88aa241
Step 7 : EXPOSE 80
 ---> Running in 70fc7da8c099
 ---> 54adf020bb9b
Removing intermediate container 70fc7da8c099
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 53f62d5e26a4
 ---> 138a059c7372
Removing intermediate container 53f62d5e26a4
Successfully built 138a059c7372
schen@scvmu01:~/dockerfile/df_test7$ 

以“Step 5”为例,“Running in”后面的“8f18912c1e8f”为执行apt-get install -y nginx命令所在的容器ID,它是由“Step 4”生成的中间层镜像“ccf581514525”运行而来的。下面紧接着的是执行命令时的输出,当命令执行完毕后Docker会执行一个类似docker commit的操作,将改动后的容器提交成为新的镜像,并将其ID“bced4541e253”显示出来。最后,Docker会将执行命令的容器“8f18912c1e8f”删除掉。

如此反复这个过程,直到最后一条Dockerfile中的指令执行完毕,就得到了我们通过Dockerfile构建的镜像。而其中每次在容器中执行命令,都会改变容器的内容,并生成一个中间层镜像。Docker并不会为我们删除这些中间层镜像,这样做的目的有两个:1.保留中间层镜像,方便我们运行镜像以检查命令的执行结果是否符合预期;2.提供镜像的构建缓存,这样就不必每次都重新构建Dockerfile中的所有指令。

例如,在“Step 5”中Docker执行了安装Nginx的指令并为我们生成了中间层镜像“bced4541e253”,当我们需要验证这条指令的执行结果时,就可以方便地启动一个容器来进行调试:

schen@scvmu01:~/dockerfile/df_test7$ docker run -it bced4541e253 /bin/bash
root@b3de1a9b6053:/# whereis nginx
nginx: /usr/sbin/nginx /etc/nginx /usr/share/nginx
root@b3de1a9b6053:/# 

我们知道,在Dockerfile的构建过程中,难免会遇到各种错误,而有了中间层镜像,我们就可以逐一排查错误产生的位置。另外,保留中间层镜像还为Docker提供了另一个重要的特性——构建缓存。

构建缓存

由于每一步的构建过程都会将结果提交为镜像,所以Docker的构建过程就可以非常地聪明,它会将之前的镜像看作缓存,这里我们再次对刚才的Dockerfile执行构建命令:

schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7_2" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> b8051df248d3
Step 3 : ENV REFRESH_DATE 2016-10-27
 ---> Using cache
 ---> cae2e21fcad8
Step 4 : RUN apt-get update
 ---> Using cache
 ---> ccf581514525
Step 5 : RUN apt-get install -y nginx
 ---> Using cache
 ---> bced4541e253
Step 6 : COPY index.html /var/www/html
 ---> Using cache
 ---> 81e30a820046
Step 7 : EXPOSE 80
 ---> Using cache
 ---> 54adf020bb9b
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Using cache
 ---> 138a059c7372
Successfully built 138a059c7372
schen@scvmu01:~/dockerfile/df_test7$ 
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
shichen/df_test7_2   latest              138a059c7372        About an hour ago   222.6 MB
shichen/df_test7     latest              138a059c7372        About an hour ago   222.6 MB
schen@scvmu01:~/dockerfile/df_test7$ 

我们没有经历像第一次构建时那样缓慢的过程,而且每一步构建时都有“Using cache”字样,也就是使用了构建缓存,这就使我们的构建过程十分高效。

但是,有些时候我们并不想使用构建缓存,比如构建指令中包含apt-get update这样的命令时,我们希望每次Docker都会去刷新apt包的缓存,这样就能使用最新版本的软件。要想跳过构建缓存,我们可以使用docker build --no-cache的方式构建镜像:

schen@scvmu01:~/dockerfile/df_test7$ docker build --no-cache -t="shichen/df_test7_3" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Running in b1c2946903c1
 ---> 494ce9041153
Removing intermediate container b1c2946903c1
Step 3 : ENV REFRESH_DATE 2016-10-27
 ---> Running in d8f5313941dc
 ---> d928c21480b8
Removing intermediate container d8f5313941dc
Step 4 : RUN apt-get update
 ---> Running in a4003d11e3ba
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
 ---> 17bee3d7a390
Removing intermediate container a4003d11e3ba
Step 5 : RUN apt-get install -y nginx
 ---> Running in 5de4d1774ff1
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
 ---> 18167e6cba98
Removing intermediate container 5de4d1774ff1
Step 6 : COPY index.html /var/www/html
 ---> 780b18cee97a
Removing intermediate container 94004594e37d
Step 7 : EXPOSE 80
 ---> Running in 52541b20eb29
 ---> 0b4fb2b14e70
Removing intermediate container 52541b20eb29
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 1604eb009770
 ---> b4718b916d77
Removing intermediate container 1604eb009770
Successfully built b4718b916d77
schen@scvmu01:~/dockerfile/df_test7$ 
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
shichen/df_test7_3   latest              b4718b916d77        12 minutes ago      222.6 MB
shichen/df_test7_2   latest              138a059c7372        2 hours ago         222.6 MB
shichen/df_test7     latest              138a059c7372        2 hours ago         222.6 MB
schen@scvmu01:~/dockerfile/df_test7$ 

我们发现整个构建过程都没有使用缓存。

除了使用--no-cache选项,我们还有一个技巧可以用来刷新缓存。在刚才的Dockerfile中,我们使用ENV指令设置了一个环境变量,现在我们只需要更新这个环境变量的值,就可以为后续的指令刷新缓存:

schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7_4" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
 ---> Using cache
 ---> 494ce9041153
Step 3 : ENV REFRESH_DATE 2016-10-28
 ---> Running in d76582ccdf5b
 ---> b753392cdab6
Removing intermediate container d76582ccdf5b
Step 4 : RUN apt-get update
 ---> Running in 3466d109a816
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
 ---> fd29f631b5f6
Removing intermediate container 3466d109a816
Step 5 : RUN apt-get install -y nginx
 ---> Running in 4290bc5c06ad
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
 ---> 26e16a96f797
Removing intermediate container 4290bc5c06ad
Step 6 : COPY index.html /var/www/html
 ---> 58616a333072
Removing intermediate container 47a4bf55d694
Step 7 : EXPOSE 80
 ---> Running in 4e95ddfc9560
 ---> c027d8c2c946
Removing intermediate container 4e95ddfc9560
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
 ---> Running in 499cc2e11c4c
 ---> 358324ff3288
Removing intermediate container 499cc2e11c4c
Successfully built 358324ff3288
schen@scvmu01:~/dockerfile/df_test7$ 
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
shichen/df_test7_4   latest              358324ff3288        8 hours ago         222.6 MB
shichen/df_test7_3   latest              b4718b916d77        8 hours ago         222.6 MB
shichen/df_test7_2   latest              138a059c7372        10 hours ago        222.6 MB
shichen/df_test7     latest              138a059c7372        10 hours ago        222.6 MB
schen@scvmu01:~/dockerfile/df_test7$ 

查看镜像的构建过程

通过docker history命令可以查看一个镜像的构建过程,它的命令格式为:

docker history [OPTIONS] IMAGE

schen@scvmu01:~/dockerfile/df_test7$ docker history shichen/df_test7
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
138a059c7372        11 hours ago        /bin/sh -c #(nop)  ENTRYPOINT ["/usr/sbin/ngi   0 B                 
54adf020bb9b        11 hours ago        /bin/sh -c #(nop)  EXPOSE 80/tcp                0 B                 
81e30a820046        11 hours ago        /bin/sh -c #(nop) COPY file:f5ebd8ab6dfdea8bd   181 B               
bced4541e253        11 hours ago        /bin/sh -c apt-get install -y nginx             56.81 MB            
ccf581514525        11 hours ago        /bin/sh -c apt-get update                       39.19 MB            
cae2e21fcad8        11 hours ago        /bin/sh -c #(nop)  ENV REFRESH_DATE=2016-10-2   0 B                 
b8051df248d3        7 weeks ago         /bin/sh -c #(nop)  MAINTAINER Shichen "shiche   0 B                 
bd3d4369aebc        9 weeks ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B                 
<missing>           9 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'doc   7 B                 
<missing>           9 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB            
<missing>           9 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0 B                 
<missing>           9 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /u   745 B               
<missing>           9 weeks ago         /bin/sh -c #(nop) ADD file:902bff94e00fb3d9eb   126.6 MB            
schen@scvmu01:~/dockerfile/df_test7$ 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嗨!对于Docker零基础入门到精通,我可以给你一些指导。首先,让我们从Docker的基础开始。 1. 安装Docker:首先,你需要在你的机器上安装Docker。你可以去Docker官方网站上下载适合你操作系统的版本,并按照官方文档中的说明进行安装。 2. Docker镜像和容器:Docker使用镜像和容器来管理应用程序和服务。镜像是一个只读的模板,它包含了构建应用程序所需的所有文件和配置。容器是基于镜像创建的运行实例,它可以被启动、停止、删除等操作。 3. Dockerfile:Dockerfile是一个包含了一系列命令的文本文件,用于自动化构建Docker镜像。通过编写Dockerfile,你可以定义构建镜像所需的环境、依赖和配置。 4. Docker命令:学习一些常用的Docker命令是非常重要的。这些命令可以帮助你管理镜像、容器、网络等。一些常用的命令包括:`docker run`(运行容器),`docker build`(构建镜像),`docker stop`(停止容器)等。你可以通过查阅Docker官方文档来了解更多命令的详细用法。 5. Docker Compose:Docker Compose是一个用于定义和管理多个Docker容器的工具。它使用YAML文件来配置应用程序的服务、网络、卷等。通过使用Docker Compose,你可以轻松地启动、停止和管理多个容器。 6. Docker网络:Docker提供了不同类型的网络,用于连接和通信容器。你可以使用默认的桥接网络,也可以创建自定义网络。此外,Docker还支持外部网络和容器之间的链接。 7. Docker卷:Docker卷是用于持久化数据的机制。通过使用卷,你可以将容器内的数据存储到宿主机上的特定位置,从而实现数据的持久化和共享。 这些是Docker入门的一些基本概念和工具。一旦你熟悉了这些基础知识,你可以进一步学习Docker的高级特性,如Docker Swarm(用于集群管理)、Docker Registry(用于镜像存储和分发)等。 希望这些信息对你有所帮助!如果你有更具体的问题,我会尽力回答。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值