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"]
如果你需要为这个独立的程序写一个启动脚本,你可以确保最后的程序能依靠exec
和gosu
指令接收到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交互
CMD
和ENTRYPOINT
指令定义的都是在容器启动的时候执行什么命令。有一些规则是描述它们之间的协作的。
- Dockerfile文件中至少要设置一个
CMD
或者ENTRYPOINT
指令。 - 当容器被作为一个程序使用的时候,
ENTRYPOINT
指令应该被定义。 CMD
应该被用作为ENTRYPOINT
命令定义默认参数的一种方式,或者是在容器中执行的临时命令。- 当运行容器的时候使用替换参数的时候,
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
, CMD
和 ENTRYPOINT
指令使用的。
警告:当一个用户确实没有基本的分组,镜像(下一条指令)将使用
root
分组来执行。
01.13 WORKDIR
WORKDIR /path/to/workdir
WORKDIR
为Dockerfile中的其它指令(RUN
, CMD
, ENTRYPOINT
, COPY
和 ADD
)设置工作目录,如果WORKDIR
指定的目录不存在,它将会被创建,即使在Dockerfile
中的后续指令不使用它。
在Dockerfile
中WORKDIR
指令可能被使用多次,如果提供的是一个相对路径,它是相对于前面的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
指令设定的同名的变量。考虑下面这个使用了ENV
和ARG
指令的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
,你可以创建更多的ARG
和ENV
指令之间有用的交互):
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程序的编译器,它需要将应用的源代码添加到指定的目录,并且它可能也需要一个稍后被调用的编译脚本。你不可能现在就调用ADD
和RUN
,因为你不可能访问到应用的源代码,并且每个应用的编译都是不一样的。你可能会简单的提供一个模板化的Dockerfile
给应用开发者,复制-粘贴到他们的应用中,但是这是低效的、易错的和难以更新的,因为它是和特定的代码混在一起的。
这个解决办法就是使用ONBUILD
提前注册一个指令,稍后再执行,也就是下一个编译阶段。
这儿是它的工作原理:
- 当编译器遇到一个
ONBUILD
指令的时候,它会添加一个触发器到正在编译的镜像的元数据中,这个指令不会影响当前的镜像编译过程。 - 编译结束的时候,所有触发器的列表时存储在镜像的manifest中的,key值是
OnBuild
。它们可以使用docker inspect
命令查看到。 - 稍后这个镜像可能被用作一个新的编译过程的基础镜像,会使用
FROM
指令引用。作为FROM
指令执行过程的一部分,下游编译器会查找ONBUILD
触发器,然后会按照它们被注册的顺序依次执行。如果任何触发器执行失败,FROM
指令会中止,反过来这就导致镜像编译失败。如果所有的触发器成功执行,FROM
指令完成之后,编译过程照常继续。 - 在最后的镜像中执行过的触发器会被清除,换句话说,它们不会在“父-子”的编译过程中被继承。
例如,你可能像这样添加一些:
[...]
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:cmd
和powershell
,以及可选的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
, CMD
和ENTRYPOINT
使用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周边的系统功能。