• Post author:
  • Post category:docker
  • Post comments:0评论

一、介绍

  Dockerfile 是一个文本格式的配置文件,用户可以使用它来快速构建自定义的镜像。Dockerfile 由一行行命令语句组成,并且支持以#开头的注释行。一般而言,Dockerfile 主体内容主要分为三部分:基础镜像信息、镜像操作指令和容器启动时执行指令。在配置好 Dockerfile 后可以使用 docker build 命令基于它去创建一个自动构建的镜像。
  官方文档:https://docs.docker.com/engine/reference/builder/

二、基础指令

指令 说明
FROM 指定所创建镜像的基础镜像
ARG 定义创建镜像过程中使用的变量
ENV 定义环境变量
LABEL 为生成的镜像添加元数据标签信息
EXPOSE 通知容器在运行时监听指定的网络端口
VOLUME 创建一个数据卷挂载点
WORKDIR 定义工作目录
ONBUILD 当生成的镜像被用作另一构建的基础镜像时,自动触发执行的操作指令
ADD 添加内容到镜像
COPY 复制内容到镜像
RUN 运行指定命令
CMD 启动容器是指定默认执行的命令

三、指令说明

  以下仅为指令的部分说明,还有不少其它用法,详见官方文档。在做示例时,请先阅读第四部分构建,明白如何使用 docker build 命令基于写好的 Dockerfile 来构建镜像。

1、FROM

指定所创建镜像的基础镜像。任何 Dockerfile 中第一条指令都必须从 FROM 指令开始。

格式:
FROM <image> [AS <name>]
FROM <image>:<tag> [AS <name>]

示例:

FROM centos

FROM centos:7.6.1810

ARG ERSION=7.6.1810
FROM centos:${VERSION}

  值得注意的是,ARG 是可以在 FROM 指令之前,但是先于 FROM 指令的 ARG 指令也仅用于 FROM 指令。因为先于 FROM 的 ARG 其实算是位于构建阶段之外,因此不能在 FROM 之后的任何指令中使用它。如果要在 FROM 之后使用它,需要再次使用 ARG 指令定义下先前定义的变量名并不设置值即可,如下:

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

  一个 Dockerfile 文件中可以存在多个 FROM 指令以创建多个镜像,也可以将一个构建阶段作为对另一个构建阶段的依赖,只需在每条新 FROM 指令之前记录一次提交所输出的最后一个镜像ID 。注意每个 FROM 指令会清除由先前指令创建的任何状态。据此可以依据这个特性进行多步骤构建镜像。
  可以通过在 FROM 指令中添加 AS name 来为新的构建阶段指定名称。该名称可以在后续的 FROM COPY --from=<name> 指令中使用,以引用在此阶段构建的镜像。一般多用于多步骤构建。

2、ARG

定义构建镜像过程中使用的变量。在执行 docker build 构建镜像时,可以使用 –build-arg 选项来传递变量。

格式:
ARG <name>[=<default value>]

示例:

ARG user=www

docker build --build-arg user=www .     # 构建命令
ARG user    # Dockerfile指令

注:使用 ENV 指令定义的环境变量始终会覆盖 ARG 同名指令,无论 ARG 指令在 ENV 前还是后都覆盖。

3、ENV

  定义环境变量。和 ARG 指令样都可以在镜像构建过程中被 RUN 指令使用,但是不同于 ARG 的是,ENV 定义的环境变量在镜像启动的容器中也会存在,而 ARG 定义的仅用于构建镜像过程中。

格式:
ENV <key>=<value> ...

示例:(引号和反斜杠可用于在值中包含空格)

ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy

ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy

4、LABEL

为生成的镜像添加元数据标签信息。这些元数据信息可以用来辅助过滤特定镜像。

格式:
LABEL <key>=<value> ...

示例:(LABEL是键值对,引号和反斜杠可用于在值中包含空格。)

LABEL version="1.0"
LABEL description="This is a small example"

  一个镜像可以有多个 LABEL 标签。你可以在一行上指定多个标签。在 Docker 1.10 之前,这将减小了最终镜像的大小,但是情况不再如此。以下是在一条指令中指定多个标签的两种方式:

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

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

5、EXPOSE

通知容器在运行时监听指定的网络端口。你可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认为 TCP。

格式:
EXPOSE <port> [<port>/<protocol>...]

示例:

EXPOSE 80/tcp
EXPOSE 80/udp

6、VOLUME

  创建一个数据挂载点。运行容器时可以从本地主机或者其他容器挂载数据卷。数据卷只要不主动删除,一般会一直存在。一般用于持久化数据。

