Dockerfile参考(3) – 解释器指令escape

解释器指令是可选的,并影响Dockerfile随后行的处理方式。解释器指令不会添加新层到镜像,也不会显示在构建步骤中。解释器指令的编写格式是一种特定的注释格式# directive=value。一个指令只能用一次。
一旦注释,空行或构建器指令已经被处理,Docker不再寻找解析器指令。相反,它将格式化为解析器指令的任何内容视为注释,并且不尝试验证它是否可能是解析器指令。因此,所有解析器指令必须位于Dockerfile的最顶端。
解析器指令不区分大小写。不过,约定使用小写。也约定解释器指令后包括一个新空行。解析器指令不支持行连续字符。
由于这些规则,下面的示例都是无效的:
由于使用了行连续字符无效:

  1. # direc
  2. tive=value

由于出现两次相同的解释器指令:

  1. # directive=value1
  2. # directive=value2
  3.  
  4. FROM ImageName

由于出现在构建指令后,所以视为注释:

  1. FROM ImageName
  2. # directive=value

由于出现在不是解释器指令的注释后面,视为一个注释:

  1. # About my dockerfile
  2. FROM ImageName
  3. # directive=value

由于不被识别,未知的指令视为一个注释。此外由于出现在不是解释器指令的注释后面,一个已知的指令也视为注释。

  1. # unknowndirective=value
  2. # knowndirective=value

解析器指令中允许使用非换行符空格。因此,下面的指令视为同样的:

  1. #directive=value
  2. # directive =value
  3. #   directive= value
  4. # directive = value
  5. #     dIrEcTiVe=value

