Docker 教程(二):Dockerfile

你好,我是看山。

本文源自并发编程网的翻译邀请,翻译的是 Jakob Jenkov 的 《Docker 教程》 中的第二篇。

Dockerfile 包含一组关于如何构建 Docker 镜像的说明,通过docker build命令执行 Dockerfile 文件,可以构建一个 Docker 镜像,本文介绍了如何编写 Dockerfile 文件以及构建一个 Docker 镜像。

Dockerfile 的好处

Dockerfile 文件以书面形式说明了如何构建一个 Docker 镜像,Docker 镜像通常包含如下内容:

  • 首先需要一个基本的 Docker 镜像,在这个基础 Docker 镜像上构建自己的 Docker 镜像。
  • 一组需要安装在 Docker 镜像中的工具和应用。
  • 一组需要复制到 Docker 镜像中的文件(比如配置文件)。
  • 可能需要在防火墙中打开的网络(TPC/UDP)端口或其他。
  • 等等。

首先,在 Dockerfile 文件中以书面形式说明这些,就意味着,我们不用特意记住应用程序如何安装,包括操作系统什么要求、需要安装的应用程序、需要赋值的文件、需要打开的网络端口等,这些内容都被记录在 Dockerfile 中。

另外,通过 Dockerfile 文件构建 Docker 镜像,我们不需要手动执行这些繁琐重复且容易出错的工作。Docker 会自动做这些事情,简单、快速、且不容易出错。

第三,我们很容易和其他人分享 Dockerfile 文件,并且他们可以自己构建 Docker 镜像。

第四,Dockerfile 很容易存储在 Git 这样的版本控制器中,这样就可以跟踪 Dockerfile(服务器、应用配置)的变更记录。版本控制器也可以很容易的让人们协同合作,比如在 Dockerfile 上,以及分享 Dockerfile。

Dockerfile 的结构

Dockerfile 包含一组指令,每个指令有一个命令和参数组成,类似于命令行可执行文件。下面是一个 Dockerfile 简单示例:

# 基础镜像
FROM ubuntu:latest

# 这里可以有更多安装软件和复制文件到镜像中的说明。
COPY    /myapp/target/myapp.jar    /myapp/myapp.jar

# 在 Docker 容器中执行的命令。
CMD echo Starting Docker Container

Docker 基础镜像

Docker 镜像是由层组成,每一层都会为最终的 Docker 镜像添加一些内容。每一个层实际上都是一个单独的 Docker 镜像,所以说,Docker 镜像是由一个或多个层镜像组成,我们可以在其上添加自己的层。

当通过 Dockerfile 文件指定自己的 Docker 镜像时,通常是从一个 Docker 基础镜像开始。这是另一个 Docker 镜像,可以在其上构建自己的 Docker 镜像。这个 Docker 基础镜像本身可能也包含多个层,并且是基于另一个基础镜像构建的。

我们可以使用From命令在 Dockerfile 文件中指定 Docker 镜像作为基础镜像,如下节所述。

MAINTAINER

MAINTAINER命令用于说明谁在维护这个 Dockerfile 文件。比如:

MAINTAINER   Joe Blocks <joe@blocks.com>

MAINTAINER命令并不常用,因为这类信息在 Git 存储或其他地方有了。

FROM

FROM命令用于指定 Docker 基础镜像,如果是从原始 Linux 镜像开始,可以使用如下命令:

# 基础镜像
FROM ubuntu:latest

CMD

CMD命令用于指定启动 Docker 容器是需要执行的命令,该容器是基于此 Dockerfile 构建的 Docker 镜像,下面是一些 Dockerfile 的CMD示例:

CMD echo Docker container started.

本例是打印“Docker container started”这行文本。

下一个CMD示例是启动一个 java 应用:

CMD java -cp /myapp/myapp.jar com.jenkov.myapp.MainClass arg1 arg2 arg3

COPY

COPY命令将一个或多个文件从主机(从 Dockerfile 文件构建 Docker 镜像的机器)复制到 Docker 镜像中,可以复制的内容包括文件或目录,下面是一个示例:

COPY    /myapp/target/myapp.jar    /myapp/myapp.jar

