Dockerfile常用指令梳理

Dockerfile reference
Best practices for writing Dockerfiles
Dockerfile 指令详解

镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。

Dockerfile 是一个脚本描述文件,其中包含了一条条构建镜像所需的指令和说明。Dockerfile 中的每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

使用 docker build 命令可以根据 Dockerfile 里编排的指令来构建定制docker镜像。大部分指令(如 RUN、COPY)运行于执行 docker build 的构建机;ENTRYPOINT 和 CMD 则运行于 docker run 启动的容器实例。

指令格式

Dockerfile 中典型指令格式如下:

# Comment
INSTRUCTION arguments
  1. 指令不区分大小写,但建议对指令大写,以便区分指令和一般参数。

  2. 和 Shell、Python 等脚本一样,以 # 开头的行被视作注释行,不参与执行。

    • 第一行注释可能是被称为parser directive类似Shebang的解释器指令,目前仅支持指定 escape 指令。
  3. Dockerfile 一般以 FROM 指令拉开序幕,指定继承的基础镜像。FROM 指令行之前可能还有parser directive、普通注释或仅供 FROM 指令引用的全局 ARG 参数。

FROM

FROM 指令开创一个新的构建阶段,其后的指令都基于该基础镜像。

FROM [--platform=<platform>] <image> [AS <name>]
# Or
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
# Or
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
  1. 一个 Dockerfile 中,可能存在多条 FROM 指令,用于构建多个镜像或者存在镜像依赖。
  2. ARG 是唯一允许出现在第一条 FROM 指令之前的指令,用于声明仅可被 FROM 指令引用的变量。FROM 之后的指令无法引用这个 ARG 参数。
ARG CODE_VERSION=latest

FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

ARG & ENV

ARG

为了提高构建定制镜像时的灵活性,可以在 Dockerfile 中通过 ARG varname 前向声明变量 varname(类似C语言中的 extern 变量声明),然后在执行 docker build 打包构建时通过 --build-arg <varname>=<value> 传参。

ARG <name>[=<default value>]

如果 docker build 传入了 --build-arg 参数 varname,但在 Dockerfile 中却没有声明 ARG varname,将会收到以下提示:

[Warning] One or more build-args [foo] were not consumed.

ENV

在 Dockerfile 中,可通过 ENV 指令设置环境变量(类似 Bash Shell 中的 export varname=value),该环境变量的生命周期将持续到 docker run 启动镜像的容器实例。

可以通过 docker inspect 查看某个镜像中定义的 ENV 环境变量,也可在 docker run 时通过 --env <key>=<value> 设置环境变量。

如果希望某个环境变量仅在build构建时有效(生命周期不持续到容器实例中),则可考虑在 RUN 指令中设置指令级作用域的环境变量:

RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...

或使用 ARG 声明配合 docker build --build-arg 传参:

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...

WORKDIR

在 Dockerfile 中,可通过 WORKDIR 指令修改当前工作目录,后续 RUN, CMD, ENTRYPOINT, COPYADD 等指令中的相对(源)目录路径基于 WORKDIR 展开。

相当于 bash shell 中 cd 设置 pwd,即使没有显式指定 WORKDIR,默认也会指定,可读取引用。

WORKDIR /path/to/workdir

COPY & ADD

COPY

COPY 指令用于将构建机上的资源(src)复制到镜像文件系统(dst)中。

  • 构建机:指执行构建命令(docker build --file ./Dockerfile)所在主机。
  • 资源:包括文件和目录,默认相对 Dockerfile 所在的目录。
  • 当目标路径不存在时会自动创建。

COPY 指令有两种写法,当路径中包含空格时,建议采用第二种方式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

<src> 可能包含通配符(wildcards),遵循Go语言的filepath.Match匹配法则。

ADD

ADD 指令用于将构建机或远程主机上的资源复制到镜像文件系统中。

ADD 指令有两种写法,当路径中包含空格时,建议采用第二种方式:

