Dockerfile 文件介绍
概述
Dockerfile 是一个文本文件,其中包含若干指令,用以层级构建 docker image。
为方便介绍 Dockerfile 有关内容,需要简单说明 image
和 container
的底层原理。
image
实际是一个层级只读文件,其结构大致如图所示:
container
可理解为镜像之上添加一个可读写层。容器之中所执行的任何操作 (例如,添加内容、修改 image
内容) 都会保存至该可读写层。
此时我们看一下 docker commit
操作,基于此操作构建得到的镜像等价于运行此容器所使用的镜像 + 该可读写层。这基本是 Dockerfile 构建镜像的原理,即以基本镜像为基础,生成容器并运行它,执行相关操作,docker commit
得到中间镜像,随后以此中间镜像为基础,再次执行类似步骤,最终得到目标镜像。
Dockerfile
此节介绍 dockerfile 之中所涉及的各种内容。
需要注意两点:
- Dockerfile 生成器将自上而下、逐行执行 Dockerfile 文件中的指令。
- Dockerfile 中每条构建指令的执行结果都会成为目标镜像中的一层。
解析器指令
解析器指令为 Dockerfile 的可选内容,它会影响 Dockerfile 文件中后续指令的执行方式。
其具体格式为 # directive=value
,且特定的 directive
仅能出现一次。Dockerfile 支持的解析器指令具体如下:
syntax –>
# syntax [remote image reference]
syntax 指令用于指定构建当前 Dockerfile 文件所使用的 Dockerfile 生成器位置。
例如:
# syntax=docker/dockerfile:1.0.0
,指定 Dockerfile 生成器位置为远端仓库的 docker/dockerfile:1.0.0。escape –>
# escape \
escape 指令用于指定 Dockerfile 文件中的转义字符,默认转义字符为
\
。例如:
# escape |
,指定转义字符为|
。
如果注释、空行、生成指令均已按序执行完成,则后续出现的解析器指令将被自动认定为注释。因此,为保证解析器指令正确执行,最好将其置于 Dockerfile 文件顶部。
解析器指令的执行结果不会成为目标镜像的一层,也不会出现在构建输出结果之中。
注释
除解析器指令外,凡是以 #
开头的行均为注释行。
执行 Dockerfile 前,会自动移除其中注释。因此,当分行书写一条指令时,其中穿插注释并不会影响指令执行效果。
.dockerignore 文件
该文件类似于 .gitignore
文件,用于排除使用 Dockerfile 构建镜像过程中所涉及的特定文件或文件夹。
构建指令
构建指令为 Dockerfile 的主体内容,它定义了镜像的具体构建过程。
构建指令一般格式为 INSTRUCTION arguments
,其中 INSTRUCTION
指明具体指令,arguments
为所涉参数。Dockerfile 支持的构建指令具体如下:
FROM –>
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM 指令用于指明目标镜像所使用的基础镜像。
例如:
FROM --platform=linux/amd64 centos:lastest AS CS
,以 centos:lastest 为基础镜像,并将此构建阶段命名为 CS,同时指定构建平台为 linux/amd64 架构 (默认为本机所处的构建平台)。除解析器指令、注释、ARG 指令外,FROM 指令应当为 Dockerfile 文件自上而下的第一条指令,其标志
build stage
的开始。RUN –>
RUN <command> <param1>
/RUN ['executable','param1']
RUN 指令用于生成目标镜像的一层,该层为基于当前最新中间镜像得到的容器的可读写层。
例如:
RUN 'echo $HOME'
,基于当前最新中间容器得到的镜像中执行echo $HOME
shell 命令,并以可读写层作为目标镜像的一层。ADD –>
ADD [--chown=<user>:<group>] <src>... <dest>
ADD 指令用于将
src
所指代的文件、文件夹或远程文件 URL 添加至当前最新中间镜像的文件系统之中。例如:
ADD test.txt /usr/
,将上下文中的test.txt
文件添加至文件系统的/usr/
处。COPY –>
COPY [--chown=<user>:<group>] <src>... <dest>
COPY 指令用于将
src
所指代的文件或文件夹添加至当前最新中间镜像的文件系统之中。COPY 指令存在可选参数
--from=<name>
,其可用于在multi-stage
之中重置当前指令所使用的stage context
。ENTRYPOINT –>
ENTRYPOINT <command> <param1>
/ENTRYPOINT ['executable','param1']
ENTRYPOINT 指令用于配置容器启动时自动执行的命令。
它具有两种使用方式:shell 形式 (
ENTRYPOINT <command> <param1>
) 和 exec 形式 (ENTRYPOINT ['executable','param1']
),前者会首先调用 shell 窗口,随后在其中执行命令,后者则直接执行命令。如果 Dockerfile 中存在多条 ENTRYPONT 指令,则最后一个 ENTRYPOINT 指令会生效。另外,如果不希望默认执行 ENTRYPOINT 所指代的命令,可在运行容器时使用参数
--entrypoint
重新设置。例如:
ENTRYPOINT ['/bin/echo','hello']
,容器启动后,它会自动执行命令/bin/echo 'hello'
。CMD –>
CMD <command> <param1>
/CMD ['executable','param1']
/CMD ['param1']
CMD 指令用于配置容器启动时自动执行的默认命令。
它具有三种用法,前两种用法与 ENTRYPOINT 相同,在此仅说明第三种用法,即
CMD ['param1']
。它需要与 ENTRYPOINT 指令联合使用,并以param1
作为 ENTRYPOINT 指令所执行命令的参数。如果 Dockerfile 中存在多条 CMD 指令,则最后一个 CMD 指令会生效。另外,如果用户运行待执行容器时指定相关参数,则其会覆盖 CMD 指令。
鉴于 ENTRYPOINT 与 CMD 指令十分相似,在此联合二者举例。
假定 Dockerfile 含有如下内容:
1
2ENTRYPOINT echo
CMD ['hello']当运行容器时,如果具体命令为
docker run centos
,则容器启动后自动执行echo "hello"
;如果具体命令为docker run centos "world"
,则容器启动后自动执行echo "world"
;如果具体命令为docker run --entrypoint='touch' centos
,则容器启动后自动执行touch "hello"
。EXPOSE –>
EXPOSE <port>
EXPOSE 指令用于指明目标镜像向外发布的端口,它类似于一个介于发布镜像者与运行容器者之间的端口文档说明。
例如:
EXPOSE 80/udp
,向外暴露面向 UDP 的 80 端口。VOLUME –>
VOLUME [mountpoint]
VOLUME 指令用于创建一个挂载点。
当运行容器时,如果没有显式挂载此挂载点,则其会默认创建匿名卷挂载此挂载点。
例如:
VOLUME '/lib/bin'
。USER –>
USER <user>:[group]
USER 指令用于设置 UID 和 GID 以供容器运行时使用。
WORKDIR –>
WORKDIR <path>
WORKDIR 指令用于设置容器运行的工作目录。
例如:
WORKDIR /bin
,设置容器的/bin
目录为工作目录。LABEL –>
LABEL <key>=<value>
LABEL 指令用于为目标镜像添加元数据。
例如:
LABEL version="1.0"
。MAINTAINER –>
MAINTAINER <name>
MAINTAINER 指令用于指明维护者信息,也可使用
LABEL maintainer=<name>
达到同样效果。例如:
MAINTAINER "Stivin"
ENV –>
ENV <key>=<value>
ENV 指令用于设置运行于容器时的环境变量 (将持久化于容器之内),其可应用于 Dockerfile 文件中的后续指令中。
例如:
ENV 'PATH'='/bin/lib'
,设置环境变量PATH
为/bin/lib
。ARG –>
ARG <name>[=<value>]
ARG 指令用于设置 Dockerfile 文件中所使用到的变量 (不会存在于容器之内)。所定义的变量从其定义位置开始生效,在此位置之前引用将得到空字符串。
ONBUILD –>
ONBUILD xxx
ONBUILD 指令用于为目标镜像添加触发器 (本质就是若干指令)。如果目标镜像作为其他 Dockerfile 文件中的基础镜像,则 Dockerfile 生成器执行完
FROM xxx
后,这些触发器会自动执行。例如:
ONBUILD ADD . /app/src
。STOPSIGNAL –>
STOPSIGNAL <signal>
STOPSIGNAL 指令用于设置用以容器退出的信号。
例如:
STOPSIGNAL SIGKILL
。HEALTHCHECK –>
HEALTHCHECK [option] CMD command
/HEALTHCHECK none
HEALTHCHECK 指令用于设置执行容器健康检查时应当做的操作。
HEALTHCHECK 指令的两种形式中,
HEALTHCHECK [option] CMD command
用于设置具体执行哪些操作,HEALTHCHECK none
表示禁止继承自基础镜像的任何健康检查操作。例如:
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
。SHELL –>
SHELL ['exectuable','paramters]
SHELL 指令用于重写默认 shell 。
multi-stage
利用上面所学的各种指令,我们可以很容易地编写一个构建特定目标镜像的 Dockerfile 文件。但是,编写 Dockerfile 文件的难点在于如何保证目标镜像大小尽可能地小。这其中存在众多技巧,在此介绍一种常见技巧。
考虑一种常见场景:项目开发过程中,往往存在两个环境——应用开发环境和应用部署环境。当项目开发完成后,就需要将其放到应用部署环境之中。
就目前所知,我们应该会构建两个 Dockerfile 文件:第一个 Dockerfile 文件用于构建开发环境,并拷贝项目代码至镜像中,运行得到二进制程序后,将其拷贝至本地;第二个 Dockerfile 文件用于构建部署环境,并将先前得到的二进制程序放置其中。
这种过程比较复杂,而且会产生中间镜像 (即根据第一个 Dockerfile 所产生的目标镜像),十分繁琐。
如果借助于 multi-stage
,我们只需构建一个 Dockerfile 文件,即可达到同样的效果。
在该种情况下,Dockerfile 文件中会存在两个 stage
(每个 stage
以 FROM
指令标识开始,以下一个 FROM
指令标识结束),第一个 stage
用于构建开发环境,并拷贝项目代码至镜像中,运行得到二进制程序即可,第二个 stage
构建部署环境,并借助于 COPY 指令的 --from
参数将第一个 stage
中的二进制程序放置其中。
使用 Dockerfile 生成器构建
multi-stage
相关的 Dockerfile 文件时,其会保存各stage
的运行结果以供其他stage
使用,但是仅最后一个stage
的运行结果会保存于目标镜像之中。
Docker build
命令 Docker build
用于根据 Dockerfile 文件构建目标镜像。
1 | Usage: docker build [OPTIONS] PATH | URL | - |
当在 shell 窗口输入此命令后,客户端会将 Dockerfile 以及上下文环境 (指代运行此命令的当前目录的全部内容,如果其中文件符合 .dockerignore
,则其会被忽略) 发送至守护进程,守护进程验证 Dockerfile 语法无误后,使用 Dockerfile 生成器依次执行 Dockerfile 文件中指令,以逐步构建目标镜像。
目标镜像构建过程往往是比较缓慢的。为解决此问题,Docker 借助于缓存机制以加快构建过程。
该缓存机制可简单分解为如下三条规则 (这里暂不考虑解析器指令):
- 如果指令文本未曾发生变动,则可直接重用先前构建的缓存结果。
- 对于 COPY、ADD 指令而言,如果所涉文件和指令文本均未曾发生变动,则可直接重用先前构建的缓存结果。
- 如果当前指令构建的层无法使用缓存结果,则其后续指令构建的层亦不能使用缓存结果。
因为存在缓存机制,编写高效 Dockerfile 的技巧之一便是:将不变部分尽可能写于 Dockerfile 文件前部。
举例而言,假定初始 Dockerfile 如下:
1 | FROM python:3.7-slim-buster |
使用命令 docker build
构建目标镜像,可得到如下运行结果:
1 | Sending build context to Docker daemon 5.12kB |
不修改 Dockerfile,再次执行命令 docker build
,可得到如下运行结果 (step2-4 均使用缓存结果):
1 | Sending build context to Docker daemon 5.12kB |