Docker 006 Dockerfile 指令

前面我们在构建镜像的时候已经使用了一些Dockerfile 中的指令,比如 FROM、RUN、EXPOSE,其实还有很多其他的指令可供我们使用。

 

FROM

一个有效的 Dockerfile 文件必须以为 FROM 指令开始,他的作用是指定一个新构建镜像的初始镜像,后续其他指令都基于 FROM 指定的镜像。

# 三种格式
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

--platform可以用于指定镜像的来源的平台,以防FROM指令引用了一个多平台的镜像,比如linux/amd64, linux/arm64, or windows/amd64等平台,值还可以使用全局参数

tagdigest值也是可选的,如果不使用的话builder会自动为我们的新镜像分配一个latest作为默认tag.

可选项AS name可以为新构建的镜像起一个名字,方便记忆,name还可以被用于接下来的FROMCOPY --from=指令引用.

ARG 指令是唯一一个可能出现在 FROM 之前的指令,

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

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

#######################
#
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

Dockerfile 中的FROM指令可以出现多次,出现的每一条 FROM 指定都是一个构建阶段,生成的镜像只能是最后一个阶段的结果,但能将前置阶段的内容拷贝到后边的阶段中。

 

RUN

RUN指令有两种形式:

RUN <command> # shell形式,命令会在shell中执行,Linux 下默认是/bin/sh -c, win下默认是cmd /S /C
RUN ["executable", "param1", "param2"]  # exec 形式

RUN指令会在当前镜像层的顶层执行命令,并提交结果,即产生新的一层,新产生的层可以用与 dockerfile 的下一步中。

exec 形式可以避免破坏 shell 字符串,并使用不包含指定 shell 的基本镜像。

shell 形式的默认 shell 可以通过 shell 命令进行修改,比如不想使用/bin/sh,想使用/bin/bash,可通过如下形式:

# 当一行内容太长时,可使用反斜线 进行分割, 依旧认为是一条指令
RUN /bin/bash -c 'source $HOME/.bashrc; \ 
echo $HOME

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

或者
RUN ["/bin/bash", "-c", "echo hello"]  # 推荐使用此形式

注意:

  • exec 形式会被解析为 json 数组,也就是说字符串周围必须是双引号。
  • exec 形式不会调用 shell,意思是说,有些 shell 处理不会发生,例如RUN [ "echo", "$HOME" ]$HOME不会发生变量替换,如果你想要 shell 来做这些处理,可以直接使用 shell 形式或者指定 shell 来执行,例如RUN echo $HOME 或者 RUN [ "sh", "-c", "echo $HOME" ]。 在exec 形式中指定 shell 的时候,变量的解析由 shell 来执行,而不是 docker。

多次构建时,RUN 指令的 cache 不会自动失效,例如RUN apt-get dist-upgrade -y的缓存会被再次使用,可以使用--no-cache参数让缓存无效,例如:docker build --no-cache

CMD

CMD指令用于指定一个容器启动时要运行的命令。类似于 RUN 指令,区别是 RUN 指令是指定镜像被构建时要运行的命令,CMD 是指定容器被启动时要运行的命令。和使用 docker run 命令启动容器时,要运行的命令非常相似,例如:

$ docker run -it web01 /bin/bash
# 等价于 Dockerfile 中的
CMD ["/bin/bash"]

# CMD 指令后的命令还可以加参数
CMD ["/bin/bash", "-l"]

CMD 支持的种格式

CMD ["executable","param1","param2"] # 使用 exec 执行,推荐方式;
CMD command param1 param2 # 在 /bin/sh 中执行,提供给需要交互的应用,可能会导致意外

需要注意的是 docker run 命令可以覆盖 CMD指令,如果在 Dockerfile 中指定了 CMD 指令,同时在 docker run 命令中也指定了运行的命令,那么CMD 指令中的命令会被覆盖。

还有就是 在 Dockerfile 中只鞥你指定一条 CMD指令,如果指定了多条 CMD指令,那么只有最后一条 CMD指令会被使用,

ENTRYPOINT

ENTRYPOINT 指令和 CMD 指令非常相似,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 --entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

ENTRYPOINT ["/usr/sbin/nginx"]
# 和 CMD 指令一样,可通过数组的方式为命令添加响应的参数
ENTRYPOINT ["/usr/sbin/nginx", "-t"]

ENTRYPOINT 指令还可以和 CMD 指令搭配使用

# 假设 Dockerfile 中有如下内容
ENTRYPOINT ["nginx", "-c"] 
CMD ["/etc/nginx/nginx.conf"] 

# 默认执行,不传递参数
$ docker run nginx:test # 这个相当于直接启动一个 nginx 容器
#此时容器中运行的命令是
$ nginx -c /etc/nginx/nginx.conf


