TaoTaoFu 2020-08-03
一般Dockerfile分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。如下:
# This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: Ray # Command format: Instruction [arguments / command] .. # Maintainer: docker_user <docker_user at email.com> (@docker_user) MAINTAINER Ray # Commands to update the image RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list RUN apt-get update && apt-get install -y nginx RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf # Commands when creating a new container CMD /usr/sbin/nginx
其中,一开始必须指明所基于的镜像,接下来推荐说明维护者信息,再接下来就是镜像操作的指令,如RUN、COPY等。每运行一条指令,就会为镜像添加新的一层并提交,注:一个镜像最多不允许超过127层。最后时CMD指令,是指定运行容器时的操作指令。
2、Dockerfile中的指令
1)FROM——指定基础镜像
格式为:FROM <image>或者 FROM <image>:<tag>。
第一条指令必须为FROM指令,如果在同一个dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次,但是一般不会这么做)。
2)MAINTAINER——指定维护者信息
格式为:MAINTAINER <name> <email>。用来指定维护者信息。
3)RUN——运行指令
格式为:RUN <command> 或者RUN ["executable", "param1", "param2"]。
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN ["/bin/bash", "-c", "echo hello"]。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。(注:如果觉得镜像的层数可能过多,可以一个RUN指令后面接多条指令,中间使用&&进行拼接即可)。
4)COPY——复制文件\目录
格式为:格式为 COPY <src> <dest>。
作用:复制本地的<src> (源文件/目录必须要与Dockerfile在相同的目录中)到容器中的<dest>。
当使用本地目录为源目录时,推荐使用COPY。
使用COPY时,所指定的源文件/目录,也可以是其他镜像中的文件,格式如下:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
5)ADD——更高级的复制文件\目录
格式为:ADD <src> <dest>。它和COPY很相似,同样需要源文件和Dockerfile位于相同目录中,或者是一个URL。它比COPY更为人性化些。
该命令将复制指定的 <src> 到容器中的 <dest>。 其中 <src> 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL(自动下载URL所对应的文件);还可以是一个 tar 文件(自动解压为目录)。
在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。
ADD --chown=55:mygroup files* /mydir/ ADD --chown=bin files* /mydir/ ADD --chown=1 files* /mydir/
ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
6)ENV——设置环境变量
格式为 ENV <key> <value>。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
举个栗子:
[ nginx]# cat Dockerfile # test FROM nginx:latest MAINTAINER Ray <> ENV var1 hello world ENV var2 test RUN echo ${var1},${var2} > /test.txt #最终此镜像运行的容器中test.txt文件内容如下: :/# cat test.txt hello world,test #并且定义的变量存在该容器的环境变量中: :/# echo $var1 hello world :/# echo $var2 test
7)ARG——构建参数
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
在 1.13 之前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg 指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。
8)EXPOSE——暴露端口
格式为:EXPOSE <port> [<port>...]。
该指令的作用是告诉docker服务端容器暴露的端口号,供互联系统使用,在启动容器时需要通过-P,docker主机会自动分配一个端口转发到指定的端口。
9)CMD——容器启动命令
它支持以下三种格式:
CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;
作用是指定启动容器时执行的命令,每个dockerfile只有一条CMD命令,如果指定了多条,那么前面的会被覆盖,只有最后一条指令生效。
如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。
如下:
[ nginx]# cat Dockerfile #dockerfile内容如下 # test FROM nginx:latest CMD echo hello world CMD echo hello [ nginx]# docker run -t zyz:v2 #只有最后一条CMD指令生效 hello [ nginx]# docker run -t zyz:v2 echo 123456 #启动容器时又指定了其他指令,则会覆盖掉dockerfile中的所有指令 123456
一般我将CMD和ENTRYPOINT结合使用。也就是上面的第三种格式。
10)ENTRYPOINT——入口点
它支持下面两种格式:
ENTRYPOINT ["executable", "param1", "param2"];
ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被docker run提供的参数覆盖。
每个dockerfile中只能有一个ENTRYPOINT ,当指定多个时,只有最后一个起效。
使用举例:
[ nginx]# cat Dockerfile #dokerfile文件如下 # test FROM nginx:latest ENTRYPOINT echo hello world ENTRYPOINT echo hello [ nginx]# docker run -t zyz:v3 #运行此镜像 hello [ nginx]# docker run -t zyz:v3 echo 123456 #运行时指定的命令也不会被执行 hello #但是可以通过“--entrypoint”指令将镜像中的ENTRYPOINT指令覆盖,只能是命令字 [ nginx]# docker run --entrypoint hostname -t zyz:v3 afb421b81a7d
11)ENTRYPOINT和CMD组合使用
在某种情况下,ENTRYPOINT和CMD组合使用能发挥更大的作用。
组合使用ENTRYPOINT和CMD, ENTRYPOINT指定默认的运行命令, CMD指定默认的运行参数。
举个栗子:
[ nginx]# cat Dockerfile #Dockerfile文件如下 # test FROM centos:7 ENTRYPOINT ["/bin/ping","-c","3"] CMD ["localhost"] [ nginx]# docker run -t zyz:v4 #运行容器 PING localhost (127.0.0.1) 56(84) bytes of data. 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.028 ms 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.072 ms 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.074 ms
查看容器最后一条执行的命令如下:
上面执行的命令是ENTRYPOINT和CMD指令拼接而成. ENTRYPOINT和CMD同时存在时, docker把CMD的命令拼接到ENTRYPOINT命令之后, 拼接后的命令才是最终执行的命令. 但是由于上文说docker run命令行执行时, 可以覆盖CMD指令的值. 如果你希望这个docker镜像启动后不是ping localhost, 而是ping其他服务器,, 可以这样执行docker run:
下表列出了如果把Shell表示法和Exec表示法混合, 最终得到的命令行, 可以看到如果有Shell表示法存在, 很难得到正确的效果:
Dockerfile Command ENTRYPOINT /bin/ping -c 3 CMD localhost #拼接后的指令如下: /bin/sh -c ‘/bin/ping -c 3‘ /bin/sh -c localhost ENTRYPOINT ["/bin/ping","-c","3"] CMD localhost #拼接后的指令如下: /bin/ping -c 3 /bin/sh -c localhost ENTRYPOINT /bin/ping -c 3 CMD ["localhost"]" #拼接后的指令如下: /bin/sh -c ‘/bin/ping -c 3‘ localhost ENTRYPOINT ["/bin/ping","-c","3"] CMD ["localhost"] #拼接后的指令如下: /bin/ping -c 3 localhost
从上面看出, 只有ENTRYPOINT和CMD都用Exec表示法, 才能得到预期的效果。
12)VOLUME——定义匿名卷
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
指令格式为:VOLUME ["/data"]。
作用:/data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
这种方式是docker manager volumes数据持久化方式,不支持Bind mount挂载方式(也就是不支持指定本地的目录)。
在基于镜像运行容器后,可以通过命令“docker inspect container_name”查看容器的详细信息,在返回的结果中,查看MOUNT字段可以看到容器内对应的本地目录位置,如下:
[ volumes]# docker inspect web02
返回的结果如下:
13)USER——指定当前用户
命令格式为:USER <用户名>[:<用户组>]。
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ]
如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" && chmod +x /usr/local/bin/gosu && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ]
14)WORKDIR——指定工作目录
格式为:WORKDIR /path/to/workdir。
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
则最终路径为 /a/b/c。
15)ONBUILD——为他人做嫁衣
格式为:ONBUILD [INSTRUCTION]。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。