🍂落页
登 录

《Docker 实战》笔记

  • 速查表
  • 欢迎来到 Docker 的世界
  • 在容器中运行软件
  • 使用 Docker 安装软件
  • 使用存储和卷
  • 单主机网络
  • 通过资源控制来限制风险
  • 将软件打包到镜像中
  • 用 Dockerfile 构建镜像
🍂落页
TALAXY
用 Dockerfile 构建镜像
🍂落页
TALAXY

用 Dockerfile 构建镜像

用 Dockerfile 构建镜像
  • 构建过程
  • 语法及常用指令
  • 在构建下层镜像时注入行为
  • 多阶段构建
  • 初始化进程
  • 健康检查
  • 镜像防御
  • 参考

Dockerfile 是文本文件,由构建镜像的指令组成。Docker 会按照从上到下的顺序执行 Dockerfile 中的指令来构建镜像。

以下是一个打包 Git 工具的 Dockerfile 示例:

FROM ubuntu:latest
LABEL maintainer="dia@allingeek.com"
RUN apt-get update && apt-get install -y git
ENTRYPOINT ["git"]
  • FROM 指令用于指定源镜像。在 Dockerfile 中,第一条指令必须是 FROM 。如果是从空的镜像开始,则可以指定一个名为 scratch 的特殊仓库;
  • LABEL 指令用于给镜像添加元数据。比如例子中添加了作者信息;
  • RUN 指令用于直接执行命令。例子中执行了 apt-get 来安装 Git ;
  • ENTRYPOINT 指令用来设置镜像的入口点。

可以通过 docker image build 来通过 Dockerfile 构建镜像:

# 最后面的 `.` 代表从当前目录查找 Dockerfile 文件
docker image build --tag ubuntu-git:auto .
  • --tag 或 -t 用于指定构建镜像的完整仓库名称;
  • Docker 默认会寻找名为 Dockerfile 的文件来构建镜像,也可以通过 -f 选项来指定 Dockerfile 的文件名。

构建过程

通过 Dockerfile 构建镜像与手动构建镜像类似,Dockerfile 中的每条指令都会创建一个新的容器,提交相应修改。在执行 docker build 后,Docker 会在控制台逐层打印构建任务信息。可以通过 --quiet 或 -q 来启用安静模式,该模式会抑制构建过程中(在控制台)的日志输出,但最终依然会输出一个镜像 ID 。

同时,Docker 会逐层缓存构建结果,当在重新执行 Dockerfile 构建时,Docker 会尝试使用历史的缓存结果。Docker 也提供了 --no-cache 选项来禁用缓存,但非必要时不应当使用该选项。

为了利用好缓存,在编写 Dockerfile 时应当注意指令间顺序。同时,一些 Dockerfile 指令支持传入多个参数,可以避免相同指令的多次使用:

ENV APPROOT="/app"
ENV APP="mailer.sh"

# 可通过一条指令直接达到目的
ENV APPROOT="/app" APP="mailer.sh"

构建完镜像后,可以通过 docker inspect 来查看镜像的元数据。

语法及常用指令

与大多数脚本一样,在 Dockerfile 可以通过 # 来写注释,也可以通过反斜杠 \ 来对单条语句换行。以下是常用的 Dockerfile 指令

  • FROM - 指定构建的源镜像
  • LABEL - 指定镜像的元数据
  • ENV - 指定环境变量
  • USER - 设置用户及 group ID
  • WORKDIR - 改变工作目录
  • EXPOSE - 设置暴露端口

运行命令相关指令:

  • RUN - 执行命令
  • ENTRYPOINT - 设置入口点(启动程序)
  • CMD - 设置默认命令

这三条指令支持两种语法:

  • INSTRUCTION ["executable", "param1", "param2"] - exec 形式
  • INSTRUCTION command param1 param2 - shell 形式

对于 exec 形式,传入的参数必须是个 JSON 数组格式,即字符串所用的引号必须是双引号。

在 exec 形式下,ENTRYPOINT 通常可以跟 CMD 搭配着用,ENTRYPOINT 用于指定一个固定的命令及参数,而 CMD 指定剩余一些容易发生变化的参数:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

修改文件系统相关指令:

  • COPY - 复制文件
  • VOLUME - 创建卷
  • ADD - 添加本地或远程文件

在构建下层镜像时注入行为

如果通过 Dockerfile 构建后的镜像会被作为源镜像进一步构建,可以通过 ONBUILD 指令来设置被构建时需要预先执行的指令:

ONBUILD COPY [".", "/var/myapp"]
ONBUILD RUN go build /var/myapp

当通过该 Dockerfile 构建镜像时,不会执行 ONBUILD 中的指令,而是会记录在镜像的元数据中。当构建后的镜像被进一步构建时,Docker 会先执行存在元数据中的 ONBUILD 的指令集合。

多阶段构建

在 Dockerfile 里可以使用多条 FROM 指令,来应用多个构建阶段。比如对于一个 Web 服务,可以分为「构建时」和「运行时」两个阶段:

FROM node:alpine AS builder
# ...
RUN yarn install --frozen-lockfile
# ...
RUN yarn build

FROM node-alpine AS runner
COPY --from=builder ...
# ...
CMD ['node', 'server.js']
  • AS 用于给阶段命名;
  • 在 COPY 中可以用 --from 来从目标构建阶段复制文件,参数值可以是阶段名称或索引。
  • 最终生成的镜像仅会包含 runner 阶段里复制的文件,而不会包含 builder 阶段中下载的依赖。

初始化进程

在容器中,如果应用程序创建了子进程,那么在应用程序退出时,这些子进程很有可能将变成僵尸进程(即仍在后台运行)。

创建容器时也可以使用 --init 选项来预先启动一个初始化进程,它会负责创建后续所有的子进程:

docker run -it --init alpine:3.6 nc -l -p 3000

Docker 背后会使用 tini 作为初始化进程工具。当容器退出时,初始化程序会结束所有的子进程,即解决了僵尸进程的问题。

健康检查

Docker 提供了健康检查的工具,来检测应用程序的运行状况。有两种方式来使用健康检查:

在 Dockerfile 中使用 HEALTHCHECK 指令:

# 检测 80 端口是否可用
HEALTHCHECK --interval=5s --retries=2 \
    CMD nc -vz -w 2 localhost 80 || exit 1
  • 默认情况下健康检查会每 30 秒运行一次,例子中指定为了 5 秒;
  • 如果健康检查失败(退出状态码非零),Docker 会试图重新执行命令。在该例子中,如果重新尝试次数超过 2 ,则会将容器标记为不健康。

也可以在创建容器时使用 --health-cmd 选项:

docker run --name=healthcheck_example -d \
    --health-cmd='nc -vz -w 2 localhost 80 || exit 1' \
    nginx:1.13-alpine

健康检查工具还支持以下选项:

  • timeout - 超时设置;
  • start_period - 容器启动时的宽限期。

镜像防御

作为镜像作者,通常很难预料镜像的所有使用场景。为了减少容器被攻击的可能,有几个防御措施可以考虑:

  • 尽可能减少镜像内的软件数量;
  • 通过 CAIID(内容可寻址镜像标识符)来作为基础镜像;
  • 设置合理的默认用户;
  • 删除具有 SUID 和 SGID 权限的可执行文件。

具体请见书 P182 。

参考

Dockerfile reference - docker.com

TALAXY 落于 2024年6月22日 。