格式:
VOLUME ["/data"]

示例:(该值可以是 JSON 数组,也可以是具有多个参数的纯字符串。)

VOLUME ["/var/log/"]    # 此处必须使用双引号

VOLUME /var/log

VOLUME /var/log /var/db

  使用以下 Dockerfile 构建镜像,再使用构建的镜像 docker run 时会创建一个新的挂载点 /myvol,并会将 greeting 文件复制到新创建的卷中。

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

  卷可以使用 docker volume ls 查看,不过一般是随机生成的一串字符。创建的数据卷放在 /var/lib/docker/volumes 目录下,而数据是放在数据卷的 _data 目录里面,我们可以在这个目录下看到 greeting 文件。
  注:该指令是无法从 Dockerfile 内挂载主机目录的,即不能实现类似于 docker run 的 -v 选项将主机指定命令映射到容器内。

7、WORKDIR

定义工作目录。即为后续 RUN、CMD、COPY、ADD、ENTRYPOIN 指令配置工作的目录。

格式:
WORKDIR /path/to/workdir

示例:
  可以使用多个 WORKDIR 指令,如果后续指令提供的是相对路径,则它将相对于上一条 WORKDIR 指令的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

这样最终的工作目录路径是 /a/b/c。

8、ONBUILD

当生成的镜像被用作另一构建的基础镜像时,自动触发执行的操作指令。任何构建指令都可以注册为触发器。

格式:
ONBUILD <INSTRUCTION>

示例:

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

注:不允许使用 ONBUILD ONBUILD。ONBUILD 指令可能不会触发 FROM 或 MAINTAINER 指令。

9、ADD

添加内容到镜像。

格式:
ADD <src> <dest>

  该指令将复制指定的 <src> 路径下内容到镜像的 路径下。
  <src> 可以是当前构建目录下的一个相对路径(文件或者目录),也可以是一个远程URL。如果远程 URL 文件受身份验证保护,则需要使用RUN wget、 RUN curl 或从容器中使用其它工具,因为 ADD 指令不支持身份验证。
  <src> 可以指定多个资源,如果它们是文件或目录,则将其路径解释为相对于当前构建目录下的相对路径。
  如果 <src> 是一个 tar 压缩文件(gzip、bzip2 或 xz)会自动解压,但是自来远程 URL 的资源不会被解压缩。
  <src> 支持通配符,并且匹配将使用 Go 的 filepath.Match 规则完成。
  <dest> 可以是镜像内的绝对目录,也可以是相对于工作目录(WORKDIR)的相对路径

示例:

ADD home.txt /mydir/    # 添加"home.txt"文件    

ADD hom* /mydir/        # 添加所有以"hom"开头的文件      

ADD hom?.txt /mydir/    # ? 表示一个单字符,例如:"home.txt"

ADD test relativeDir/   # 相对路径。表示添加"test"文件到 `WORKDIR`/relativeDir/ 下

ADD test /absoluteDir/  # 绝对路径。表示添加"test"文件到 /absoluteDir/ 下

  添加包含特殊字符(例如:[ 和 ])的文件或目录时,需要按照 Golang 规则转义那些路径,以防止将它们被视为匹配模式。例如,要添加名为 arr[0].txt 的文件,请使用以下命令;

ADD arr[[]0].txt /mydir/

  注: 有无斜杠 / 结尾代表意义不一样,无代表常规文件,有代表目录。如果 直接或由于使用通配符而指定了多个资源,则 必须是目录,即并且必须以斜杠 / 结尾。目录本身不被复制,仅其内容被复制。如果 不存在,它将与路径中所有缺少的目录一起创建。

10、COPY

复制内容到镜像

格式:
COPY <src> <dest>

该指令将复制本地主机指定的 <src> 路径下内容到镜像的 <dest> 路径下。
<src> 是当前构建目录下的一个相对路径(文件或者目录),也就是说要复制的文件或者目录要处于构建目录下。
<src> 可以指定多个资源,如果它们是文件或目录,则将其路径解释为相对于当前构建目录下的相对路径。
<src> 支持通配符,并且匹配将使用 Go 的 filepath.Match 规则完成。
<dest> 可以是镜像内的绝对目录,也可以是相对于工作目录(WORKDIR)的相对路径
COPY 指令用法和上面的 ADD 指令类似,除了不支持自动解压缩和远程URL,都基本类似。

示例:

COPY home.txt /mydir/    # 复制"home.txt"文件    