# 指定参数运行,会传递参数
$ docker run nginx:test -c  /etc/nginx/new.conf
#此时容器中运行的命令是
$ nginx -c /etc/nginx/new.conf

 

WORKDIR

WORKDIR指令的作用是 在从镜像创建一个新容器时,为 RUN,CMD,ENTRPOINT,COPY和ADD指令指定工作目录。可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

# pwd命令的输出/a/b/c

docker run 命令中的 -w 参数可以覆盖 WORKDIR 中的工作目录

$ docker run -it -w /var/log ubuntu pwd
# 此时容器内的工作目录是 /var/log

 

ENV

ENV指令用来在镜像的构建过程中设置环境变量。

# key为变量名, value 为变量的值
ENV key value   # 设置一个变量
ENV key1=value1 key2=value2  # 设置多个变量

通过 ENV指令设置的变量可以在后续其他指令中继续使用。

 

USER

USER 指令用来指定镜像会以哪个用户来运行,

USER nginx

基于该镜像启动的容器会以nginx 用户的身份来运行,还可以实行用户名或UID以及组或者 GID,或者两者的组合

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

还可以在 docker run 命令中以-u参数来覆盖改指令的值

 

VOLUME

VOLUME指令用来向基于镜像创建的容器添加卷,一个卷可以是一个或多个容器内特定的目录,该目录可以绕过联合文件系统,并提供以下功能:

  • 卷可以在容器建共享和重用
  • 一个容器不是必须和其他容器共享卷
  • 对卷的修改是立即生效的
  • 对卷的修改不会对更新镜像产生影响
  • 卷会一直存在,直到没有任何容器再使用它

使用卷功能,可以把数据或某些内容添加到镜像而不是提交到镜像上,还可以在多个容器间共享这些内容。

# 所有基于此进行启动的容器,都会创建挂载点:/opt/web
VOLUME ["/opt/web"]
VOLUME ["路径1", "路径2", ...]

卷服务有有个作用是:

  • 可避免容器不断变大
  • 避免重要数据,因 容器重启而丢失

ADD

ADD 指令用来将构建环境下的文件和目录复制到镜像中,ADD指令需要指定源文件位置和目标文件位置两个参数。

ADD src dst

# 将构建目下下的 sofaware.lic 文件拷贝到 /opt/web/software.lic
ADD sofaware.lic  /opt/web/software.lic
ADD http://re2do.com/test.zip /opt/web/test.zip

# 如果源是归档文件,docker 会将其解开,docker在这里的行为和 tar -x 一样
# 该指令执行后的输入原目标目录+归档文件中的内容,如果目标目录下存在与源文件同名的目录或文件,那么目标目录下的文件或目录不会被覆盖
# 缺点也很明显,不解压无法复制归档文件
ADD test.tar.gz /opt/web/test/

源文件可以是一个 URL、或者构建上下文或环境中的文件或者目录,且不能是构建目录或者上下文之外的文件。Docker 通过目的地址的字符判断文件源是目录还是文件,如果以”/“结尾,那么 Docker 就任务源是目录,如果不以”/“结尾,就认为源是文件。

COPY

COPY 指令类似于 ADD 指令,区别是,COPY 只从上下文目录中复制文件或目录到指定路径。

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 路径中有空格的需要使用此形式
# [--chown=<user>:<group>]:可选参数,仅适用于构建linux容器,用户改变复制到容器内文件的拥有者和属组,不指定时属主属组都为 0

把src 中的文件或目录复制到容器中的 dst 路径中,src 中可能包含通配符:

COPY hom* /mydir/        # 复制所有以 home 开头的文件
COPY hom?.txt /mydir/    # 问号? 表示任意的单字符

目标路径是绝对路径或者 WORKDIR的相对路径

COPY test relativeDir/   # 把 test复制到 `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # 把 test复制到 /absoluteDir/

在复制的文件或目录的路径有有特殊字符时,需要按照 golang 的规则将特殊字符转义,例如复制名为arr [0] .txt的文件:

COPY arr[[]0].txt /mydir/    # 复制文件"arr[0].txt" 到 /mydir/

LABEL

LABEL指令用于以键值对的形式为镜像添加元数据,如果值中包含空格,请使用引号和反斜杠,示例:

# 用法
LABEL <key>=<value> <key>=<value> <key>=<value> ...

# 示例
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

LABEL multi.label1="value1" multi.label2="value2" other="value3"

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

# 使用 docker inspect 命令查看镜像中的 label 信息、
$ docker inspect image_name
"Labels": {
    "com.example.vendor": "ACME Incorporated"
    "com.example.label-with-value": "foo",
    "version": "1.0",
    "description": "This text illustrates that label-values can span multiple lines.",
    "multi.label1": "value1",
    "multi.label2": "value2",
    "other": "value3"
},

 

