logo头像
Snippet 博客主题

Dockerfile 参考手册(三):指令介绍

本文于351天之前发表,文中内容可能已经过时。

接着上一篇《Dockerfile 参考手册(二):指令介绍》继续介绍Dockerfile相关指令。文档是基于Docker v17.09 版本。
文章内容完全是翻译官方文档

01.10 ENTRYPOINTY

ENTRYPOINT 有两种形式:

  • ENTRYPOINT ["executable", "param1", "param2"](exec形式,推荐)
  • ENTRYPOINT command param1 param2(shell形式)

ENTRYPOINT可以让你的容器功能表现得像一个可执行程序一样。

例如下面的命令将使用默认的内容启动nginx,监听端口为80:

docker run -i -t --rm -p 80:80 nginx

docker run <image>的所有参数被追加到exec形式的ENTRYPOINT之后,并且会覆盖所有的CMD参数。entry point也可以从外部传入参数,例如,docker run <image> -d将传递-d的参数到entry point。你也可以使用docker run --entrypoint覆盖ENTRYPOINT指令。

shell形式不能使用CMD或者RUN命令的任何参数,但是也有一个优势是,你的ENTRYPOINT是作为/bin/sh -c的子命令启动的,它是不能传递信号的,这就意味着此程序不是容器的PID 1,不能接收Unix信号,因此你的程序是不能接收到docker stop <container>的终止信号。

在一个Dockerfile中只有最后一个ENTRYPOINT指令是有效的。

01.10.1 exec形式的ENTRYPOINTY示例

你可以使用ENTRYPOINT指令的exec形式设置非常稳定的默认命令和参数,然后使用CMD指令设置其它很可能需要改变的参数。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

当你启动容器的时候,你可以通过top命令看到这是唯一的进程:

$ docker run -it --rm --name test  top -H
top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

进一步检查结果,你可以使用docker exec

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

你可以使用docker stop test优雅地将top进程关闭。

在下面的Dokcerfile中展示了,使用ENTRYPOINT在前台运行一个Apache的例子(例如,PID 1):

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

如果你需要为这个独立的程序写一个启动脚本,你可以确保最后的程序能依靠execgosu指令接收到Unix信号:

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果你需要在关闭的时候做一些额外的清理(或者和其他的容器通信),或者协调多个程序,你可能需要确保ENTRYPOINT脚本能够接收到Unix信号,传递过去之后能做更多的工作:

#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

如果你使用命令docker run -it --rm -p 80:80 --name test apache运行这个镜像,然后你可以使用docker exec或者docker top检查容器的进程,也可以调用脚本停止Apache:

$ docker exec -it test ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux
$ docker top test
PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real    0m 0.27s
user    0m 0.03s
sys    0m 0.03s

注意:你可以使用--entrypoint覆盖ENTRYPOINT,但是仅能为exec形式设置二进制程序(不能使用sh -c)。

注意:exec形式会被解析为JSON数组,这意味着,你必须使用双引号(”)包围单词,而不是使用单引号(’)。

注意:不像shell形式那样,exec形式不能调用一个shell命令,这意味着不能做正常的shell处理过程,例如,ENTRYPOINT [ "echo", "$HOME" ]$HOME上不会做变量替换。如果你想使用shell处理,要么使用shell的形式,要么直接执行一个shell,例如:ENTRYPOINT [ "sh", "-c", "echo $HOME" ]。当使用exec形式直接执行一个shell命令的时候,这和shell形式是一样的,它是一个正在做环境变量扩展的shell,而不是docker。

01.10.2 shell形式的ENTRYPOINTY示例

可以为ENTRYPOINT指令指定一个普通字符串,它将会在/bin/sh -c中被执行,这种形式使用shell进程替换shell的环境变量,并且也会忽略CMD或者docker run的命令行参数。为了确保docker stop能够正确地通知任何长期运行的ENTRYPOINT程序,你需要在启动的时候使用exec

FROM ubuntu
ENTRYPOINT exec top -b

当你运行这个镜像的时候,你将看到唯一的一个进程PID 1

$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

执行docker stop,进程会整齐地退出:

$ /usr/bin/time docker stop test
test
real    0m 0.20s
user    0m 0.02s
sys    0m 0.04s

如果你在ENTRYPOINT的前面忘记了添加exec

FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1

这时运行它(给它一个下一步中使用的名字):

