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 $HOMEshell 命令,并以可读写层作为目标镜像的一层。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 xxxONBUILD 指令用于为目标镜像添加触发器 (本质就是若干指令)。如果目标镜像作为其他 Dockerfile 文件中的基础镜像,则 Dockerfile 生成器执行完
FROM xxx后,这些触发器会自动执行。例如:
ONBUILD ADD . /app/src。STOPSIGNAL –>
STOPSIGNAL <signal>STOPSIGNAL 指令用于设置用以容器退出的信号。
例如:
STOPSIGNAL SIGKILL。HEALTHCHECK –>
HEALTHCHECK [option] CMD command/HEALTHCHECK noneHEALTHCHECK 指令用于设置执行容器健康检查时应当做的操作。
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 |