Dockerfile最佳实战

概述

Docker 可以解析Dockerfile文件中的指令自动构建镜像,这个文件包含了构建指定镜像的所需的所有指令。Dockerfile遵循特定的格式,使用特定的指令集。你可以在《Dockerfile 参考》页面学习到更多基础知识。如果你是一个编写Dockerfile的新手,你应该从这里开始。Dockerfile。我们强烈地建议你遵循下面的推荐(其实,如果你正在创建一个官方的镜像,你必须遵循这些规范)。 在buildpack-deps Dockerfile里,你可以看到许多这样的规范和建议。

注意:想获得在这里提及的任何Dockerfile 命令的更多详细说明,请移步到《Docker 参考》页面。

通用指南和建议

容器应该是“朝生暮死”

Dockerfile

使用一个.dockerignore文件

.dockerignore 文件来排除那些没用的文件和文件夹。这个文件支持类似 .gitignore 文件那样的排除模式。关于如何创建它,可以移步到dockerignore 文件。

避免安装不需要的包

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

一个服务一个容器

几乎在所有的情况下,你都应该遵循一个服务一个容器的规范。解耦应用程序到多个容器中,是它更易横向扩展和重用容器。如果这个服务依赖另外服务,可以使用容器互联。

层数最小化

Dockerfile 文件的可读性(处于长期维护考虑)与它使用的最小层数之间做折中的取舍。关于层数,你应持有谨慎的态度。

分多行编排你的参数

\) 钱加一个空格也很有帮助。 这里有一个来自 buildpack-deps镜像的例子:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

构建缓存

Dockerfile 文件中的命令。随着每条指令的执行,Docker将会到它的缓存中去寻找可以重用的镜像,而不是再去创建一个重复的镜像。如果你完全不想使用缓存,你可以在 docker build 命令里使用 --no-cache=true 选项去关闭它。

然而,如果你想让Docker使用它的缓存,那么,理解它什么时候去寻找一个镜像是很重要的。Docker将遵循下面的这些规则:

  • 从缓存中的基础镜像开始,往下的每条命令将与构建子镜像的指令相比较,寻找与之相同的指令,如果没有找到,缓存不起作用。

DockerfileADD

  •  和

COPYADD

  •  和 

COPY

  •  命令外,缓存检查不会查看容器里的文件而决定是否匹配。例如,当处理 

RUN apt-get -y updateDockerfile

Dockerfile 指令

在编写一个 Dockerfile文件时。

FROM

更多信息请移步《Dockerfile 参考》的FROM部分

 Debian的镜像,虽然它被设计的非常紧凑简小(目前不到100兆),但是仍然是一个完整的发行版本。

RUN

更多信息请移步《Dockerfile参考》的RUN部分

Dockerfile 更加易读,易懂和便于维护,请将长的或者复杂的 RUNRUN 一般都是搭配 apt-get一起使用。当使用 apt-get时,这里几个注意事项:RUN apt-get update

  •  。 这样会引起缓存问题,如果关联的归档文档被更新了,将会导致后续的 

apt-get installRUN apt-get upgrade

  •  或

dist-upgrade

  • , 因为很多来自基础镜像的“底层”的包将会更新失败,在一个无特权的容器里。如果一个基础包已经过期,你应该通知它的维护人员。如果你知道这里一个特定的包,如 

foo

  • ,它需要更新,可以直接使用

apt-get install -y foo

  •  让它自动更新。
  • 应该这样编写你的指令:
RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo

apt-get update,确保绕开本地的缓存,安装最新的版本而不需要编写更多的指令和手动的干预。

  • 绕开缓存可以实现包的版本定位(例如:

package-foo=1.3.*

  • )。这将强制去检索指定的版本,不管缓存里存储了什么。编写你的 

apt-get

例子

RUN 指令,它演示了上述的建议。注意最后的包 s3cmd,指定了一个版本 1.1.0*。如果这个镜像之前使用过一个就得版本,指定的新版将引起 apt-get update 缓存失效,确保一个新的版本被安装(在这个应用场景中,需要这个特性)。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    btrfs-tools \
    build-essential \
    curl \
    dpkg-sig \
    git \
    iptables \
    libapparmor-dev \
    libcap-dev \
    libsqlite3-dev \
    lxc=1.0* \
    mercurial \
    parallel \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.0*

使用这种方法编写指令也可以帮助你避免包的重复,因为这样写比下面的写法更加的易读:

RUN apt-get install -y package-foo && apt-get install -y package-bar

CMD

更多的信息请移步《Dockerfile参考》的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 和ENTRYPOINT 协同工作时,应该使用 CMD [“param”, “param”] 格式。这种方式尽量少用,除非你和你的用户对 ENTRYPOINT 实现机制都很了解。

EXPOSE

更多的西悉尼请移步《Dockerfile参考》的EXPOSE部分

EXPOSE 指令指定容器监听的端口。因此,你应该使用通用、惯例的端口到你的应用。例如,一个包含着Apacheweb服务端的镜像将使用80端口,当镜像包含是一个MangoDB应该使用EXPOSE 27017docker run 带上一个标志,表明如何映射指定的端口到他们选择的端口。为了容器的连接,Docker提供了环境变量来指定接受容器到源容器的路径(如,MYSQL_PORT_3306_TCP)。