$ docker run -it --name test top --ignored-param2
Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU:   9% usr   2% sys   0% nic  88% idle   0% io   0% irq   0% sirq
Load average: 0.01 0.02 0.05 2/101 7
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     S     3168   0%   0% /bin/sh -c top -b cmd cmd2
    7     1 root     R     3164   0%   0% top -b

你可以从top命令的输出中看到,ENTRYPOINT设定的进程号不是PID 1。这时如果你运行docker stop test,容器不会整齐地退出,超时之后stop命令会被强制发送一个SIGKILL

$ docker exec -it test ps aux
PID   USER     COMMAND
    1 root     /bin/sh -c top -b cmd cmd2
    7 root     top -b
    8 root     ps aux
$ /usr/bin/time docker stop test
test
real    0m 10.19s
user    0m 0.04s
sys    0m 0.03s

01.10.3 理解CMD怎么和ENTRYPOINTY交互

CMDENTRYPOINT指令定义的都是在容器启动的时候执行什么命令。有一些规则是描述它们之间的协作的。

  1. Dockerfile文件中至少要设置一个CMD 或者 ENTRYPOINT指令。
  2. 当容器被作为一个程序使用的时候,ENTRYPOINT指令应该被定义。
  3. CMD应该被用作为ENTRYPOINT命令定义默认参数的一种方式,或者是在容器中执行的临时命令。
  4. 当运行容器的时候使用替换参数的时候,CMD指令将会被覆盖。

下表展示了在不同的ENTRYPOINT / CMD组合时,执行什么命令:

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

01.11 VOLUME

VOLUME ["/data"]

VOLUME指令使用指定名字创建了一个挂载点,并将它标记为一个来自本地宿主机或者其它容器的外部挂载卷。这个值是一个JSON数组,VOLUME ["/var/log/"],或者是一个多参数的普通字符串,例如:VOLUME /var/log 或者 VOLUME /var/log /var/db。更多的信息和示例以及Docker客户端中的挂载指令,参见Share Directories via Volumes 文档。

docker run命令会新创建一个数据卷,其中包含在基础镜像中指定位置的所有数据,例如下面这个Dockerfile片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

这个Dockerfile文件的结果是,docker run启动的这个镜像会创建一个新的挂载点/myvol,并且会复制greeting文件到新创建的数据卷。

关于指定卷的注意事项

