Docker构建相关问题集锦


我们在日常使用 docker 的时候,很容易出现错误,而且很多错误即使你已经很熟悉 docker 了还是出错。我这里整理了,自己使用 docker 时候常见的需要注意的一些要点,希望之后多多留意。

Docker问题集锦


1. 镜像构建机制

主要说明容器镜像构建的流程

构建 docker 镜像的时候,需要 Dockerfile 文件和构建所需的上下文。而编译文件,可以来自 Git 仓库、压缩包和指定的配置文件。

# build命令使用说明
docker build [OPTIONS] PATH | URL | -
# Git仓库
$ docker build https://github.com/docker/context.git

# 压缩包
$ docker build - < context.tar.gz
$ docker build http://server/context.tar.gz
$ docker build -f ctx/Dockerfile http://server/context.tar.gz

# 配置文件
$ docker build .
$ docker build - < Dockerfile
$ wget http://server/Dockerfile | docker build -

当执行上述构建命令的时候,docker 客户的命令会:

  1. 把当前目录及子目录当做上下文,传递给 docker server
  2. 从当前目录开始找 Dockerfile 文件,不包括子目录
  3. 找到文件之后,检查文件的语法是否存在问题
  4. 依次执行 Dockerfile 文件中的指令,根据指令生成中间过度镜像

构建镜像所生成中间过度镜像会存储在本地,为之后的指令或构建作缓存。为了加快构建速度,注意需要减少传递给 docker server 的文件数量,最好将 Dockerfile 文件放在单独的空目录中。

# --no-cache: 不使用缓存,每条指令都重新生成镜像
# -f: 明确指定Dockerfile文件存放位置
# -t: 给生成的镜像打上标签,可以重复指定多个标签
$ docker build --no-cache=true -f /path/to/Dockerfile \
    -t image_name:image_version /path/to/build

可以使用 .dockerignore 来忽略构建时用不到的文件,之后再执行 COPY 命令的时候将排除不需要的文件。建议使用,拒绝所有文件,将需要的文件和目录进行添加。

