在“Dockerfile最佳实践(一)”中,我们已经了解到Dockerfile中常用指令的使用,并给出了演示示例,这一篇将再补充和巩固Dockerfile中的常用知识点。

Dockerfile context(上下文)

在执行docker image build时,CLI首先会告知此次构建将向Docker守护进程发送生成上下文的大小,例如。

# cat > Dockerfile < 
  

Sending build context to Docker daemon 2.048kB

前面我们已经讲到镜像的构建是由Docker守护进程(Docker daemon)完成的,那么上述执行的"docker build -t demo . "其实是经历了两步:

首先,Docker CLI将上下文下包含的所有文件(递归地)发送给Docker守护进程Docker daemon。

然后,Docker Demon会收到Dcoker CLI发送的内容,通过读取Dockerfile里面的指令进行镜像的分层构建。

注意:若是使用"/"根作为上下文,则可能会导致主机异常重启(在AWS上验证是会导致主机自动重启),因为它将会将宿主机"/"根目录下所有文件传输到Docker的守护进程。

COPY指令

COPY指令有2种书写格式

COPY [--chown=user:group] 本地源文件 容器目标目录

COPY [--chown=user:group] ["本地源文件", "容器目标目录"]

默认所有拷贝到容器的文件属主(UID)和属组(GID)都是 0(root用户),除非可选参数--chown指定用户名、组名或UID/GID。--chown允许是username和groupname字符串,或者使用UID和GID。

--chown 特性仅支持用于构建Linux容器的Dockerfiles,在Windows容器上无效。这是因为Linux和Windows的用户和组的概念是有差异并且不能相互转换的,因此使用/etc/passwd和/etc/group将用户和组名称转换为UID或GID此功也仅适用于基于Linux操作系统的容器。

COPY指令的本地源文件支持使用模糊匹配和正则匹配。

COPY指令中源文件的路径是以上下文(context)作为起始点,而不是宿主机上某个绝对路径下的文件。也因此推荐用户在构建镜像时使用"."作为上下文,并且事先将Dockerfile中所需文件拷贝到指定的上下文路径下。

注意:使用COPY指令拷贝的源文件是多个文件(不是一个文件)时,则目标目录必须以"/"结尾,如“/test/",不能写成“/test”。

演示示例1

# mkdir demo2
# cd demo2
# mkdir dir{a..z}
# touch arr{a..z}.txt
# cat >Dockerfile < 
  

RUN指令

RUN指令有2种书写格式

RUN 命令         #RUN指令后面所接的命令是在shell中运行的,在Linux上就好比执行了 /bin/sh -c command (例如在终端执行:/bin/sh -c ls),在Windows上就好比是执行 cmd /S /C command(例如按win+r键在“运行”窗口执行: cmd /S /C mstsc)

RUN ["命令", "参数1", "参数2"]             #以exec的方式运行

RUN指令执行的任何命令都发生在当前镜像之上的一个新层中(即一个中间容器intermediate container),命令执行结束后会将结果提交到新的镜像(如同docker commit),并删除中间容器,所以我们在执行RUN指令时会经常看到类似如下日志。

Step 9/19 : RUN chmod 755 /root/start.sh

---> Running in a2e6b9ce0940

Removing intermediate container a2e6b9ce0940

---> 6b03e9b0ce70

新提交镜像将用于Dockerfile中的下一个指令。分层运行指令和提交符合Docker的核心概念,在Docker中,提交开销很小,我们可以从镜像历史记录的任何一点来创建容器,就像源代码管理一样。

CMD指令

CMD指令有3种书写格式

CMD 命令              #以shell方式运行

CMD ["可执行程序","参数1","参数2"]                 #exec方式,推荐运行方式

CMD ["参数1", "参数2"]            #用于给ENTRYPOINT指令提供默认参数

Dockerfile中只能有一条CMD指令。如果出现多条,则只有最后一条指令生效。

注意: CMD指令会被 docker run 后的参数所覆盖。例如:

docker run -idt --name demo01 demo:v0.1 /bin/bash        # "/bin/bash"将会覆盖CMD指令

ENTRYPOINT指令

ENTRYPOINT指令有2种书写格式

ENTRYPOINT [“命令”,“参数1”,“参数2”]                     #执行效果同docker exec

ENTRYPOINT 命令 参数1 参数2                     #执行效果同shell命令

ENTRYPOINT指令不会被 docker run 后的参数所覆盖,而是附加在ENTRYPOINT指令之后。而且CMD指令中的参数会传递给ENTRYPOINT。

同CMD指令一样,Dockerfile中也只能有一条ENTRYPOINT指令,并且若是多条则最后一条生效。

CMD与ENTRYPOINT指令存在的目的就是在容器启动时就运行必要的应用程序。

演示示例2

验证CMD指令

# mkdir demo2
# cd demo2
# cat >Dockerfile < 
  

构建镜像

# docker image build -t demo02:v0.1 .

根据上述镜像创建容器demo02,并且不指定类似"/bin/bash" 命令

# docker run -idt --name demo02 demo02:v0.1

