Dockerfile 参考手册(一)

作者: rabbitGYK | 来源:发表于2017-10-16 00:30 被阅读226次

    博客原文

    最近学习docker过程中,发现Dockerfile是一个非常重要的文档,本文系统学习一下。
    文档是基于Docker v17.09 版本。
    翻译作品,原文请见官网英文文档

    00 前言

    Docker可以读取Dockerfile中的指令自动构建镜像,Dockerfile是一个文本文件,它包含很多命令,用户可以在命令行上调用这些命令组装镜像。用户可以使用docker build来自动构建镜像,它可以连续执行若干命令行指令。

    本文将介绍在Dockerfile中你可以使用命令,你读完这篇文章之后,Dockerfile
    Best Practices
    是另一篇很好的指导。

    01 用法

    docker build命令根据Dockerfile和上下文来构建镜像,构建过程的上下文是通过PATH或者URL指定的一系列文件。PATH是一个本地文件系统的目录,URL是一个Git仓库的位置。

    上下文是一个递归的处理过程。因此,PATH可以包含任何的子目录,URL`包括仓库和它的子模块。下面是一个构建镜像的命令的示例,使用当前目录作为上下文:

    $ docker build .
    Sending build context to Docker daemon  6.51 MB
    ...
    

    Build是通过Docker daemon(docker 守护进程),而不是 CLI(命令行界面)执行的。Build过程要做的第一件事是发送整个上下文(递归)到Docker的守护进程。最佳实践是,开始创建一个空的文件夹作为上下文,然后将你的Dockerfile文件放在那个文件夹下,仅添加一些你在编译Dockerfile过程中需要的文件。

    注意:千万不要使用根路径 / 作为PATH,这将导致Build会发送你的硬盘上的所有内容到Docker的守护进程。

    在Build的上下文中为了使用Dockerfile中指定的一个文件,这个文件是某个指令(例如COPY指令)用到的。为了提高Build的性能,通过添加.dockerignore文件,可以排除上下文目录中的某些文件和目录,关于如何创建.dockerignore文件更多信息见本文的下面章节。

    一般认为,Dockerfile文件都应该位于上下文的根目录下,你可以在docker build后使用-f标识来指定你的文件系统中任意位置的Dockerfile文件。

    $ docker build -f /path/to/a/Dockerfile .
    

    你还可以指定用来存储成功编译的镜像文件的仓库和标签:

    $ docker build -t shykes/myapp .
    

    Build的时候也可以为镜像添加多个仓库标签,在你执行Build命令的时候添加多个-t参数即可:

    $ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
    

    Docker守护进程在执行Dockerfile中的指令之前,会首先对Dockerfile做一个初步校验,如果有语法错误,它会返回一个错误:

    $ docker build -t test/myapp .
    Sending build context to Docker daemon 2.048 kB
    Error response from daemon: Unknown instruction: RUNCMD
    

    Docker守护进程是逐步执行Dockerfile中的指令的,如果需要的话,会提交每个指令的结果到新的镜像中,最后输出新镜像的的ID。Docker的守护进程也会自动清除你发送的上下文。

    注意,每一条指令都是独立执行的,因此在创建一个镜像的时候,RUN cd /tmp这条指令不会对下一条指令有任何影响。

    无论任何可能的时候,Docker都将会重用中间状态(缓存)的镜像,这样能够明显地加速docker build的过程,这是通过控制台输出的信息Using cache来标识的。(更多信息参见,在Dockerfile的最佳实践指导中的Build cache section):

    $ docker build -t svendowideit/ambassador .
    Sending build context to Docker daemon 15.36 kB
    Step 1/4 : FROM alpine:3.2
     ---> 31f630c65071
    Step 2/4 : MAINTAINER SvenDowideit@home.org.au
     ---> Using cache
     ---> 2a1c91448f5f
    Step 3/4 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
     ---> Using cache
     ---> 21ed6e7fbb73
    Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
     ---> Using cache
     ---> 7ea8aef582cc
    Successfully built 7ea8aef582cc
    

    仅在编译那些有具有本地主链的镜像时使用缓存,意思是这些镜像的创建依赖前面的Build,或者整个镜像链都已经通过docker load加载进来了。如果你希望对一个指定镜像使用build cache,你可以使用--cache-from来指定,通过--cache-from指定的镜像不需要有一个主链,也可能是从其他的中心拉取的。

    当你编译完成的时候,你该学习 Pushing a repository to its registry

    02 格式

    下面是Dockerfile文件的格式:

    # Comment
    INSTRUCTION arguments
    

    指令对字母大小写是不敏感的,但是,习惯上将它们大写,以便容易和参数区分开。

    Docker是按照顺序来执行Dockerfile中的指令的。一个Dockerfile文件必须以FROM指令开始,FROM指令指定了你正在编译镜像的基础镜像。在Dockerfile文件中,FROM指令的前面仅可以是一个或者多个ARG指令,这些声明的参数被用于FROM指令。

    Docker认为以#开头的行是注释,除非这一行是一个有效的转义的指令。#标识出现在一行的任何其它地方,都会被认为是一个参数。就像下面这段:

    # Comment
    RUN echo 'we are running some # of cool things'
    

    注释中不支持继续字符。

    03 转义指令

    转义指令是可选的,它会影响在Dockerfile中后续行的处理方式。转义指令并不会添加任何层到构建的镜像中,也不会作为构建一个步骤展示,转义指令是被写作一个特殊类型的注释,形式为# directive=value,一个指令可能只会被使用一次。

    一旦有一行注释、空行或者编译指令被执行,Docker就不会再检查转义指令了,而是将任何格式的转义指令认为是注释,不会尝试去验证它是否是转义指令。因此所有的转义指令必须放在Dockerfile文件的第一行。

    转义指令不是大小写敏感的,但是通常使用小写的形式,习惯上任何的转义指令后面都跟一个空行。转义指令不支持续行符。

    根据上面这些规则,下面是一些无效的转义指令的例子:

    由于续行符,导致无效:

    # direc \
    tive=value
    

    由于出现两次,导致无效:

    # directive=value1
    # directive=value2
    
    FROM ImageName
    

    由于出现在了编译指令之后,被当作了注释:

    FROM ImageName
    # directive=value
    

    由于出现在了注释之后,被当作了注释,而不是转义指令:

    # About my dockerfile
    # directive=value
    FROM ImageName
    

    未知的指令由于无法识别被当作了注释,另外一个已知的指令由于出现在了注释的后面,被当作了注释而不是转义指令。

    # unknowndirective=value
    # knowndirective=value
    

    转义指令中允许出现非断行的空格,所以下面几行都是相同的:

    #directive=value
    # directive =value
    #   directive= value
    # directive = value
    #     dIrEcTiVe=value
    

    下面的转义指令是支持的:
    escape

    04 转义符指令

    # escape=\ (backslash)
    

    或者

    # escape=` (backtick)
    

    escape指令是用来设置Dockerfile中转义字符的字符,如果不指定的话,默认的转义字符是\

    转义字符不仅用在一行中的转义字符上,也用在开启一个新行。Dockerfile中指令允许是多行的。注意,无论在Dockerfile中是否包含escape转义指令,在RUN命令中是不会执行转义的,除非是在一行的末尾。

    在Windows环境下,设置转义字符为 ` ,是非常有用的,由于\是目录路径的分隔符,`和windows下的转义字符是一致的。

    考虑下面的一个例子,在windows环境下是失败的,在第二行的第二个\被解释成了换行的转义符,而不是被第一个\转义了的目标,同样的,在第三行末尾的\也是,它们被认作是一个指令,\被认为是续行符。这个Dockerfile的结果就是第二行和第三行被认为是一行指令:

    FROM microsoft/nanoserver
    COPY testfile.txt c:\\
    RUN dir c:\
    

    结果是:

    PS C:\John> docker build -t cmd .
    Sending build context to Docker daemon 3.072 kB
    Step 1/2 : FROM microsoft/nanoserver
     ---> 22738ff49c6d
    Step 2/2 : COPY testfile.txt c:\RUN dir c:
    GetFileAttributesEx c:RUN: The system cannot find the file specified.
    PS C:\John>
    

    一个解决办法是,上面都使用/作为COPY指令和dir的目标。然而,最好的情况下,这只是看着windows下的路径不自然,最坏的情况下,并不是所有的windows命令都支持/作为路径分隔符。

    另一种解决办法,添加一个escape转义指令,下面的Dockerfile成功的执行,如预期的一样windows平台很自然路径表示语义:

    # escape=`
    
    FROM microsoft/nanoserver
    COPY testfile.txt c:\
    RUN dir c:\
    

    结果是:

    PS C:\John> docker build -t succeeds --no-cache=true .
    Sending build context to Docker daemon 3.072 kB
    Step 1/3 : FROM microsoft/nanoserver
     ---> 22738ff49c6d
    Step 2/3 : COPY testfile.txt c:\
     ---> 96655de338de
    Removing intermediate container 4db9acbb1682
    Step 3/3 : RUN dir c:\
     ---> Running in a2c157f842f5
     Volume in drive C has no label.
     Volume Serial Number is 7E6D-E0F7
    
     Directory of c:\
    
    10/05/2016  05:04 PM             1,894 License.txt
    10/05/2016  02:22 PM    <DIR>          Program Files
    10/05/2016  02:14 PM    <DIR>          Program Files (x86)
    10/28/2016  11:18 AM                62 testfile.txt
    10/28/2016  11:20 AM    <DIR>          Users
    10/28/2016  11:20 AM    <DIR>          Windows
               2 File(s)          1,956 bytes
               4 Dir(s)  21,259,096,064 bytes free
     ---> 01c7f3bef04f
    Removing intermediate container a2c157f842f5
    Successfully built 01c7f3bef04f
    PS C:\John>
    

    05 环境变量占位符

    环境变量(ENV声明)可以被用在某些指令中作为变量(可以被Dockerfile解释)。转义指令也可以用于处理语句中包含类似变量的语法。

    环境变量在Dockerfile中表示为$variable_name 或者 ${variable_name},他们是等效的,大括号的语法通常用来强调没有空格的变量名,例如${foo}_bar${variable_name}语法也支持一些标准的bash修饰符,例如下面:

    • ${variable:-word}意思是,如果variable被设置了,结果将是那个值,如果variable没被设置,那个word就是结果。
    • ${variable:+word}意思是,如果variable被设置了,word就是结果,否则结果就是空。

    以上所有情形,word可以是任何字符串,包括其它的环境变量。

    转义可以在变量之前添加\:例如,\$foo或者\${foo}将被转义为$foo${foo}两个常量。

    举个例子(转义之后的结果展示在#的后面):

    FROM busybox
    ENV foo /bar
    WORKDIR ${foo}   # WORKDIR /bar
    ADD . $foo       # ADD . /bar
    COPY \$foo /quux # COPY $foo /quux
    

    环境变量在下面这些Dockerfile指令中都是支持的:

    • ADD
    • COPY
    • ENV
    • EXPOSE
    • FROM
    • LABEL
    • STOPSIGNAL
    • USER
    • VOLUME
    • WORKDIR

    此外还有:

    • ONBUILD(当与上面任何一个指令结合时)

    注意:在1.4版本之前,ONBUILD是不支持环境变量的,即使与上面列出的指令结合时。

    在整个指令中环境变量的替换值都是用同一个值,换句话说,就是下面的例子:

    ENV abc=hello
    ENV abc=bye def=$abc
    ENV ghi=$abc
    

    结果是,def的值是hello,而不是byeghi的值是bye,因为它不是设置abcbye的指令的一部分。

    06 .dockerignore文件

    在docker命令行界面中发送上下文到docker的守护进程之前,它会检查上下文目录根路径下名为.dockerignore的文件,如果这个文件存在,命令行界面会修改上下文,排除那些被.dockerignore中的模式匹配到的文件和目录。这有助于避免一些不必要的(大的或者敏感的文件和目录)发送到守护进程,还能避免一些潜在的使用ADD 或者 COPY添加文件和目录到镜像中。

    命令行解释.dockerignore文件为一个换行符分割的模式列表,类似于Unix shell的glob文件。由于这个匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar都是在排除目录foo下面一个叫bar的文件或者目录,目录fooPATH的子目录或者URL指定的git仓库下的子目录。不排除任何其它的。

    如果在.dockerignore文件中有一行以#开头,那么这一行被认为是注释,命令行解释之前为忽略它。

    下面是一个.dockerignore文件的例子:

    # comment
    */temp*
    */*/temp*
    temp?
    

    这个文件将引发下面的构建行为:

    规则 行为
    # comment 忽略。
    */temp* 排除根目录下的子目录中任何以temp开头的文件和目录,例如,/somedir/temporary.txt 这个文本文件会被排除,/somedir/temp这个目录也会被排除。
    */*/temp* 排除来自子目录的任何以temp开头的文件和目录,这个子目录是根目录下两层,例如,/somedir/subdir/temporary.txt被排除的。
    temp? 排除那些根目录下名字以temp开始拓展一个字符的文件和目录,例如,/tempa/tempb是被排除的。

    完成这个匹配使用的是Go语言的文件路径匹配规则,在预处理步骤中会去除掉开头和结尾的空格,并清除...元素,在这个过程中使用的是Go语言的文件路径清理方法,预处理过程中会忽略掉空白行。

    在Go语言的文件路径匹配规则之外,Docker还支持一个特殊的通配符**,用于匹配任意数量的目录(包括零),例如,**/*.go将排除所有以.go结尾的文件,它会在编译上下文的根目录的所有目录中找。

    以感叹号!开始的行被用于标出排除中的异常文件,下面的这个.dockerignore文件的例子就使用了这种机制:

    *.md
    !README.md
    

    在上下文中除了README.md之外,所有markdown文件都会被排除。

    异常规则!的位置影响行为:.dockerignore文件的最后一行匹配一个特定文件,它是包含还是排除呢?看下面的例子:

    *.md
    !README*.md
    README-secret.md
    

    除了README 文件之外,没有任何markdown文件被包含进上下文,并没有README-secret.md

    现在看这个例子:

    *.md
    README-secret.md
    !README*.md
    

    所有的README文件都会被包含进去,中间一行是没有任何影响的,因为!README*.md 能够匹配 README-secret.md,并且在后面。

    你甚至可以用.dockerignore来排除Dockerfile文件和.dockerignore,这些文件仍然是会被送到守护进程的,因为需要它们做这些工作,但是ADDCOPY指令是不会copy它们到镜像中去的。

    最后,你可能想要指定文件包含进上下文,而不是排除它们,为了实现这个目的,可以使用*作为第一个模式,下面使用一个或者多个!异常模式。

    注意:由于历史原因,模式.是被忽略的。

    到此为止介绍Dockerfile文件中工作原理和一些语法,以及相关的一些东西,其中03和04节不太常用,翻译不是太好,请高手指正。Dockerfile中常用的指令下一篇文章再介绍。

    相关文章

      网友评论

        本文标题:Dockerfile 参考手册(一)

        本文链接:https://www.haomeiwen.com/subject/fczxuxtx.html