ARG

ARG指令用于定义一个变量,在docker build命令使用--build-arg =参数时,可以将使用时传递给运行时的变量,

 

一个变量,在构建时,用户可以传给给构建者,当 docker build 使用 --build-arg =参数时,可以将值传递给该变量。

# Dockerfile 文件部分内容
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER

# 要执行的 docker build 命令
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

# 在这里例子中,ARG 指令中的CONT_IMG_VER变量会被替换为 v2.0.1 ,
# 然后 ENV 指令做了变量替换,如果CONT_IMG_VER未被设置,则会使用v1.0.0

 

ONBUILD

当镜像是其他镜像的基础镜像时,使用ONBUILD指令可以添加一条触发指令,以便稍后执行;触发指令会在下游的构建上下文中执行,就好像它已经被插入到下游 Dockfile 的 FROM指令 之后一样。触发指令可以是任何构建指令。

# 示例:可能添加了 如下内容
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

注意:

  • 不允许出现 ONBUILD ONBUILD 这种形式的 ONBUILD 链
  • ONBUILD指令可能不会触发 FROM 或者MAINTAINER(废弃) 指令

STOPSIGNAL

STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器,且这个信号必须是内核系统调用表中合法的数字(例如 9)或者 SIGNAME 格式中的信号名称(如 SIGKILL)。

# 格式
STOPSIGNAL signal

 

HEALTHCHECK

HEALTHCHECK指令有两种形式 :

# 第一种形式:通过在容器内部运行一个命令来检查容容器的健康状态
HEALTHCHECK [OPTIONS] CMD command

# 第二种形式:禁用从基本映像继承的任何健康状态检查
HEALTHCHECK NONE

在 CMD 之前可以使用的选项如下:

  • --interval=DURATION (default: 30s): ,从容器运行起来开始计时,interval秒进行第一次健康检查,随后每间隔interval秒进行一次健康检查
  • --timeout=DURATION (default: 30s):执行command需要时间,比如curl 一个地址,如果超过timeout秒则认为超时是错误的状态,此时每次健康检查的时间是timeout+interval秒。
  • --start-period=DURATION (default: 0s): 启动时间, 默认 0s, 如果指定这个参数, 则必须大于 0s 。为需要启动的容器提供了初始化的时间段, 在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。
  • --retries=N (default: 3): 连续检查retries次,如果结果都是失败状态,则认为这个容器是unhealth的

 

Dockerfile 中只会有一条HEALTHCHECK指令有效,如果出现多次HEALTHCHECK,那么只有最后一次的会生效。

关键字 CMD之后的命令和 ENTRYPOINT一样,可以是一个 shell 格式(HEALTHCHECK CMD /bin/check-running)或者exec 格式。

命令的返回值可以表明容器的健康状态:

  • 0 :成功,容器是健康的,可以使用
  • 1 : 失败,容器没有正确运行
  • 2 :保留,不要使用这个返回值

例如,每 5 分钟请求一次网站首页,如果 3 秒内无响应就认为失败:

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

为了便于调试,该命令在stderr或stdout 的任何输出(UTF-8编码)都会被存储在健康状态中,可使用 docker inspect 命令查询到。此类输出应该简短(当前值保存钱 4096 个字节)。

当容器的健康状态发生变化时,将以新状态生成一个health_status事件。

SHELL

SHELL 指令允许覆盖默认的shell 形式命令的默认 shell,在 Linux 中,默认的 shell 是["/bin/sh", "-c"],Win 上的是["cmd", "/S", "/C"]

SHELL 指令必须是 JSON 格式:

# SHELL 指令格式:
SHELL ["executable", "parameters"]

SHELL 指令在 win 上特别有用;Win 上有两个不同的shell:cmd 和 powershell,以及别用 shell:sh。

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

当在 Dockerfile 中使用RUN, CMDENTRYPOINT指令的 shell 形式,他们可能会受到 SHELL 指令的影响。

下面的例子是Win 上常见的形式,可以使用 SHELL 指令进行精简:

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

# docker 调用的命令将会如下:
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

这样做会效率低下,有两个原因。

  • 第一:有一个不必要的 cmd.exe 被调用
  • 第二:使用 shell 格式的 RUN 指令都需要额外的命令前缀:powershell -command

为了变的更高效,可以采用两种机制之一。

第一种:使用JSON 形式的 RUN 指令:

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

第二种:使用 SHELL 指令的 shell 形式。

# 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 的运行方式,例如在 win 上使用SHELL cmd /S /C /V:ON|OFF.

如果需要备用shell(例如 zsh、csh、tcsh 等),也可以使用 SHELL 指令。