查看容器demo02日志输出

# docker logs demo02

PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.

64 bytes from 114.114.114.114: icmp_seq=1 ttl=61 time=36.7 ms

64 bytes from 114.114.114.114: icmp_seq=2 ttl=61 time=36.5 ms

登录到容器,并执行top命令查看,如图1.1所示。

# docker exec -it demo02 /bin/bash

Dockerfile最佳实践(二)巩固篇_第1张图片

图1.1


如果,我们在创建容器demo02_2时指定运行参数 "/bin/bash",如

# docker run -idt --name demo02_2 demo02:v0.1 /bin/bash


查看容器demo02_2运行状态

# docker ps -a |grep demo02_2

9b76b8af295d    demo02:v0.1      "/bin/bash"              7 seconds ago                   Up 7 seconds                    demo02_2

登录到容器,使用top命令查看容器进程,可以看到CMD指令被docker run 后的"/bin/bash"参数覆盖了,如图1.2所示。

# docker exec -it demo02_2 /bin/bash


Dockerfile最佳实践(二)巩固篇_第2张图片

图1.2

演示示例3

验证ENTRYPOINT指令

# mkdir demo3
# cd demo3
# cat >Dockerfile < 
  

注意,创建容器时不指定类似"/bin/bash"

# docker run -idt --name demo03 demo03:v0.1

查看容器日志

# docker logs demo03

PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.

64 bytes from 114.114.114.114: icmp_seq=1 ttl=61 time=36.6 ms

64 bytes from 114.114.114.114: icmp_seq=2 ttl=78 time=36.5 ms

登录到容器,并执行top命令查看,如图1.3所示。

# docker exec -it demo03 /bin/bash


Dockerfile最佳实践(二)巩固篇_第3张图片

图1.3


创建容器demo05时指定运行参数 ”127.0.0.1”

# docker run -idt --name demo05 demo03:v0.1 127.0.0.1

进入容器demo05并执行top查看,确认docker run 后的参数127.0.0.1是传递给了ENTRYPOINT指令,如图1.4所示。

Dockerfile最佳实践(二)巩固篇_第4张图片

图1.4


演示示例4

ENTRYPOINT指令和CMD指令结合使用

我们将可能会调整的参数写到CMD指令。然后在docker run 里指定参数,这样CMD指令后的参数就会被覆盖掉而ENTRYPOINT里的不被覆盖。

# cat >Dockerfile < 
  

创建容器demo03_2时指定运行参数为"-c 3"

# docker run -idt --name demo03_2 demo03:v0.2 -c 3

查看容器运行日志,确认是ping -c 3次后结束,即CMD指令后的参数被覆盖为CMD ["-c 3"]

# docker logs demo03_2

PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.

64 bytes from 114.114.114.114: icmp_seq=1 ttl=64 time=36.7 ms

64 bytes from 114.114.114.114: icmp_seq=2 ttl=59 time=36.5 ms

64 bytes from 114.114.114.114: icmp_seq=3 ttl=62 time=36.4 ms

--- 114.114.114.114 ping statistics ---

3 packets transmitted, 3 received, 0% packet loss, time 2003ms

rtt min/avg/max/mdev = 36.495/36.613/36.789/0.254 ms


Dockerfile使用管道符或标准输入方式来构建镜像

示例:

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -

或者

docker build -< 
  

上述2个示例是等价的

docker build [OPTIONS] - 中的"-"是连接符,用于获取路径的位置,并指示Docker从stdin读取构建上下文。

以下示例我们尝试使用COPY或ADD指令

# mkdir example1
# cd example1
# touch somefile.txt
docker build -t myimage:v0.1 -< 
  

会提示如下错误,因为上述并没有指定从本地来构建上下文

Sending build context to Docker daemon 2.048kB

Step 1/3 : FROM busybox

---> b534869c81f0

Step 2/3 : COPY somefile.txt .

COPY failed: stat /var/lib/docker/tmp/docker-builder461920604/somefile.txt: no such file or directory

但使用以下语法就可以使用本地文件系统上的文件,并且使用stdin中的Dockerfile来构建镜像。语法使用-f(或--file)选项指定要使用的Dockerfile,使用连字符(-)作为文件名指示Docker从stdin读取Dockerfile

docker build [OPTIONS] -f- PATH

以下示例我们尝试使用COPY或ADD指令

# mkdir example2
# cd example2
# touch somefile.txt
# docker build -t myimage:v0.2 -f- . < 
  


从远端仓库接取代码然后再从标准输入读取Dockerfile构建镜像

docker build [OPTIONS] -f- PATH

如果要从不包含Dockerfile的git仓库中来构建镜像,或者是使用自定义Dockerfile构建镜像,则此语法非常有用。

# docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git < 
  

注意:上述示例执行成功前提是已安装好git客户端

总结

本节示例较多,但均是非常简单的示例,说话孰能生巧,需勤加练习。另Docker官方建议,为降低复杂性、依赖性、以及镜像大小和构建时间,要尽量避免安装额外的或不必要的包。