ADD [--chown=<user>:<group>] [--checksum=<checksum>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

<src> 可能包含通配符(wildcards),遵循Go语言的filepath.Match匹配法则。

difference

虽然 ADDCOPY 指令的功能相似,区别在于 COPY 指令仅支持将本地资源拷贝到镜像,而 ADD 增强支持下载远程主机资源。
一般建议优先采用 COPY 指令,因为 COPY 指令更加透明,ADD 指令的一些特性(例如下载、解压过程)则没有那么透明。
如果你需要拷贝多个资源,建议分多条指令分别 COPY,而不要在一条指令中同时拷贝多个资源,这样能保证独立文件最小缓存。

例如:

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

相比 COPY . /tmp/ 放在 RUN 之前而言,以上例子将使 RUN 命令使用更小的缓存更新。

另外,由于镜像大小的限制,需要下载远程资源时,非必要不建议采用 ADD,建议直接使用 curlwget

RUN

RUN 指令用于在当前镜像基础上执行新层命令。

RUN 指令有两种写法:

  • RUN <command> (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
  • RUN ["executable", "param1", "param2"] (exec form)

shell form

shell 形式的 RUN 指令中的命令默认执行的shell环境可以通过 SHELL 命令修改。

在 shell 形式的 RUN 指令中,可使用反斜杠(\)进行跨行书写(续行),考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

两个续行合并起来的效果等同于一下一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

exec form

exec 形式的 RUN 指令可避免shell字符串歧义,可以指定基础镜像中不存在的shell命令。

exec 形式可以指定希望的shell,如果想使用不同的shell环境(默认是 /bin/sh),例如以下指定在 /bin/bash 下执行命令 echo hello

RUN ["/bin/bash", "-c", "echo hello"]

difference

exec 形式通常解析为 JSON 数组,这也意味着必须使用双引号而不是单引号。

不同于 shell 形式,exec 形式不会新建 command shell,这意味着一些常规的shell处理可能不会执行。例如 RUN [ "echo", "$HOME" ] ,不会执行shell变量HOME的替换,而是当成普通字符串。

ENTRYPOINT & CMD

CMD

CMD 用于指定在镜像启动后的容器主进程的启动命令,这个“命令”必须是镜像内的软件。

CMD 指令有三种写法:

  • CMD ["executable","param1","param2"] (exec form, this is the preferred form)
  • CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
  • CMD command param1 param2 (shell form)

一般情况下,一个 Dockerfile 中只能有一条 CMD 指令,如果有多条则只有最后一条生效(前面的将被覆盖)。

可以指定 executable(方式1),也可不指定 executable(方式2),不指定时一般会在前面声明一条 ENTRYPOINT 指令,此时的作用是为 ENTRYPOINT 提供参数。当 ENTRYPOINTCMD 搭配使用时,注意都要是 JSON 数组形式,往往用作 service-based daemon 镜像(容器)。

如果使用 shell 格式的话,实际的命令会被包装为 /bin/sh -c 的参数的形式执行:

FROM ubuntu

CMD service nginx start

CMD service nginx start 会被理解为 CMD [ "/bin/sh", "-c", "service nginx start"],此时的主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了;sh 作为主进程退出了,容器就会退出。

如果希望以后台守护进程形式启动 nginx 服务,可使用JSON数组形式的 CMD,第一个参数指定 nginx 命令的绝对路径,后续逐个指定参数。这样将直接执行 nginx 命令(nginx -g 'daemon off;'),启动前台 nginx 服务。

FROM ubuntu

CMD ["nginx", "-g", "daemon off;"]

注意docker run 命令行参数将会覆盖 CMD 指令,例如 ubuntu 镜像默认的 CMD 是 /bin/bash,执行 docker run -it ubuntu 会直接进入 bash。运行 docker run -it ubuntu cat /etc/os-release 则用 cat /etc/os-release 命令替换默认的 /bin/bash 命令,输出系统版本信息。

ENTRYPOINT

ENTRYPOINT 指令用于配置容器的入口点,和 CMD 一样都是指定容器启动程序及参数。

ENTRYPOINTRUN 指令一样,分为 exec 和 shell 两种格式:

# exec form(preferred)
ENTRYPOINT ["executable", "param1", "param2"]

# shell form
ENTRYPOINT command param1 param2

docker run <image> 的命令行参数,将被添加到 exec 形式的 ENTRYPOINT 的 JSON 数组命令之后,例如 docker run <image> -d 将把 -d 选项(或参数)追加到 ENTRYPOINT 后。这样,给 docker run 带来一些定制传参灵活性。

ENTRYPOINTCMD 一般同时使用JSON数组形式配套使用。此时,ENTRYPOINT 后面的 CMD 的含义就发生了改变,CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时将变为 <ENTRYPOINT> "<CMD>"

shell 形式的 ENTRYPOINT 将会阻断后面的 CMD 指令,也不吸纳 docker run 后面的参数。这种形式的缺点是会被启动为 /bin/sh -c 的子命令,这将导致无法接收信号。此时,ENTRYPOINT 的 shell executable 将无法接收到 docker stop <container> 发出的 SIGTERM 信号。

一般情况下,一个 Dockerfile 中只指定一条 ENTRYPOINT,如果有多条只有最后一条生效(前面的将被覆盖)。运行 docker run 时,也可通过 --entrypoint 参数来指定覆盖 ENTRYPOINT 指令。

interact

既然 CMDENTRYPOINT 作用类似,有了 CMD 后,为什么还要有 ENTRYPOINT 呢?<ENTRYPOINT> "<CMD>" 这种组合形式可以带来哪些额外的便利呢?

以下描述了两条命令的协作机制:

  1. Dockerfile 必须指定至少一条 CMDENTRYPOINT 指令。
  2. ENTRYPOINT 可用于将整个容器作为一个 executable 的情形。
  3. CMD 可以用作定义 ENTRYPOINT 的更多参数,或者在容器中执行一条 ad-hoc 命令。
  4. docker run 中的参数可以覆盖 CMD 指令。

以下表格总结了 ENTRYPOINT / CMD 不同搭配组合形式的含义:

No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”]
*No CMD-*error, not allowed-/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
*CMD exec_cmd p1_cmd-/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

注意ENTRYPOINT 指令将会把基础镜像中定义的 CMD 复位为空,此时需要在当前 Dockerfile 中重新定义 CMD

scenario

启动容器就是启动主进程,但有些时候在启动主进程前,需要做一些准备工作。

考虑这样一种场景,容器主进程的作用是启动nginx作为web服务器,但是在启动nginx之前,需要用shell脚本(docker-entrypoint.sh)对 Flutter Web 进行一些预处理。

由于涉及到测试、预发布和发布三个环境,一份Flutter Web构建产物,最终将在集群中部署三个微服务。因此,在 Flutter Web 代码中,需要根据环境变量预埋插桩。在基座配置的 envVars libsonnet 定义环境变量 RUN_MODE,取值分别对应三个环境。

部署后的集群Pod管理的YAML中可看到 containers env 中的 RUN_MODE,容器启动后可读取该环境变量。sh 脚本根据 RUN_MODE 针对不同环境做一些处理,例如替换 CGI Host、替换 <base href> 、开启或关闭vconsole引入模块的注释开关等处理。

以下是 Dockfile,其中 ENTRYPOINT 指令指定入口点为自定义的docker-entrypoint.sh脚本,并带有2个参数。

FROM nginx:latest
# 将docker build构建命令所在主机本地文件(相对该 Dockerfile 所在目录),复制到容器镜像文件系统。
COPY app/scripts/docker/ /usr/local/etc/docker/
COPY app/build/web/ /usr/share/nginx/www/
EXPOSE 80
RUN chmod +x /usr/local/etc/docker/docker-entrypoint.sh
ENTRYPOINT [ "/usr/local/etc/docker/docker-entrypoint.sh", "/usr/share/nginx/www/main.dart.js", "/usr/share/nginx/www/index.html" ]
# 在nginx启动之前,COPY覆盖替换默认的nginx配置
COPY app/scripts/docker/nginx.conf /etc/nginx/nginx.conf
CMD ["nginx", "-g", "daemon off;"]

自定义的docker-entrypoint.sh脚本末尾一句为 exec "${@:3}"${@:3} 表示从索引3开始后面的所有参数。
ENTRYPOINT JSON数组有3个参数,CMD JSON数组的3个参数追加到ENTRYPOINT参数后面(索引从3开始),所以 exec "${@:3}" 的意思是将CMD JSON数组中的参数当做命令行执行,即在预处理完成后,执行 nginx -g 'daemon off;' 启动前台nginx服务。

refs

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值