Dockerfile可以认为是一个脚本,包含如何构建Docker镜像的说明。实际上,这些指令是一组在Docker环境中自动执行的命令,以构建特定的Docker镜像。
指令关键字
FROM
Docker镜像有着分层的概念,因此制作任何一个Docker镜像都需要有一个基础镜像,FROM
用于指定基础镜像,语法为
1 | FROM <image>:<tag> |
其中基础镜像的tag可以不指定,即默认使用latest,在制作时尽量要使用官方的镜像作为基础镜像,如果想制定一个小型轻量的镜像,基础镜像可以选择Alpine
另外需要提到的是,这里说了任何镜像都需要有一个基础镜像,那么问题来了,就好比是先有鸡还是先有蛋的问题,基础镜像的“祖宗”是什么呢?能不能在构建时不以任何镜像为基础呢?答案是肯定的,可以选用scratch,具体就不展开了,可以参考:Create a base image
1 | FROM scratch |
LABEL
LABEL
一般用来添加镜像的 “元数据” ,没有实际作用。常用于声明镜像作者,licensce等信息,写法为<key>=<value>
,语法为
1 | LABEL my.app="foo" |
在镜像构建后并成功运行容器,可以通过inspect
查看
1 | docker image inspect --format='' myimage |
如果要声明作者,语法为
1 | LABEL maintainer="ashinWu" |
之前直接通过指令MAINTAINER指定,这种写法已经废弃掉了
ENV
ENV
用来设置环境变量,一旦环境变量设置,就可以在Dockerfile后面的内容及容器运行后的应用中获取使用这个环境变量,ENV的写法也是<key>=<value>
,语法为
1 | ENV MY_NAME="ashinWu" |
COPY和ADD
COPY
和ADD
都是用于在构建时往镜像中复制文件或目录的,并且两者都支持在复制时修改文件或目录的属主和属组,语法为
1 | ADD [--chown=<user>:<group>] <src>... <dest> |
两者的使用差不多,但ADD功能更丰富:
- 支持URL
例如源路径是文件的URL链接,构建时自动进行下载,下载后放到目标路径下,文件权限为600 - 压缩包自动解压
例如tar、gzip、bzip2、xz格式的压缩包,ADD指令将会自动解压缩这个压缩文件到目标路径去
ARG
ARG
设置的是构建时的参数,也可以理解为构建时的环境变量,与ENV的不同是只在构建时生效,生成的镜像中是不存在的
可以在ARG
中同时声明参数名和参数值
也可以只声明参数名,在构建时通过–build-arg<参数名>=<值>的形式来赋值,赋值的前提是在Dockerfile中进行了声明,否则会出现警告
语法为
1 | ARG <name>[=<default value>] |
RUN
RUN
指令表示在当前的镜像层之上的新层中执行命令并将结果提交,用户Dockerfile的下一步操作,语法为
1 | RUN /bin/bash -c 'source $HOME/.bashrc; \ |
在RUN命令中也可以使用exec格式来避免shell字符串损坏,语法为
1 | RUN ["/bin/bash", "-c", "echo hello"] |
RUN作为Dockerfile中最为常用的指令,在使用时有以下建议:
在RUN指令执行过程中,产生的中间镜像会被当做缓存在下一次构建时使用,如果不想使用缓存,使其失效,可以在build时添加–no-cache
尽量把所有的RUN指令写到一起,如果是多条shell命令,可以不用每条命令都添加RUN,更好的做法是通过\换行,通过&&连接多个指令,这样对构建生成的镜像的大小优化是很有帮助的,语法为
1
2
3
4
5RUN set -x && \
yum install -y epel-release \
make \
gcc \
gcc-c++
WORKDIR
WORKDIR
指令为Dockerfile中的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录。如果工作目录不存在,即使它没有在后续的Dockerfile指令中使用,它也会被创建。
WORKDIR指令可以在Dockerfile中使用多次。如果提供了一个相对路径,它将相对于前一个WORKDIR指令的路径,语法为
1 | WORKDIR /a |
这个Dockerfile中的最后一个pwd命令的输出是/a/b/c
WORKDIR
指令也可以解析之前使用ENV设置的环境变量,只能使用在Dockerfile中显式设置的环境变量,语法为
1 | ENV DIRPATH=/path |
这里的最终路径是/path/$DIRNAME
EXPOSE
EXPOSE
指令声明了容器在运行时监听指定的网络端口,可以指定端口是监听TCP还是UDP,默认为TCP
EXPOSE
指令实际上并不发布端口,即端口限制,它的作用仅仅是作为构建映像的人和运行容器的人之间的一种文档,关于要发布哪些端口。当运行容器时,要实际发布端口,使用docker运行中的-p参数来发布和映射一个或多个端口,或者直接使用-P来自动随机映射EXPOSE
声明的端口
语法为
1 | EXPOSE <port> [<port>/<protocol>...] |
CMD和ENTRYPOINT
- CMD
CMD
和ENTRYPOINT
都是指定容器将如何运行
CMD
的主要目的是为执行容器提供默认值。这些默认值可以包括可执行文件,也可以省略可执行文件,在这种情况下,必须指定一个ENTRYPOINT
指令
CMD
指令有三种形式
1 | CMD ["executable","param1","param2"] # 首选的exec格式 |
注意:在Dockerfile中只能有一条CMD
指令,如果指定了多条,只有最后一条会生效
- ENTRYPOINT
ENTRYPOINT有两种形式
1 | ENTRYPOINT ["executable", "param1", "param2"] # exec格式 |
如果指定了ENTRYPOINT
,则CMD
将只是提供参数,传递给ENTRYPOINT
docker run <image>
的命令行参数将被附加在exec
类型的ENTRYPOINT
的所有元素之后,并将覆盖使用CMD
指定的所有元素。这允许参数被传递给ENTRYPOINT
例如,docker run <image> -d
将传递-d参数给ENTRYPOINT
也可以使用docker run --entrypoint
覆盖ENTRYPOINT
指令
- CMD和ENTRYPOINT组合出现
官方有一段关于CMD和ENTRYPOINT组合出现时的结果
No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
- 在k8s中的场景
k8s中可以通过资源清单的command、args也可以为Pod指定一些运行参数,四者组合出现时的最终结果如下
如果要覆盖默认的Entrypoint
与Cmd
,需要遵循如下规则:
如果在容器配置中没有设置 command 或者 args,那么将使用Docker镜像自带的命令及其参数
如果在容器配置中只设置了 command 但是没有设置 args,那么容器启动时只会执行该命令, Docker镜像中自带的命令及其参数会被忽略
如果在容器配置中只设置了 args,那么Docker镜像中自带的命令会使用该新参数作为其执行时的参数
如果在容器配置中同时设置了 command 与 args,那么Docker镜像中自带的命令及其参数会被忽略。 容器启动时只会执行配置中设置的命令,并使用配置中设置的参数作为命令的参数
镜像 Entrypoint | 镜像 Cmd | 容器 command | 容器 args | 命令执行 |
---|---|---|---|---|
[/ep-1] | [foo bar] | [ep-1 foo bar] | ||
[/ep-1] | [foo bar] | [/ep-2] | [ep-2] | |
[/ep-1] | [foo bar] | [zoo boo] | [ep-1 foo bar] | |
[/ep-1] | [foo bar] | [/ep-2] | [zoo boo] | [ep-2 foo bar] |
HEALTHCHECK
HEALTHCHECK
用于检查容器的健康状态,Docker可通过健康状态来决定是否对容器进行重新调度
语法为
1 | HEALTHCHECK [选项] CMD <命令> |
可选项为
1 | –interval=<间隔> :两次健康检查的间隔,默认为30秒 |
对一些可能造成假死情况的服务建议提供健康检查,以便及时重新调度恢复服务
如果基础镜像有健康检查指令,想要屏蔽掉其健康检查,可以使用HEALTHCHECK NONE
实际上有了k8s更为丰富的健康检查探针之后,Docker自带的健康检查就不用了.
ONBUILD
当我们在一个Dockerfile文件中加上ONBUILD指令,该指令对利用该Dockerfile构建的镜像不会产生实质性影响
但是当我们编写一个新的Dockerfile文件来基于上面通过包含ONBUILD
构建的基础镜像构建一个新镜像时,这时构造基础镜像的Dockerfile文件中的ONBUILD
指令就生效了,在构建新镜像的过程中,首先会执行ONBUILD指令指定的指令,然后才会执行其它指令
要注意的是ONBUILD
仅仅能 “子代遗传” ,并不能 “隔代遗传” ,即传递到 “孙子镜像”
镜像构建
构建上下文
构建上下文build context,”上下文” 意为和现在这个工作相关的周围环境。在docker镜像的构建过程中有构建上下文build context这一概念,通俗的来说就是指执行docker build时当前的工作目录,不管构建时有没有用到当前目录下的某些文件及目录,默认情况下这个上下文中的文件及目录都会作为构建上下文内容发送给Docker Daemon
当docker build开始执行时,控制台会输出Sending build context to Docker daemon xxxMB
,这就表示将当前工作目录下的文件及目录都作为了构建上下文
前面提到可以在RUN指令中添加–no-cache不使用缓存,同样也可以在执行docker build命令时添加该指令以在镜像构建时不使用缓存
忽略构建
和git忽略文件.gitignore
一样的道理,在docker构建镜像时也有.dockerignore
,可以用来排除当前工作目录下不需要加入到构建上下文build context中的文件
例如,在构建npm前端的镜像时项目时,在 Dockerfile 的同一个文件夹中创建一个 .dockerignore 文件,带有以下内容,这样在构建时就可以避免将本地模块以及调试日志被拷贝进入到Docker镜像中
多阶段构建
多阶段构建的应用场景及优势就是为了降低复杂性并减少依赖,避免镜像包含不必要的软件包
例如,应用程序的镜像中一般不需要安装开发调试软件包。如果需要从源码编译构建应用,最好的方式就是使用多阶段构建
简单来说,多阶段构建就是允许一个Dockerfile中出现多条FROM指令,只有最后一条FROM指令中指定的基础镜像作为本次构建镜像的基础镜像,其它的阶段都可以认为是只为中间步骤
每一条FROM指令都表示着多阶段构建过程中的一个构建阶段,后面的构建阶段可以拷贝利用前面构建阶段的产物
这里列举一个编译构建npm项目,利用多阶段构建最终把静态资源制作成nginx镜像的Dockerfile
1 | #### Stage 1: npm build |