这个例子是把主机的/myapp/target/myapp.jar 文件复制到 Docker 进行中的/myapp/myapp.jar 文件。第一个参数是主机路径(从哪里来),第二个参数是 Docker 镜像的路径(到哪里去)。

我们还可以复制一个目录到 Docker 镜像中,比如:

COPY    /myapp/config/prod    /myapp/config

这个例子是把主机的/myapp/config/prod 目录复制到 Docker 镜像中的/myapp/config 目录。

我们还可以复制多个文件到 Docker 镜像中的一个目录中,比如:

COPY    /myapp/config/prod/conf1.cfg   /myapp/config/prod/conf2.cfg   /myapp/config/

这个例子是将主机的/myapp/config/prod/conf1.cfg 文件和/myapp/conig/prod/conf2.cfg 文件复制到 Docker 镜像中的/myapp/config/目录中。注意,目标目录必须以/(斜杠)结束才能工作。

ADD

ADD命令与COPY命令工作方式相同,只有一些细微的差别:

  • ADD命令可以复制并提取 TAR 文件到 Docker 镜像中。
  • ADD命令可以通过 HTTP 下载文件,并复制到 Docker 镜像中。

下是一些示例:

ADD    myapp.tar    /myapp/

这个例子是将指定的 TAR 文件解压缩并提取到 Docker 镜像的/myapp/目录中。

下面是另一个例子:

ADD    http://jenkov.com/myapp.jar    /myapp/

ENV

ENV命令是在 Docker 镜像中设置环境变量,此环境变量可用于CMD命令在 Docker 镜像内部启动应用程序。举个例子:

ENV    MY_VAR   123

本例将环境变量MY_VAR设置为值 123。

RUN

RUN可以在 Docker 镜像中执行命令行指令,执行时机是 Docker 镜像构建过程中,所以RUN命令只会执行一次。RUN命令可用于在 Docker 镜像中安装应用程序、提取文件或其他命令行功能,这些操作只需要执行一次,以供 Docker 镜像后续使用。

RUN apt-get install some-needed-app

ARG

ARG命令允许定义一个参数,这个参数可以在通过 Dockerfile 文件构建 Docker 镜像时,通过命令参数传递给 Docker。比如:

ARG tcpPort

当执行docker build命令执行 Dockerfile 构建 Docker 镜像时,可以指定tcpPort参数,比如:

docker build --build-arg tcpPort=8080 .

注意,--build-arg后面的tcpPort=8080,是将tcpPort参数的值设置为 8080。

我们可以通过多个ARG命令定义多个参数,举个例子:

ARG tcpPort
ARG useTls

当构建 Docker 镜像时,必须为所有构建参数提供值。【译者注,1.13 版本之前,不提供值会直接报错,1.13 版本之后,不提供值不会报错,但是会弹出警告】。举个例子:

docker build --build-arg tcpPort=8080 --build-arg useTls=true .

我们可以为ARG设置默认值,当构建 Docker 镜像时,如果没有指定参数值,将使用默认值。举个例子:

ARG tcpPort=8080
ARG useTls=true

如果tcpPortuseTls在生成 Docker 镜像时,都没有设置参数,将使用默认值 8080 和 true。

ARG声明的参数通常在 Dockerfile 的其他地方引用,比如:

ARG tcpPort=8080
ARG useTls=true

CMD start-my-server.sh -port ${tcpPort} -tls ${useTls}

注意:两个引用${tcpPort}${useTls},引用名是tcpPortuseTls这两个ARG声明的参数。

docker build --build-arg tcpPort=8080

WORKDIR

WORKDIR命令指明了 Docker 镜像中的工作目录,工作目录将对WORKDIR指令之后的所有命令生效,举个例子:

WORKDIR    /java/jdk/bin

EXPOSE

EXPOSE命令将对外开放 Docker 容器中的网络端口,比如,如果 Docker 容器运行一个 web 服务器,那么,该 web 服务器可能需要打开端口 80,以便客户端链接到它。举个例子:

EXPOSE   8080

我们还可以指明打开端口的通信协议,比如:UDP 和 TCP。下面是设置允许通信协议的示例:

EXPOSE   8080/tcp 9999/udp

如果没有指定协议,将默认认定为 TCP 协议。

VOLUME