关于在Dockerfile中的volume,请记住以下事项:

  • 基于windows的容器的数据卷:当使用基于windows的容器时,容器中的数据卷的目标路径必须是下面的其中之一:
    1: 一个不存在的或者空的目录。
    2: 一个除 C: 之外的驱动。
  • Dockerfile内部改变数据卷:数据卷声明之后,如果任何编译步骤修改数据卷内部的数据,这些改变都是会被丢弃的。
  • JSON格式:这个列表时作为JSON数组解析的,你必须使用双引号(")包围单词,而不是单引号(')。
  • 主机目录在容器运行时声明:按照其特性,主机目录(挂载点)是依赖主机的,这是为了保持镜像的可移植性。因为给定的一个主机目录,并不能保证在所有主机上都是可用的。由于这个原因,你不能在Dockerfile中挂载一个主机目录。VOLUME指令不支持指定一个host-dir参数,你必须在容器创建或者运行的时候指定挂载点。

01.12 USER

USER <user>[:<group>] or
USER <UID>[:<GID>]

USER指令是用来指定用户名(或者UID),用户分组(或者GID,可选的),这个用户是在启动镜像的时候,Dockerfile中的RUN, CMDENTRYPOINT指令使用的。

警告:当一个用户确实没有基本的分组,镜像(下一条指令)将使用root分组来执行。

01.13 WORKDIR

WORKDIR /path/to/workdir

WORKDIR为Dockerfile中的其它指令(RUN, CMD, ENTRYPOINT, COPYADD)设置工作目录,如果WORKDIR指定的目录不存在,它将会被创建,即使在Dockerfile中的后续指令不使用它。

DockerfileWORKDIR指令可能被使用多次,如果提供的是一个相对路径,它是相对于前面的WORKDIR指令中的路径,例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

Dockerfile中,最后pwd命令的输出将是/a/b/c

WORKDIR指令可以解析前面使用ENV设置的环境变量,你仅可以使用在Dockerfile中显示设置的环境变量,例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

Dockerfile中,最后pwd命令的输出将是/path/$DIRNAME

01.14 ARG

ARG <name>[=<default value>]

ARG指令定义了一个变量,用户可以在docker build的编译期间使用--build-arg <varname>=<value>标识给编译器传递参数。如果用户指定了一个编译参数,但是在Dockerfile中没有定义,编译的时候会输出一个警告:

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

在一个Dockerfile中可以包含一个或多个ARG指令,例如下面就是有效的Dockerfile:

FROM busybox
ARG user1
ARG buildno
...

警告:不推荐在编译期间的命令行传递密码如github密钥,用户凭证等数据。编译期间设置的变量对镜像的任何用户都是可见的,可以通过docker history命令来查看。

01.14.01 默认值

ARG指令可以包含一个默认值(可选的):

FROM busybox
ARG user1=someuser
ARG buildno=1
...

如果一个ARG指令有一个默认值,并且在编译期间没有传值,编译器会使用这个默认值。

01.14.02 作用域

定义好的一个ARG变量是从Dockerfile中定义它的那一行开始生效的,而不是从使用它的命令行或者其他地方开始的。例如,下面这个Dockerfile:

1 FROM busybox
2 USER ${user:-some_user}
3 ARG user
4 USER $user
...

用户使用下面的命令编译这个文件:

$ docker build --build-arg user=what_user .

在第二行的USER的值是some_user,因为user变量是在下面的第三行定义的。在第四行的USER的值是what_user,也就是user变量定义的,what_user是由命令行传入的。在ARG指令定义变量之前,任何使用变量的结果都是空值。

ARG指令在被定义的编译阶段结束之后,就到了它的作用域之外,为了在多个阶段使用一个arg,需要在每个阶段都包含ARG指令。

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

01.14.03 ARG变量的用法

你可以使用ARG或者ENV指令定义变量,这些变量对RUN指令都是可用的。使用ENV指令定义的环境变量总是能覆盖ARG指令设定的同名的变量。考虑下面这个使用了ENVARG指令的Dockerfile:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER v1.0.0
4 RUN echo $CONT_IMG_VER

然后,使用下面的命令构建这个镜像:

$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

在这种情况下,RUN指令使用的是v1.0.0,而不是用户通过ARG指令设置的v2.0.1。这个行为和shell脚本很类似,一个当前作用域的变量会覆盖传递参数的变量,或者从环境和定义的变量中继承的。

上面用法的示例如下(但是有一个不一样的ENV,你可以创建更多的ARGENV指令之间有用的交互):

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
4 RUN echo $CONT_IMG_VER

ARG指令不一样的是,ENV的值是可以被持久化到构建的镜像中的。考虑,docker镜像编译的阶段没有使用--build-arg标识:

$ docker build .

这个Dockerfile中示例的用法,CONT_IMG_VER仍会被持久化到镜像中,但是它的值是v1.0.0,因为它是第三行中ENV指令设置的默认值。

在这个示例的变量拓展技术,允许你从命令行传递参数,然后通过改变ENV指令将它们持久化到镜像中。变量拓展仅支持a limited set of Dockerfile instructions.

01.14.04 预定义的ARG变量

Docker有一些预先定义好的ARG变量,在Dockerfile中,你可以在没有ARG指令定义的情况下使用它。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

使用它们,只需要简单在命令行使用标识传值即可:

--build-arg <varname>=<value>

默认情况下,这些预定义的变量是不会出现在docker history的输出中。排除它们可以减小变量HTTP_PROXY中敏感认证信息泄露的风险。

例如,考虑使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com编译下面的Dockerfile:

FROM ubuntu
RUN echo "Hello World"

在这个示例中,HTTP_PROXY变量的值在docker history中是不可见的,并且也不会被缓存。如果你改变位置和你的代理服务器为http://user:pass@proxy.sfo.example.com,后续的编译不会导致缓存的丢失。

如果你想覆盖这个行为,你可以像下面这样在Dockerfile中添加ARG指令的声明:

FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

当编译这个Dockerfile的时候,HTTP_PROXY变量的值是被保存在docker history中的,并且改变它的值,使编译缓存作废。

01.14.05 ARG对编译缓存的影响

ARG变量不能像ENV变量那样,能持久化到编译的镜像中,然而,ARG变量确实有类似的方式影响编译缓存。如果一个Dockerfile中定义了一个ARG变量,它的值和前一层的编译中不一样,然后就会遇到“缓存丢失”。尤其,所有带有ARG指令的RUN指令都隐式地使用一个ARG变量(作为一个环境变量),这样就可能导致缓存丢失。所有预定义的ARG变量都不会有缓存,除非在Dockerfile中有一个同名的ARG声明。

例如,考虑这两个Dockerfile:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo $CONT_IMG_VER
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo hello

如果你在命令行指定--build-arg CONT_IMG_VER=<value>,在这两种情形下,第二行都不会引起缓存丢失,第三行会引起缓存丢失,ARG CONT_IMG_VER会使RUN那一行同样被认为运行CONT_IMG_VER=<value>,然后echo hello,因此如果<value>被改变,我们将会看到缓存丢失。

考虑同一个命令行下的另外一个例子:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER $CONT_IMG_VER
4 RUN echo $CONT_IMG_VER

在这个例子中,缓存丢失出现在第三行。这个丢失的原因是,ENV引用了ARG变量的的值,并且变量通过命令行被改变了。在这个例子中,ENV命令使用镜像包含了这个值。

如果一个ENV指令覆盖了一个同名的ARG指令的值,像下面这个Dockerfile:

1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER hello
4 RUN echo $CONT_IMG_VER

第三行不会引起缓存丢失,因为CONT_IMG_VER的值是一个常量(hello)。因此,在编译期间环境变量和RUN指令使用的值都不会改变。

01.15 ONBUILD

ONBUILD [INSTRUCTION]

ONBUILD指令为镜像添加了一个触发器指令,它在稍后的时间执行,即当这个镜像被当作另一个编译的镜像的基础镜像时执行,这个触发器在下游编译的上下文中被执行,在下游的Dockerfile中的FROM指令之后立即被执行,就像插入一个触发器一样。

任何编译指令都可以都可能被注册为一个触发器。

如果你正在编译一个其它镜像的基础镜像,这个是有用的,例如一个应用的编译环境,或者一个可以通过用户配置自定义的后台进程。

例如,如果你的镜像是一个可重复使用的Python程序的编译器,它需要将应用的源代码添加到指定的目录,并且它可能也需要一个稍后被调用的编译脚本。你不可能现在就调用ADDRUN,因为你不可能访问到应用的源代码,并且每个应用的编译都是不一样的。你可能会简单的提供一个模板化的Dockerfile给应用开发者,复制-粘贴到他们的应用中,但是这是低效的、易错的和难以更新的,因为它是和特定的代码混在一起的。

这个解决办法就是使用ONBUILD提前注册一个指令,稍后再执行,也就是下一个编译阶段。

这儿是它的工作原理:

  1. 当编译器遇到一个ONBUILD指令的时候,它会添加一个触发器到正在编译的镜像的元数据中,这个指令不会影响当前的镜像编译过程。
  2. 编译结束的时候,所有触发器的列表时存储在镜像的manifest中的,key值是OnBuild。它们可以使用docker inspect命令查看到。
  3. 稍后这个镜像可能被用作一个新的编译过程的基础镜像,会使用FROM指令引用。作为FROM指令执行过程的一部分,下游编译器会查找ONBUILD触发器,然后会按照它们被注册的顺序依次执行。如果任何触发器执行失败,FROM指令会中止,反过来这就导致镜像编译失败。如果所有的触发器成功执行,FROM指令完成之后,编译过程照常继续。
  4. 在最后的镜像中执行过的触发器会被清除,换句话说,它们不会在“父-子”的编译过程中被继承。

例如,你可能像这样添加一些:

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

警告:如ONBUILD ONBUILD,这种ONBUILD指令的链式使用是不允许的。

警告:ONBUILD指令不能设置FROM 或者 MAINTAINER指令为触发器。

01.16 STOPSIGNAL

STOPSIGNAL signal

STOPSIGNAL指令是给系统发送退出信号(终止信号),该信号可以是与内核系统调用 “9”相似,或者是格式为SIGNAME的信号名称,例如SIGKILL。

01.17 HEALTHCHECK

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command(通过运行容器中的一个命令来检查容器的健康状态)
  • HEALTHCHECK NONE(禁用继承自基础镜像的任何健康检查)

HEALTHCHECK指令告诉Docker是如何测试一个容器,检查它是否是一致在工作。举一个web服务器探测的例子,即使服务器的进程一直是在运行的,但是它可能困在了无限的循环中,而不能接收新的连接。

当一个容器指定了一个健康检查的话,它除了正常状态之外多了一个health状态。这个状态初始化为starting,无论什么时候一个健康检查通过了之后,它会变成healthy(无论之前是什么状态)。经过一定数量的连续故障之后,它会变成unhealthy

CMD指令之前可以出现的选项:

  • --interval=DURATION (默认: 30s)
  • --timeout=DURATION (默认: 30s)
  • --start-period=DURATION (默认: 0s)
  • --retries=N (默认: 3)

第一次执行健康检查是在容器启动之后的一个interval秒,然后在前一次检查完成之后的interval秒再次执行。

如果有一个检查所花的时间超过了timeout秒,那么就认为这次检查失败了。

如果连续retries次失败,就认为此容器状态为unhealthy

start-period为需要时间引导的容器提供初始化的时间,探测故障期间的不被计入最大重试次数。然而,在start-period期间,如果一个健康检查成功,容器就被认为是启动成功的,并且之后所有的连续失败都会被计入最大重试次数。

Dockerfile中仅可以有一个HEALTHCHECK指令,如果你列出了多个,仅有最后一个HEALTHCHECK指令是有效的。

CMD关键字之后的命令可以是一个shell命令(例如HEALTHCHECK CMD /bin/check-running),也可以是exec数组(像Dockerfile中其它的指令,详细的可参见ENTRYPOINT)。

命令的退出状态表示着容器的健康状态,可能的值:

  • 0: success - 容器是健康的,可用的
  • 1: unhealthy - 容器不能正确工作的
  • 2: reserved - 不要使用这个退出码

例如,每五分钟检查一次容器,确保web server能够在3秒内正常输出主页:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

为了方便调试探针失败原因,检查命令的标准输出或者标准错误输出(UTF-8编码)会写到health状态中,并且可以通过docker inspect查看。输出应该尽量短(目前仅能存储前4096字节)。

当容器的健康状态变化的时候,health_status会生成一个新的状态。

HEALTHCHECK功能是在Docker 1.12添加的。

01.18 SHELL

SHELL ["executable", "parameters"]

SHELL指令可以覆盖命令的shell模式所使用的默认shell。Linux的默认shell是["/bin/sh", "-c"],Windows的是["cmd", "/S", "/C"]。在Dockerfile中SHELL指令必须以JSON格式编写。

SHELL指令在windows上特别有用,因为它有两个常用的且不太相同的本地shell:cmdpowershell,以及可选的sh的。

SHELL指令可以出现多次,每个SHELL指令都会覆盖之前的SHELL指令设置的shell,并影响后续的指令。例如:

FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello

下面的指令都会被SHELL指令设置的shell影响,当在Dockerfile中这些指令RUN, CMDENTRYPOINT使用shell形式时,将使用SHELL指令设置的shell执行。

以下的示例是windows常见的模式,可以使用SHELL指令精简:

...
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
...

docker解析的命令将是:

cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

这个命令有两个低效的原因。首先,没有必要调用cmd.exe进程(aka shell)。第二,每个使用shell形式的RUN指令都需要一个额外的powershell -command放在命令前面。

为了使它更加高效,有两个方法可以使用,其中之一是使用RUN命令的JSON形式,例如:

...
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
...

虽然JSON形式是清晰的,未使用没有必要的cmd.exe,但是它需要更加冗长的表示,需要使用双引号和转义符。另一个方法就是使用SHELL指令和shell形式,使得windows用户可以使用更自然的语法,特别是与escape指令一起用时:

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

编译结果:

PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/28/2016  11:26 AM                Example


 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>

SHELL指令也可能使用在修改shell操作的方式,例如,在windows上使用SHELL cmd /S /C /V:ON|OFF,延迟环境变量的扩展语义可以被修改。

SHELL指令也可以在Linux上使用,有时候需要交替使用一些shell(例如zsh, csh, tcsh和其它的)。

SHELL功能是在Docker 1.12中添加的。

01.19 Dockerfile示例

下面你可以看到一些Dockerfile语法的示例,如果你对一些更加实用的东西感兴趣,可以查看Dockerization examples

# Nginx
#
# VERSION               0.0.1

FROM      ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
# Firefox over VNC
#
# VERSION               0.3

FROM ubuntu

# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'

EXPOSE 5900
CMD    ["x11vnc", "-forever", "-usepw", "-create"]
# Multiple images example
#
# VERSION               0.1

FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f

FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4

# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.

02 结束语

看完Dockerfile的参考手册,并将其翻译完,经历了一个完美提升的过程。从开始对docker仅仅是了解,到能够简单的构建docker镜像。要想能够对docker有更加深入的理解,好需要更多的实践过程,同时还需要了解docker周边的系统功能。

上一篇