目前支持的解释器指令有:

  • escape
  • escape

    格式为:

    1. # escape= (backslash)

    1. # escape=` (backtick)

    escape指令用于设置在Dockerfile中转义使用的字符。如果不指定,默认的转义字符是。
    转义字符用来转义一行中的字符,也可以转义一个新行。这就允许Dockerfile的指令跨越多行。注意,不管escape解析器指令是否包括在Dockerfile中,除了在行尾转义新行 RUN命令中不进行字符转义。
    设置转义字符`对于windows上使用Dockerfile非常有用,因为默认的是目录路径的分隔符。而`与Windows PowerShell使用的转义字符一致。
    看下下面的在windows Dockerfile的示例,不是很显然地看出错误。第二行行尾的第二个解释为转义新行,而不是第一个的转义目标。结果是这个Dockerfile的第2行和第3行合并为一行处理:

    1. FROM windowsservercore
    2. COPY testfile.txt c:\
    3. RUN dir c:

    构建时输出:

    1. PS C:John> docker build -t cmd .
    2. Sending build context to Docker daemon 3.072 kB
    3. Step 1 : FROM windowsservercore
    4.  —> dbfee88ee9fd
    5. Step 2 : COPY testfile.txt c:RUN dir c:
    6. GetFileAttributesEx c:RUN: The system cannot find the file specified.
    7. PS C:John>

    上面的一个解决方法是使用/作为COPY和dir的路径分隔符。不过这个语法最好的结果是只会由于不是windows原生的分隔符感到混乱,最糟糕的情况是会导致错误,因为windows不是所有的命令都支持这个分隔符。
    所以最好的解决方法是使用excape解释器指令来指令`作为分隔符:

    1. # escape=`
    2.  
    3. FROM windowsservercore
    4. COPY testfile.txt c:
    5. RUN dir c:

    结果:

    1. PS C:John> docker build -t succeeds –no-cache=true .
    2. Sending build context to Docker daemon 3.072 kB
    3. Step 1 : FROM windowsservercore
    4.  —> dbfee88ee9fd
    5. Step 2 : COPY testfile.txt c:
    6.  —> 99ceb62e90df
    7. Removing intermediate container 62afbe726221
    8. Step 3 : RUN dir c:
    9.  —> Running in a5ff53ad6323
    10.  Volume in drive C has no label.
    11.  Volume Serial Number is 1440-27FA
    12.  
    13.  Directory of c:
    14.  
    15. 03/25/2016  05:28 AM    <DIR>          inetpub
    16. 03/25/2016  04:22 AM    <DIR>          PerfLogs
    17. 04/22/2016  10:59 PM    <DIR>          Program Files
    18. 03/25/2016  04:22 AM    <DIR>          Program Files (x86)
    19. 04/18/2016  09:26 AM                 4 testfile.txt
    20. 04/22/2016  10:59 PM    <DIR>          Users
    21. 04/22/2016  10:59 PM    <DIR>          Windows
    22.                1 File(s)              4 bytes
    23.                6 Dir(s)  21,252,689,920 bytes free
    24.  —> 2569aa19abef
    25. Removing intermediate container a5ff53ad6323
    26. Successfully built 2569aa19abef
    27. PS C:John>

    Dockerfile参考(2) – 格式

    下面是Dockerfile的格式:

    1. # Comment
    2. INSTRUCTION arguments

    instruction指令是不区分大小写的,不过一般约定使用大写字符以与参数区分开来。
    docker按顺序执行Dockerfile中的指令。第一个指令必须是FROM,用于指定从哪个Base Image构建镜像。
    Docker把以#开头的行视为注释,除非该行是一个有效的解释器指令。#标志在行中的其它地方视为一个参数,如:

    1. # Comment
    2. RUN echo ‘we are running some # of cool things’

    注释中不支持行连续字符。

    Dockerfile参考(1) – 用法

    docker build命令从Dockerfile文件和上下文构建一个镜像。构建的上下文是一个指定的PATH或URL位置。PATH是你本地文件系统的一个目录。URL是git仓库地址。
    上下文是递归处理的。所以PATH包括所有的子目录,URL包括仓库和它的子模块。下面是使用当前目录作为上下文的简单build命令:

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

    build命令来docker daemon运行,不是由CLI。build进程的第一件事是递归发送整个上下文给daemon。大多数情况下,最好使用一个空的目录作为上下文并放置Dockerfile到此目录。只添加构建Dockerfile时所需的文件。

    警告:不要使用root目录/作为PATH,因为它会导致build传递你硬盘的所有内容到docker daemon。

    Dockerfile包括了一系列用于构建镜像的指令,例如COPY指令。为了提高构建性能,可以通过添加.dockerignore文件到上下文目录来排除文件和目录。
    通常称为Dockerfile的Dockerfile文件放置在上下文的根目录。你可以将docker build与-f参数一起使用来指向你文件系统的任何位置的Dockerfile。

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

    可以使用-t参数指定保存新镜像的仓库和标签:

    1. $ docker build -t shykes/myapp .

    要标记镜像到多个仓库,可以在build命令中使用多个-t参数:

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

    docker daemon一个接一个运行Dockerfile中的指令,在最后生成新镜像之前,如果需要,将提交每一个指令的结果到新镜像。docker daemon会自动清理你发送过来的上下文。
    注意每个指令都是独立运行的,所以RUN cd /tmp不会对下一个指令有任何影响。
    如果有可能,docker会重用镜像缓存来加速docker build进程。在控制台输出中使用到缓存时将显示Using cache消息。如:

    1. $ docker build -t svendowideit/ambassador .
    2. Sending build context to Docker daemon 15.36 kB
    3. Step 1 : FROM alpine:3.2
    4.  —> 31f630c65071
    5. Step 2 : MAINTAINER [email protected]
    6.  —> Using cache
    7.  —> 2a1c91448f5f
    8. Step 3 : RUN apk update &&      apk add socat &&        rm -r /var/cache/
    9.  —> Using cache
    10.  —> 21ed6e7fbb73
    11. Step 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
    12.  —> Using cache
    13.  —> 7ea8aef582cc
    14. Successfully built 7ea8aef582cc

    Docker用户指南(1) – 编写Dockerfile的最佳实践

    Docker通过读取Dockerfile里的指令来自动构建一个镜像。Dockerfile是一个包含了所有用于构建镜像的命令的文本文件。
    Dockerfile遵循特定的格式来使用一组特定的指令。你可以在Dockerfile Reference了解其基础知识。
    本文涵盖了Docker,Inc推荐的最佳实践和方法。以及Docker社区创建易于使用的,有效的Dockerfile文件。

    一般准则和建议

    容器应该是精简的

    用来生成容器的Dockerfile文件应该尽可能的精简。意味着它可以停止和销毁并生成一个新的最小配置的容器替换旧的。

    使用.dockerignore文件

    在大多数情况下,最好把Dockerfile放到一个空的目录下。然后只添加构建Dockerfile所需的文件。为了提升构建性能,你应该通过添加一个.dockerignore文件到那个目录来排除文件和目录。这个文件的排除语法与.gitignore文件类似。

    避免安装不必要的包

    为了减少复杂性,依赖性,文件大小和构建时间,你应该避免安装额外的或不必要的包。例如,你不需要在一个数据库镜像添加一个文本编辑器。

    一个容器一个进程

    在决大多数情况中,你应该在一个容器只运行一个进程。将应用程序解耦到多个容器中使得容器更易水平扩展和重用。如果一个服务依赖另一个服务,使用容器链接功能。

    尽量减少层的数量

    你需要在Dockerfile的可读性(以及因此的长期可维护性)和最小化它使用的层数之间找到平衡。 要慎重引用新的数据层。

    排序多行参数

    尽可能的通过以字母数字排序多行参数以方便以后的更改。这会帮助你避免重复的软件包以及之后更容易地更新这个列表。通过添加反斜杠,可以使代码更易读。如下示例:

    1. RUN apt-get update && apt-get install -y
    2.   bzr
    3.   cvs
    4.   git
    5.   mercurial
    6.   subversion

    构建缓存

    在构建一个镜像期间,Docker将按顺序执行Dockerfile中的每一个指令。当执行每个指令前,Docker会在缓存中查找可以重复的镜像,而不是创建一个新的,重复的镜像。如果不想使用缓存可以在docker build命令中加入–no-cache=true参数。
    不过当你要用Docker镜像缓存时,很有必要了解Docker什么时候会和什么时候不会使用缓存。Docker将遵循的基本规则如下:

  • 现在你要重新构建已存在缓存中的镜像,docker将指令与该缓存镜像导出的数据层作对比看它们中的任意一个数据层构建使用的指令是否一样。如果不一样,则认为缓存是无效的。
  • 在大多数情况下仅仅对比Dockerfile中的与数据层的指令就足够了。不过某些指令需要更多的检查和解释。
  • 对于ADD和COPY指令,检查镜像中文件的内容,并计算每个文件的checksum。文件最后修改时间和最后访问时间不会影响到checksum结果。在查找缓存时,将对比当前文件与缓存镜像中文件的checksum。如果文件有更改,如内容和元数据,那么缓存将失效。
  • 除了ADD和COPY命令,docker不会通过对比文件的checksum来决定缓存是否匹配。如当执行apt-get -y update命令时,docker不会对比更新的文件的checksum,只会对比命令本身。
  • 一旦一个指令的缓存无效,接下来的Dockerfile命令将生成新的数据层,不会再使用缓存。

    Dockerfile指令

    FROM

    只要有可能,使用当前的官方镜像作为你的基础镜像。我们推荐使用Debian镜像,因为非常严格控制并保持最小大小(目前150mb以下),然后仍然是一个完整的发行版本。

    LABEL

    你可以向镜像添加标签,以帮助按项目组织镜像,记录许可信息,帮助自动化或出于其他原因。 对于每个标签,添加一行以LABEL开头,并使用一个或多个键值对。 以下示例显示了不同的可接受格式。

    1. # Set one or more individual labels
    2. LABEL com.example.version="0.0.1-beta"
    3. LABEL vendor="ACME Incorporated"
    4. LABEL com.example.release-date="2015-02-12"
    5. LABEL com.example.version.is-production=""
    6.  
    7. # Set multiple labels on one line
    8. LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"
    9.  
    10. # Set multiple labels at once, using line-continuation characters to break long lines
    11. LABEL vendor=ACME Incorporated
    12.       com.example.is-beta=
    13.       com.example.is-production=""
    14.       com.example.version="0.0.1-beta"
    15.       com.example.release-date="2015-02-12"

    RUN

    一如既往,为了使你的Dockerfile更易读,可理解和可维护,使用反斜杠分隔复杂的RUN语句。
    使用RUN最常用的场景可能是使用apt-get安装软件。RUN apt-get是安装软件的命令,有几个问题需要注意。
    你应该避免使用RUN apt-get upgrade或dist-upgrade,因为许多来自基础镜像的“必需”包不会在非特权容器内升级。如果一个基础镜像的软件包过期了,你应该联系它的维护者。如果你知道的foo软件包需要升级,使用apt-get install -y foo来自动更新它。
    始终将RUN apt-get update与apt-get install组合在同一RUN语句中,例如:

    1. RUN apt-get update && apt-get install -y
    2.         package-bar
    3.         package-baz
    4.         package-foo

    在一个RUN语句中单独使用apt-get update可能会引起缓存问题和随后的apt-get install指令失败。例如,你有这样一个Dockerfile:

    1. FROM ubuntu:14.04
    2.     RUN apt-get update
    3.     RUN apt-get install -y curl

    构建镜像后,所有的数据层在docker缓存中。假设你要安装额外的包:

    1. FROM ubuntu:14.04
    2.     RUN apt-get update
    3.     RUN apt-get install -y curl nginx

    docker看到第一个指令RUN apt-get update没有更改就开始使用上一次的缓存。因为docker使用了缓存,所以导致apt-get update没有执行。因为apt-get update没有被执行,那么有可能curl和nginx的包是过期的版本。
    使用RUN apt-get update && apt-get install -y确保你的Dockerfile安装最新的软件包版本,无需进一步的编码或手动干预。
    这种技术被称为“cache busting”。 你还可以通过指定软件包版本来实现缓存无效化。 例如:

    1. RUN apt-get update && apt-get install -y
    2.         package-bar
    3.         package-baz
    4.         package-foo=1.3.*

    指定版本强制构建镜像时安装特定版本的软件而不管缓存里的是什么版本。
    以下是apt-get安装软件时推荐的格式:

    1. RUN apt-get update && apt-get install -y
    2.     aufs-tools
    3.     automake
    4.     build-essential
    5.     curl
    6.     dpkg-sig
    7.     libcap-dev
    8.     libsqlite3-dev
    9.     mercurial
    10.     reprepro
    11.     ruby1.9.1
    12.     ruby1.9.1-dev
    13.     s3cmd=1.1.*
    14.  && rm -rf /var/lib/apt/lists/*

    s3cmd指令指定一个1.1.0*版本。如果之前的镜像使用的是一个旧版本,指定一个新版本会使缓存失效而开始执行apt-get update命令,从而确保安装了新的版本。
    另外,清除apt缓存和删除/var/lib/apt/lists能有效减小镜像大小。

    注意:官方的Debian和Ubuntu镜像会自动执行apt-get clean,所以不需要我们显示调用。

    CMD

    CMD指令用来运行镜像里的软件,命令后面可以添加参数。CMD指令的格式为CMD [“executable”, “param1”, “param2”…]。因此如果镜像是用于运行服务,如Apache和Rails,指令应该为CMD [“apache2″,”-DFOREGROUND”]。实际上这种格式适用于所有运行服务的镜像。
    在大多数其它情况下,CMD应该使用一个交互式的shell,如bash,python的perl。例如,CMD [“perl”, “-de0”], CMD [“python”], 或CMD [“php”, “-a”]. 使用这种形式意味着当你执行如docker run -it python,你会进入到一个可用的shell。CMD应该很少以CMD [“param”,“param”]的形式与ENTRYPOINT连接使用,除非你对ENTRYPOINT很熟悉。

    EXPOSE

    EXPOSE指令表示容器中的哪个端口用来监听连接。因此你应该使用常见的惯例的端口。例如,Apache web server应该EXPOSE 80端口,而MongoDB容器应该EXPOSE 27017等。
    对于容器需要外部访问的时候,用户可以执行docker run跟随一个参数来映射指定的端口,此时EXPOSE对这种情况无作用。对于container linking,Docker为链接容器提供了访问被链接容器的路径环境变量,如PHP容器连接到MySQL容器的环境变量MYSQL_PORT_3306_TCP。

    ENV

    为了使新软件更容易运行,你可以使用ENV来更新PATH环境变量。例如ENV PATH /usr/local/nginx/bin:$PATH会确保CMD [“nginx”]正常运行。
    ENV指令也可以为你想容器化的软件指定所需的环境变量,如Postgres的PGDATA环境变量。
    最后,ENV也可以用来指定一个版本号,为之后的安装配置软件使用,以便更好的进行维护。

    ADD或COPY

    虽然ADD和COPY功能类似,一般来讲,首选COPY。因为它比ADD更透明。COPY只是比本地文件复制到容器中,而ADD有一些其它的功能(如会解压tar文件和下载远程文件)不会很明显。ADD最佳用途是将本地的tar文件自动解压到镜像中,如ADD rootfs.tar.xz /。
    如果在Dockerfile中有多处需要不同的文件,每个文件单独使用一个COPY,而不是使用一个COPY指令一次复制完。这保证了当其中的某个文件更新时,只是这个文件的缓存失效,其它的还是能够正常使用缓存。
    例如:

    1. COPY requirements.txt /tmp/
    2. RUN pip install –requirement /tmp/requirements.txt
    3. COPY . /tmp/

    这个示例当除requirements.txt其它文件更新时,前两步还是能够使用缓存的,如果只用一条COPY指令,那么/tmp/目录里的文件一旦更新,缓存将全部失效。
    由于关系到镜像的大小,不推荐使用ADD来获取远程文件;你应该使用curl或wget替代。用这种方式你可以当文件解压后删除原来的压缩文件,且没有新加一层数据层。例如,应该避免如下用法:

    1. ADD http://example.com/big.tar.xz /usr/src/things/
    2. RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
    3. RUN make -C /usr/src/things all

    使用如下方法:

    1. RUN mkdir -p /usr/src/things
    2.     && curl -SL http://example.com/big.tar.xz
    3.     | tar -xJC /usr/src/things
    4.     && make -C /usr/src/things all

    对于其它不需要自动解压文件的情况,你应该始终使用COPY。

    ENTRYPOINT

    ENTRYPOINT最佳用途是设置镜像的主命令,允许镜像作为命令一样运行(然后使用CMD设置默认参数)
    如下示例:

    1. ENTRYPOINT ["s3cmd"]
    2. CMD ["–help"]

    运行如下命令会显示命令帮助:

    1. $ docker run s3cmd

    或者设置参数:

    1. $ docker run s3cmd ls s3://mybucket

    ENTRYPOINT指令还可以与helper脚本结合使用,允许它以类似于上述命令的方式工作,即使启动软件可能需要多个步骤。
    例如,Postgres Official Image使用以下脚本作为其ENTRYPOINT:

    1. #!/bin/bash
    2. set -e
    3.  
    4. if [ "$1" = ‘postgres’ ]; then
    5.     chown -R postgres "$PGDATA"
    6.  
    7.     if [ -z "$(ls -A "$PGDATA")" ]; then
    8.         gosu postgres initdb
    9.     fi
    10.  
    11.     exec gosu postgres "$@"
    12. fi
    13.  
    14. exec "$@"

    注意:脚本里使用bash命令exec,使得程序以容器的PID 1运行。这样程序就能接收到发送到容器的Unix信号。

    帮助程序脚本复制到容器中,并通过容器启动时的ENTRYPOINT运行:

    1. COPY ./docker-entrypoint.sh /
    2. ENTRYPOINT ["/docker-entrypoint.sh"]
    3. CMD ["postgres"]

    该脚本允许用户以几种方式运行Postgres.
    最简单启动Postgres的方法:

    1. $ docker run postgres

    或者运行Postgres并传递参数过去:

    1. $ docker run postgres postgres –help

    最后,也可以启动一个完全不同的工具,如bash:

    1. $ docker run –rm -it postgres bash

    VOLUME

    VOLUME指令应用于公开由docker容器创建的任何数据库存储区域,配置存储或文件/文件夹。
    强烈建议你对镜像的任何更改和/或用户可维护的部分使用VOLUME。

    USER

    如果服务可以在没有权限的情况下运行,请使用USER更改为非root用户。 首先在Dockerfile中创建一个类似于RUN groupadd -r postgres && useradd -r -g postgres postgres的用户和组。

    注意:镜像中的用户和组获得非确定性的UID / GID,因为无论镜像如何重建,都会分配“下一个”UID / GID。 所以,如果它是关键的,你应该分配一个显式的UID / GID。

    你应该避免安装或使用sudo,因为它具有不可预测的TTY和信号转发行为,可能导致更多的问题。 如果你绝对需要类似于sudo的功能(例如,以root身份初始化守护程序,但以非root身份运行它),则可以使用“gosu”。
    最后,为了减少数据层和复杂性,避免频繁地来回切换USER。

    WORKDIR

    为了清晰和可靠,你应该始终为WORKDIR使用绝对路径。 此外,你应该使用WORKDIR,而不是如RUN cd …&do-something,这很难维护。