**/*
!config
!requirements.txt
!app_backend

2. 环境变量设定

主要说明容器环境变量的设定和使用方法

Dockerfile 中,使用 ENV 指令来定义环境变量。环境变量有两种使用方式: $variable_name${variable_name} ,推荐使用后者。同时,ENV 指令支持部分 bash 语法。

  • ${variable:-word}
    • 如果variable不存在,则使用word;如果variable存在,则使用原字符串
  • ${varialbe:+word}
    • 如果variable存在,则使用word;如果variable不存在,则使用空字符串

以下指令都支持使用 ENV 指令定义的环境变量:

  • ADDCOPYENVEXPOSELABEL
  • USERWORKDIRVOLUMESTOPSIGNALONBUILD

我们在日常使用中,都是通过 ENV 指令来设定程序使用的配置文件以及系统使用的字符编码格式,切记不要将密码当做环境变量进行设定,因为这样很不安全。

# ENV中也是可以使用变量的
ENV LANG=en_US.UTF-8
ENV NEW_LANG=${LANG}

# 支持两种定义方式
# ENV <key> <value>
# ENV <key>=<value>

3. 容器执行指令

再次介绍容器主要执行指令的注意事项

在初次写 Dockerfile 的时候,常常会被如何使用 RUN/CMD/ENTRYPOINT 而感到烦恼,这里我总结一下这三者的用法和使用方式:

  1. RUN 指令
  • 使用说明:在镜像的构建过程中执行特定的命令并生成一个中间镜像
  • shell 格式:RUN command param1 param2
  • exec 格式:RUN ["executable", "param1", "param2"]
  1. CMD 指令
  • 使用说明:指定容器运行时的默认参数,如果出现多次以最后一次为准
  • shell 格式:CMD command param1 param2
  • exec 格式:CMD ["executable", "param1", "param2"]
  1. ENTRYPOINT 指令
  • 使用说明:指定镜像的执行程序,只有最后一条指令有效
  • shell 格式:ENTRYPOINT command param1 param2
  • exec 格式:ENTRYPOINT ... CMD ["param1", "param2"]
# CMD和ENTRYPOINT至少得使用一个
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

FROM ubuntu
ENTRYPOINT exec top -b
# [问题]
# 那么有了CMD后,为什么还要有ENTRYPOINT呢?
# 这种 <ENTRYPOINT> "<CMD>" 有什么好处么?

# [回答]
# 之前我们说过,跟在镜像名后面的是command值,运行时会替换CMD的默认值
# 这是因为当存在ENTRYPOINT后,CMD的内容将会作为参数传给ENTRYPOINT

使用 RUNCMD 指令来执行命令的时候,通常都提供 shell 格式和 exec 格式执行方式,它们的主要区别在于:

  1. shell 格式
  • shell格式的是在某个shell中行可执行文件,默认为/bin/sh -c
  • shell格式支持使用转义符来换行,默认为\字符
  • shell格式的可以进行变量替换,如echo $HOME
  1. exec 格式
  • exec格式的是直接执行可执行文件,也就是["/bin/bash", "-c", "echo hello"]
  • exec格式会被解析为json数组,所以使用双引号"而不是单引号'
  • exec格式因为不在shell中执行,不会进行变量替换

使用 docker build 构建镜像的时候,需要注意使用 < 符号的一些注意事项:

  1. docker build - < somefile
  • 以这种方式来构建镜像,则没有上下文
  • ADD只能使用远程文件URLCOPY不能使用
  1. docker build - < archive.tar.gz
  • 会在压缩包的根目录中寻找Dockerfile,压缩包的根目录当做上下文

4. 容器常用指令

再次介绍容器常用指令的注意事项

如果远程文件是需要登录才能访问的,应该使用 RUN wgetRUN curl,而不是直接使用 ADDCOPY 指令。使用 ADDCOPY 远程复制文件,会赋予文件 600 权限,并且 HTTP Last-Modified 的时间就是文件的最后修改时间。最后修改时间被改变,docker 不会认为文件被改变,docker 只会检查文件内容。

  1. ADD 指令
  • src 可以是文件、目录,也可以是文件URL地址
  • src 可以使用模糊匹配,类似于shell的匹配方式
  • src 可以指定多个但必须是在上下文目录和子目录中
  • src 可以是目录,复制的是目录下的所有内容,但不包括该目录
  • src 可以是被docker能够识别的压缩包,会被自动解压复制的
  • dest 可以是绝对路径,也可以是相对WORKDIR目录的相对路径
  • 需要注意的是,复制的所有文件的UIDGID都是0
  1. COPY 指令
  • 格式和使用方式都和ADD一致
  • 不过ADD是将上下文内的文件复制到镜像内,COPY是在镜像内的复制
  • 如果dest不存在,COPY指令会自动创建所有目录,包括子目录
# copy command
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/

# add command
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/

更改后续的 Dockerfile 指令中所使用的 shell。默认的 shell["bin/sh", "-c"]。可多次使用,每次都只改变后续指令。

  1. VOLUME 指令
  • 指定镜像内的目录为数据卷,可指定多个数据卷
  • 在容器运行的时候,docker会把镜像中的数据卷的内容复制到容器的数据卷中去
  • 如果在接下来的Dockerfile指令中,修改了数据卷中的内容,则修改无效
  1. USER 指令
  • 为接下来的Dockerfile指令指定用户
  • 收影响的指令有:RUNCMDENTRYPOINT
  1. WORKDIR 指令
  • 为接下来的Dockerfile指令指定当前工作目录,可多次使用
  • 如果使用的是相对路径,则相对的是上一个工作目录,类似shell中的cd命令
  • 收影响的指令有:RUNCMDENTRYPOINTCOPYADD
  1. ONBUILD 指令
  • 当以该镜像为基础镜像再次构建新的镜像时,会触发执行其中的指令
  • ONBUILD只会继承给子节点的镜像,不会再继承给孙子节点
[...]
# 在下一次以此镜像为base image的构建中,执行ADD . /app/src,将项目代目添加到新镜像中去
ONBUILD ADD . /app/src
# 并且build Python代码
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]
  1. HEALTHCHECK 指令
  • 在看别人写的 Dockerfile 时候,通常不会看到有些 HEALTHCHECK。这是因为无法判断我们检测的实际业务需求,但是这个检查确实非常有必要的。比如用来检查数据库连接是否通达,业务网址是否可以正常访问等。
  1. 使用方式
  • 通过在容器内运行命令来检查心跳 HEALTHCHECK [OPTION] CMD command
  • 取消从基础镜像继承来的心跳检测 HEALTHCHECK NONE
  • command可以是shell脚本,也可以是exec格式的json数组
  1. 可选参数
  • 检测间隔 --interval=DURATION 默认 30 秒
  • 超时时间 --timeout=DURATION 默认 30 秒
  • 重试次数 --retries=N 默认 3 次
  1. 返回状态
  • dockercommand的退出状态码来区分容器是否健康
  • 0:命令返回成功,容器健康
  • 1:命令返回失败,容器不健康
  • 2:保留状态码,不要使用
# 每5分钟检测本地网页是否可访问,超时设为3秒
# 可以使用 docker inspect 命令来查看健康状态
HEALTHCHECK --interval=5m --timeout=3s CMD curl -f http://localhost/ || exit 1
HEALTHCHECK --interval=5m --timeout=3s CMD ["curl", "-f", "http://localhost/"] || exit 1

5. 镜像构建技巧

分享一个比较冷门的 Dockerfile 的小技巧

当你要安装一个 binary 工具时(比如 jqyqkubectlhelmdocker 等等)可以考虑直接从它们的镜像里 COPY 过来,替代使用 wget/curl 下载安装的方式,比如:

COPY --from=docker:20.10.12-dind-rootless \
    /usr/local/bin/docker /usr/local/bin/docker

Docker问题集锦 - 镜像构建技巧


6. 构建跨平台镜像

分享一个比较有用的容器构建的小技巧

  • 开启特性
experimental=true
  • 启动交叉编译 - binfmt
$ docker run -it --rm --privileged tonistiigi/binfmt --install all

$ docker buildx create --name multi-platform \
    --use --platform linux/amd64,linux/arm64 \
    --driver docker-container
  • 启动交叉编译 - qemu
$ apt install qumu

$ docker buildx build –platform linux/amd64,linux/arm64 -t chrony .

文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录