VOLUME命令会在 Docker 镜像中创建一个目录,这个目录可以挂载到 Docker 主机上。换句话说,可以在 Docker 镜像中创建目录,比如/data,这个目录可以在稍后挂载到 Docker 主机的/container-data/container1目录上。挂载成功后,容器会启动。下面是一个使用VOLUME命令在 Dockerfile 中定义装载目录的示例:

VOLUME   /data

ENTRYPOINT

ENTRYPOINT命令为从该 Docker 镜像启动 Docker 容器提供入口点,入口点是 Docker 容器启动时执行的应用程序或命令。这样,ENTRYPOINTCMD工作方式类似,不同之处在于,使用ENTRYPOINT时,当ENTRYPOINT执行的应用程序完成时,Docker 容器将关闭。因此,ENTRYPOINT使 Docker 镜像本身成为一个可执行命令,可以启动,完成后关闭。以下是ENTRYPOINT示例:

ENTRYPOINT java -cp /apps/myapp/myapp.jar com.jenkov.myapp.Main

这个示例将在容器启动时执行 Java 应用程序的主类com.jenkov.myapp.Main,当应用程序关闭时,Docker 容器也会关闭。

HEALTHCHECK

HEALTHCHECK命令可以定期执行健康检查,以监视 Docker 容器中运行的应用程序的运行状况。如果命令返回 0,Docker 将认为应用程序和容器正常,如果命令返回 1,Docker 会认为应用程序和容器不正常。示例如下:

HEALTHCHECK java -cp /apps/myapp/healthcheck.jar com.jenkov.myapp.HealthCheck https://localhost/healthcheck

这个示例中使用了 java 应用程序的com.jenkov.myapp.HealthCheck作为健康检查的命令,我们可以使用任何有意义的健康检查命令。

健康检查间隔时间

默认情况下,Docker 每 30 秒执行一次HEALTHCHECK命令。如果想修改时间间隔,我们可以自定义时间,通过--interval参数,可以指定健康检查的检查间隔时间。下面是一个将HEALTHCHECK间隔设置为 60 秒的示例:

HEALTHCHECK --interval=60s java -cp /apps/myapp/healthcheck.jar com.jenkov.myapp.HealthCheck https://localhost/healthcheck

健康检查开始时间

默认情况下,Docker 会立即检查 Docker 容器的监控状况。但是,有些应用程序可能需要一段时间启动,因此,只有经过某段时间后再进行健康检查才有意义。我们可以使用--start-period参数设置健康检查开始时间。下面是一个将健康检查设置为 5 分钟的示例,在 Docker 开始健康检查之前,为容器和应用程序提供 300 秒(5 分钟)的启动时间:

HEALTHCHECK --start-period=300s java -cp /apps/myapp/healthcheck.jar com.jenkov.myapp.HealthCheck https://localhost/healthcheck

健康检查超时时间

健康检查很有可能超时,如果HEALTCHECK命令需要超过给定时间限制才完成,Docker 将认为健康检查超时。可以使用--timeout参数设置超时时间,如下是设置超时时间为 5 秒的示例:

HEALTHCHECK --timeout=5s java -cp /apps/myapp/healthcheck.jar com.jenkov.myapp.HealthCheck https://localhost/healthcheck

注意,如果健康检查超时,Docker 也会认为容器不健康。

健康检查重复次数

如果HEALTHCHECK命令执行失败,有可能是结果返回 1,或者执行超时,Docker 会在认定容器不健康前,重试 3 次HEALTHCHECK命令,用于检查 Docker 容器是否返回健康状态。可以通过--retries设置重试次数。下面是将重试次数设置为 5 的示例:

HEALTHCHECK --retries=5 java -cp /apps/myapp/healthcheck.jar com.jenkov.myapp.HealthCheck https://localhost/healthcheck

推荐阅读


你好,我是看山,公众号:看山的小屋,10 年老猿,Apache Storm、WxJava、Cynomys 开源贡献者。游于码界,戏享人生。

原文链接:Docker Dockerfile
翻译:https://www.howardliu.cn
译文链接:Docker 教程(二):Dockerfile
CSDN 主页:http://blog.csdn.net/liuxinghao
CSDN 博文:Docker 教程(二):Dockerfile

公众号:看山的小屋