COPY hom* /mydir/        # 复制所有以"hom"开头的文件      

COPY hom?.txt /mydir/    # ? 表示一个单字符,例如:"home.txt"

COPY test relativeDir/   # 相对路径。表示复制"test"文件到 `WORKDIR`/relativeDir/ 下

COPY test /absoluteDir/  # 绝对路径。表示复制"test"文件到 /absoluteDir/ 下

  复制包含特殊字符(例如:[ 和 ])的文件或目录时,需要按照 Golang 规则转义那些路径,以防止将它们被视为匹配模式。例如,要添加名为 arr[0].txt 的文件,请使用以下命令;

COPY arr[[]0].txt /mydir/

  注: 有无斜杠 / 结尾代表意义不一样,无代表常规文件,有代表目录。如果 直接或由于使用通配符而指定了多个资源,则 必须是目录,即并且必须以斜杠 / 结尾。目录本身不被复制,仅其内容被复制。如果 不存在,它将与路径中所有缺少的目录一起创建。

11、RUN

运行指定命令

格式:(有两种格式)
RUN <command>
RUN ["executable", "param1", "param2"]

  第一种格式为 shell 形式,命令在 shell 中运行,默认情况,在 Linux 下以 "/bin/sh -c" 运行。
  第二种格式为 exec 形式,其中指令会被解析为 JSON 数组形式,必须用双引号,用的是 exec 执行的,不会启动 shell 环境。
  每条 RUN 指令将再当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用 \ 来换行。

示例:

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

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

要使用 "/bin/sh" 以外的其它 shell,请使用 exec 形式传入所需的 shell,例如:

RUN ["/bin/bash", "-c", "echo hello"]

注:在 JSON 格式中,必须转义反斜杠。

12、CMD

指定启动容器时默认执行的命令。
格式:
CMD ["executable","param1","param2"](exec 形式,这是首选形式)
CMD ["param1","param2"](作为 ENTRYPOINT 的默认参数)
CMD command param1 param2(shell 形式)

  每个 Dockerfile 只能有一条 CMD 命令,如果指定了多条命令,只有最后一条会被执行。如果 docker run 时手动指定了运行的命令,则会覆盖 CMD 指定的命令。

示例:
如果使用的 shell 形式的 CMD,则将默认使用 “/bin/sh -c” 执行。例如:

FROM ubuntu
CMD echo "This is a test." | wc -

如果要将 <command> 没有 shell 的情况下运行,则必须将命令表示为 JSON 数组,并提供可执行文件的完整路径。 此数组形式是 CMD 的首选格式。任何其他参数必须在数组中分别表示为字符串。例如:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

四、构建

编写完 Dockerfile 之后,可以通过 docker build 命令来构建镜像。
命令格式:(URL是是一个Git仓库的位置)
docker build [OPTIONS] PATH | URL | -

  该命令将读取指定目录下(包括子目录)的 Dockfile,并将该路径下所有数据作为上下文发送给 Docker守护进程。然后 Docker 会校验 Dockerfile 格式通过后,逐条执行其中的指令。此过程会将每个指令的结果提交到新镜像,然后最后输出最终新镜像的ID。注意,每条指令都是独立运行的,并会导致创建新的镜像。
  所以不要用你的根目录作为构建的路径,因为它会将你硬盘驱动器的全部内容传输给 Docker守护进程。

示例:在当前目录下构建镜像,使用当前目录下 Dockerfile 文件,并指定镜像的标签。

docker build -t test/test1:v1 .

参数说明:
  -t:指定镜像的标签
  -f:指定Dockerfile名称,默认为"PATH/Dockerfile"(即当前路径下的Dockerfile)

五、.dockerignore文件

  .dockerignore 文件用于忽略匹配路径或者文件,从而在构建时不将无关数据发送到 Docker 守护进程。它有助于避免不必要地将较大或敏感的文件和目录发送到 Docker 守护进程,并避免使用 ADD 或 COPY 将它们添加到镜像中。
  以下是一个示例 .dockerignore 文件:

# comment
*/temp*        # 排除任何名称以 /temp 开头的文件和目录。例如,排除了普通文件 /somedir/temporary.txt,也排除了目录 /somedir/temp。
*/*/temp*      # 排除了从根以下下两个级别开始以 temp 开头的文件和目录。例如,/somedir/subdir/temporary.txt 被排除。
temp?          # 排除根目录中名称为 temp 后接单个字符的文件和目录。例如,/tempa 和 /tempb 被排除。

以 ! 开头的行可用于排除。以下示例表示除 README.md 文件外,所有 md 文件都将排除在外。

*.md
!README.md

注:*(表示任意多个字符)、?(表示单个字符)、!(表示不匹配)。

六、多阶段构建

  自 17.05 版本开始,Docker 支持多阶段构建镜像( Multi-stage build) 特性,它可以精简最终生成的镜像大小。
  多阶段构建镜像允许在 Dockerfile 中使用多个 FROM 指令。两个 FROM 指令之间的所有指令会生产一个中间镜像,最后一个 FROM 指令之后的指令将生成最终镜像。每个 FROM 指令会清除由先前指令创建的任何状态,所以它能有效的精简最终生成的镜像大小。
  中间镜像中的文件可以通过 COPY --from=<image-number> 或者 COPY --from=<name> 指令拷贝。其中 image-number 表示镜像编号,0为第一个构建的基础镜像;name 表示在 FROM 指令中使用 AS name 指定的名称。–from 选项也可以指定本地或者远程仓库的镜像。
  对于需要编译的应用(如 C、Go、Java等)来说,这意味着要为应用构建 Docker 镜像是需要编译源代码以及打包目标代码,通常情况下这至少需要准备两个环境的 Docker 镜像:
   • 编译环境镜像:包括完整的编译引擎、依赖库等,往往比较庞大。作用是编译源码为二进制文件。
   • 运行环境镜像:利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。
  而使用多步骤创建,我们可以只使用一个 Dockerfile 来定义整个构建过程。这样,就不需要定义多个 Dockerfile,也不需要使用数据卷来拷贝文件,并且保证了最终生成的运行环境镜像的精筒。

以 Go语言应用为例。创建一个新目录,进入到目录中,创建 main.go 文件。

root@cp:~# mkdir /go
root@cp:~# cd /go/
root@cp:/go# vim main.go
// main.go will output "Hello, Docker"

package main

import ("fmt")

func main() {
    fmt.Println("Hello, Docker")
    }

  创建 Dockerfile,使用 golang:1.9 镜像编译应用二进制文件为 app,使用 alpine:latest 作为运行环境镜像。

root@cp:/go# vim Dockerfile

FROM golang:1.9 as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=O GOOS=linux go build -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/test/app . 
CMD ["./app"]

  执行如下命令构建镜像。我们可以看到会生成两个镜像,test/test-go:latest 镜像就是我们最终生成的运行环境镜像,只有7.99MB。而 : 为第一次构建阶段的生成的编译环境镜像,有 751MB 大小,这个我们可以清理掉。

root@cp:/go# docker build -t test/test-go .
......
Successfully built d6b30b26fa21
Successfully tagged test/test-go:latest
root@cp:/go# docker images
REPOSITORY     TAG       IMAGE ID       CREATED          SIZE
test/test-go   latest    d6b30b26fa21   14 seconds ago   7.99MB
<none>         <none>    4ddee896749e   48 seconds ago   751MB

使用最终生成的运行环境镜像创建容器运行应用测试。

root@cp:/go# docker run -it test/test-go
Hello, Docker

六、注意事项

  • 精简镜像用途:尽量让每个镜像的用途都比较单一集中,避免构造大而复杂、多功能的镜像。
  • 选用合适的基础镜像:容器核心是应用,选择过大的基础镜像(centos、ubuntu)会导致生成的镜像臃肿。我们可以选择瘦身过的应用镜像,如node:slim;也可以选择小巧的系统镜像,如 alpinebusybox
  • 尽可能减少镜像层数:通常来说镜像层数越多,镜像越大。我们可以尽量合并 RUN、ADD、COPY 指令。通常情况下,多个 RUN 指令可以合并为一条 RUN 指令。
  • 根据实际情况正确使用镜像版本号:使用明确的版本号,而非依赖于默认的latest。通常镜像的版本号(TAG) 我们可以在 hub.docker.com 查询到。通过版本号我们可以尽可能避免环境不一致所导致的问题。
  • 恰当使用多步骤构建镜像,有助于精简镜像大小。
  • 合理使用.dockerignore文件,有助于避免发送不必要的数据。



参考:
  书籍:《Docker技术入门与实战》 作者: 杨保华 / 戴王剑 / 曹亚仑
  官方文档:https://docs.docker.com/engine/reference/builder/
  文章:https://blog.fundebug.com/2017/05/02/about-docker-sock/ 作者:Fundebug

发表评论

验证码: 3 + = 11