ENV

更多的信息请移步《Dockerfile参考》的ENV的部分

ENV 去更新环境变量PATH 。例如,ENV PATH /usr/local/nginx/bin:$PATH 保证CMD [“nginx”]ENV 指令也可以为容器化的运用提供必需的环境变量,比如,Postgres的 PGDATAENV

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ENV

ADD 或 COPY

更多信息请移步《Dockerfile参考》的 ADD部分更多信息请移步《Dockerfile参考》的 COPY部分

ADD 和COPY 的功能类似,一般而言,推荐使用COPY  。因为它比ADD更加见名知意。COPY 只支持将本地本件拷贝到容器中,虽然ADD 拥有一些功能(例如,抽取本地tar文件内容和支持远程URL),但是这些功能不是很常用。因此,ADD 的最佳使用场景是,自动抽取一个本地tar的内容到镜像中,例如:ADD rootfs.tar.xz /Dockerfile 步骤且使用来自的环境中不同的文件,分开COPY

例如:

COPY requirements.txt /tmp/
RUN pip install /tmp/requirements.txt
COPY . /tmp/

RUN 步骤可以增加缓存的命中率,如果你把COPY . /tmp/ADD 从远程URL提出t内容的方法强烈不推荐。你应该使用curl 或 wget

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

相反,你应该这样做:

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

ADD,其他时候,你应该总是使用COPY

ENTRYPOINT

更多信息请移步《Dockerfile参考》的 ENTRYPOINT部分

ENTRYPOINT 最佳使用场景是设置镜像的主入口命令,允许镜像好像命令运行一样(使用 CMD 作为默认的标志)。s3cmd的镜像:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

现在,启动后的镜像与在命令行中执行命令的帮助类似:

$ docker run s3cmd

或在右边添加参数来执行一个命令:

$ docker run s3cmd ls s3://mybucket

这很用,如上所述,可以把镜像的名字当做一个二进制程序来使用。

ENTRYPOINT 例如,Postgres官方镜像使用下面的脚本作为它的ENTRYPOINT

#!/bin/bashset -eif [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fiexec gosu postgres "$@"fiexec "$@"

注意:这个脚本使用了exec,运行时的应用程序会变成容器的PID 1。这将允许应用可以接收发送到容器的所有Unix信号。 查看ENTRYPOINT 帮助文档获得更多的信息。

ENTRYPOINT

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

这个脚本允许用户使用几种交互的方法启动Postgres:

可以简单的启动Postgres:

$ docker run postgres

或者,可以使用它去运行带几个参数的Postgres: 

$ docker run postgres postgres --help

最后,它也可以用来启动一个完全不同的工具,如,Bash:

$ docker run --rm -it postgres bash

VOLUME

更多信息请移步《Dockerfile参考》的VOLUME部分

VOLUME 指令应该用于暴露任何的数据库存储域、配置存储、文件/文件夹,在创建容器的时候。任何易变的或镜像的供用户使用的部分,建议使用VOLUME

USER

更多信息请移步《Dockerfile参考》的USER部分

USER 切换到一个非root用户。使用像这种命令 RUN groupadd -r postgres && useradd -r -g postgres postgres可以创建一个用户和用户组。

注意:镜像里的用户和组的UID/GID都是不确定的,不管它是否被重建。如果这些信息对你很重要,你应该显示的指定一个UID/GID。

sudo ,因为这些操作带来不确定的TTY和信号的转发行为,是一个得不偿失的设置。如果你必需要使用类似 sudo 的功能(例如,在非root用户在初始化一个需要root权限的的守护进程),你可能需要使用“gosu”USER

WORKDIR

更多内容请移步《Dockerfile参考》的WORKDIR部分

为了清晰和可靠,你应该始终为你的WORKDIR指定一个绝度路径。另外,你因该使用WORKDIR 来替代类似RUN cd … && do-something指令,这样可以降低可读性、故障排除难度、维护成本。

ONBUILD

更多内容请移步《Dockerfile参考》的ONBUILD部分

ONBUILD 命令在当前的Dockerfile 构建完成后会被执行。当使用 FROM 为镜像个派生出子镜像时,ONBUILD 也会被执行。也可以简单的理解为,其实是将父Dockerfile 的ONBUILD 中的指令放到子Dockerfile中。ONBUILD 命令会先于子Dockerfile中所有命令执行。ONBUILD 对使用 FROM 基于指定镜像构建很有帮助。例如, ONBUILD 允许你在 Dockerfile里,基于某种语言栈构建任意的软件镜像,你可以参考Ruby的 ONBUILD。.ONBUILD 因该指定一个指定标志(tag),例如:ruby:1.9-onbuild 或 ruby:2.0-onbuildADD 或 COPY 放到ONBUILD要注意。 如果新的构建环境缺少要添加的资源,会导致镜像的构建失败。添加一个分隔标签,如条建议一样,以供编写的 Dockerfile

官方的仓库例子

Dockerfile很有参考价值: