当前位置: 首页 > news >正文

构建更快,部署更智能:立即优化您的 Docker 设置

您准备好提升您的云和 DevOps 技能了吗?
🐥《云原生devops》专门为您打造,我们精心打造的 30 篇文章库,这些文章涵盖了 Azure、AWS 和 DevOps 方法论的众多重要主题。无论您是希望精进专业知识的资深专业人士,还是渴望学习相关知识的新手,这套资源库都能满足您的需求。

如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
————————————————

Docker 是一款强大的容器化工具,但构建速度缓慢可能会影响开发周期。以下是一些可操作的技巧和步骤,可帮助您优化 Docker 构建速度。

1. 使用多阶段构建


描述:多阶段构建允许您在 Dockerfile 中使用多个 FROM 语句。这样,您可以避免在最终镜像中包含不必要的文件,从而减小镜像大小并加快构建速度。


示例:

FROM eclipse-temurin:21.0.2_13-jdk-jammy
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]

没有使用多阶段的Dockerfile,构建后的镜像大小为540M。

ninjamac@ninjamacdeMacBook-Air spring-boot-docker % docker images
REPOSITORY                                                          TAG                               IMAGE ID       CREATED              SIZE
spring-helloworld                                                   latest                            538dd687fed5   16 minutes ago       540MB

FROM eclipse-temurin:21.0.2_13-jdk-jammy AS builder
WORKDIR /opt/app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY ./src ./src
RUN ./mvnw clean installFROM eclipse-temurin:21.0.2_13-jre-jammy AS final
WORKDIR /opt/app
EXPOSE 8080
COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar
ENTRYPOINT ["java", "-jar", "/opt/app/*.jar"]

使用多阶段Dockerfile构建的镜像大小只有290M。

ninjamac@ninjamacdeMacBook-Air spring-boot-docker % docker images
REPOSITORY                                                          TAG                               IMAGE ID       CREATED              SIZE
spring-helloworld-multistage                                        latest                            06fa85d3415a   About a minute ago   291MB
spring-helloworld                                                   latest                            538dd687fed5   16 minutes ago       540MB


2. 利用 Docker 缓存


说明:Docker 逐层构建镜像,并缓存每一层。您可以将不常更改的指令(例如安装依赖项)放在最顶层,从而充分利用这一点。
提示:始终将依赖项的 COPY 命令放在应用程序代码之前。
示例:
Dockerfile

FROM node:14
WORKDIR /app# Install dependencies first
COPY package.json package-lock.json ./
RUN npm install# Then copy the application code
COPY . .CMD ["npm", "start"]


3. 最小化层数


说明:Dockerfile 中的每个命令都会创建一个新的层。通过将命令分组来最小化层数。
提示:使用 && 组合 RUN 命令。
示例:
Dockerfile

FROM ubuntu:latest
RUN apt-get update && \apt-get install -y git curl && \rm -rf /var/lib/apt/lists/*


4. 有效使用 .dockerignore


描述:与 .gitignore 类似,.dockerignore 文件会告知 Docker 在构建上下文中应忽略哪些文件/文件夹,从而减少发送到 Docker 守护进程的不必要数据。
示例:创建如下 .dockerignore 文件:
 

node_modules
*.log
*.tmp
.git

5. 减小镜像大小


描述:镜像越小,构建和推送所需的时间就越短。请尽可能使用精简的基础镜像(例如 Alpine)。
示例:
Dockerfile
 

FROM alpine:latest
RUN apk add --no-cache python3


6. 优化软件包安装


描述:尽量减少安装的软件包数量,并使用支持缓存或更快安装的软件包管理器。
提示:安装软件包时请使用特定版本,以避免在构建过程中重复下载。
示例:
Dockerfile
 

RUN apt-get update && apt-get install -y --no-install-recommends git=1:2.25.1-1ubuntu3

7. 使用 Docker BuildKit 缓存依赖项


描述:启用 BuildKit 以获得更好的缓存和构建性能。
命令:
 

DOCKER_BUILDKIT=1 docker build -t myapp .


8. 使用构建参数


描述:使用构建参数自定义构建,这有助于避免重复下载。
示例:
Dockerfile
 

ARG NODE_VERSION=14
FROM node:${NODE_VERSION}

9. 创建可复用阶段


如果您有多个镜像,并且它们有很多共同点,请考虑创建一个包含共享组件的可复用阶段,并以此为基础创建您的专属阶段。Docker 只需构建一次通用阶段。这意味着您的衍生镜像可以更高效地利用 Docker 主机上的内存,并更快地加载。

维护一个通用的基础阶段(“不要重复自己”)也比使用多个不同的阶段执行类似的操作更容易。

10.解耦应用程序


每个容器应该只关注一个问题。将应用程序解耦到多个容器中,可以更轻松地进行水平扩展和容器复用。例如,一个 Web 应用程序堆栈可能由三个独立的容器组成,每个容器都有自己独特的镜像,用于以解耦的方式管理 Web 应用程序、数据库和内存缓存。

将每个容器限制为一个进程是一个不错的经验法则,但并非一成不变。例如,不仅可以通过 init 进程生成容器,某些程序还可以自行生成其他进程。例如,Celery 可以生成多个工作进程,而 Apache 可以为每个请求创建一个进程。

请尽最大努力保持容器的简洁和模块化。如果容器相互依赖,您可以使用 Docker 容器网络来确保这些容器能够通信。

11. Dockerfile 的最佳实践

RUN

pipeline


某些 RUN 命令依赖于使用管道符 (|) 将一个命令的输出通过管道传输到另一个命令的能力,如下例所示:

RUN wget -O - https://some.site | wc -l > /number


Docker 使用 /bin/sh -c 解释器执行这些命令,该解释器仅评估管道中最后一个操作的退出代码来确定是否成功。在上面的示例中,只要 wc -l 命令成功,即使 wget 命令失败,此构建步骤也会成功并生成新的镜像。

如果您希望该命令因管道中任何阶段的错误而失败,请在命令前面添加 set -o pipefail && ,以确保意外错误会阻止构建意外成功。例如:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

注意

并非所有 Shell 都支持 -o pipefail 选项。

对于基于 Debian 的镜像上的 dash shell 等情况,请考虑使用 RUN 的 exec 形式明确选择支持 pipefail 选项的 shell。例如:

 

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

apt-get


在基于 Debian 的镜像中,RUN 指令的一个常见用例是使用 apt-get 安装软件。由于 apt-get 会安装软件包,因此 RUN apt-get 命令存在一些需要注意的违反直觉的行为。

请务必在同一个 RUN 语句中结合使用 RUN apt-get update 和 apt-get install。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
package-bar \
package-baz \
package-foo


在 RUN 语句中单独使用 apt-get update 会导致缓存问题,并导致后续的 apt-get install 指令失败。例如,以下 Dockerfile 中会出现此问题:

# syntax=docker/dockerfile:1FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl


构建镜像后,所有层都位于 Docker 缓存中。假设您稍后修改 apt-get install 命令,添加一个额外的软件包,如下 Dockerfile 所示:

# syntax=docker/dockerfile:1FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl nginx


Docker 会将初始指令和修改后的指令视为相同,并重用之前步骤的缓存。因此,apt-get update 命令不会执行,因为构建使用的是缓存版本。由于 apt-get update 命令未运行,您的构建可能会获得过时版本的 curl 和 nginx 软件包。

使用 RUN apt-get update && apt-get install -y --no-install-recommends 可确保您的 Dockerfile 安装最新的软件包版本,无需进一步编码或手动干预。此技术称为缓存清除。您也可以通过指定软件包版本来实现缓存清除。这称为版本锁定。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
package-bar \
package-baz \
package-foo=1.3.*


版本锁定会强制构建程序检索特定版本,而不管缓存中的内容。此技术还可以减少由于所需软件包发生意外更改而导致的故障。

以下是一条格式正确的 RUN 指令,演示了所有 apt-get 建议。

RUN apt-get update && apt-get install -y --no-install-recommends \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/* 

FROM


尽可能使用最新的官方镜像作为镜像的基础。Docker 推荐使用 Alpine 镜像,因为它控制严格且体积小巧(目前不到 6 MB),同时仍然是一个完整的 Linux 发行版。

LABEL


您可以为镜像添加标签,以便按项目组织镜像、记录许可信息、辅助自动化或用于其他目的。对于每个标签,添加一行以 LABEL 开头的文本,其中包含一个或多个键值对。以下示例展示了不同的可接受格式。解释性注释已内联。

带有空格的字符串必须用引号引起来或进行转义。内部引号 (") 也必须转义。例如:

# 设置一个或多个单独的标签

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""


一个镜像可以有多个标签。在 Docker 1.10 之前,建议将所有标签合并到一个 LABEL 指令中,以防止创建额外的层。现在不再需要这样做,但仍然支持合并标签。例如:

# 在一行上设置多个标签

LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

以上示例也可以写成:

# 一次设置多个标签,使用行继续符来断开长标签行

LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"

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,随时可以运行。除非您和您的预期用户已经非常熟悉 ENTRYPOINT 的工作方式,否则 CMD 很少应以 CMD ["param", "param"] 的方式与 ENTRYPOINT 结合使用。

ENV


为了使新软件更容易运行,您可以使用 ENV 更新容器所安装软件的 PATH 环境变量。例如,ENV PATH=/usr/local/nginx/bin:$PATH 可确保 CMD ["nginx"] 正常运行。

ENV 指令还可用于提供特定于您要容器化的服务(例如 Postgres 的 PGDATA)所需的环境变量。

最后,ENV 还可用于设置常用的版本号,以便更轻松地维护版本升级,如下例所示:

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


与在程序中使用常量变量(而非硬编码值)类似,这种方法允许您更改单个 ENV 指令,以自动升级容器中软件的版本。

每行 ENV 指令都会创建一个新的中间层,就像 RUN 命令一样。这意味着即使您在后续层中取消设置环境变量,它仍然会保留在此层中,并且其值可以被转储。您可以通过创建如下所示的 Dockerfile 并进行构建来测试这一点。

# syntax=docker/dockerfile:1
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USERdocker run --rm test sh -c 'echo $ADMIN_USER'mark


为了防止这种情况,并真正地取消设置环境变量,请在 shell 命令中使用 RUN 命令,在同一个层级中设置、使用和取消设置变量。您可以使用 ; 或 && 分隔命令。如果您使用第二种方法,并且其中一个命令失败,docker 构建也会失败。这通常是一个好主意。在 Linux Dockerfile 中使用 \ 作为行继续符可以提高可读性。您也可以将所有命令放入一个 shell 脚本中,并让 RUN 命令直接运行该 shell 脚本。

# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
&& echo $ADMIN_USER > ./mark \
&& unset ADMIN_USER
CMD shdocker run --rm test sh -c 'echo $ADMIN_USER'

 

USER


如果服务无需特权即可运行,请使用 USER 切换到非 root 用户。首先,在 Dockerfile 中创建用户和组,例如:

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres


注意

请考虑使用明确的 UID/GID。

镜像中的用户和组会被分配一个非确定性的 UID/GID,因为无论镜像是否重建,都会分配“下一个”UID/GID。因此,如果情况紧急,您应该分配一个明确的 UID/GID。

注意

由于 Go 归档/tar 包在处理稀疏文件时存在一个尚未解决的 bug,尝试在 Docker 容器内创建具有较大 UID 的用户可能会导致磁盘空间耗尽,因为容器层中的 /var/log/faillog 文件会被 NULL (\0) 字符填充。一种解决方法是将 --no-log-init 标志传递给 useradd。Debian/Ubuntu adduser 包装器不支持此标志。

避免安装或使用 sudo,因为它具有不可预测的 TTY 和信号转发行为,可能会导致问题。如果您确实需要类似于 sudo 的功能,例如以 root 身份初始化守护进程但以非 root 身份运行它,请考虑使用“gosu”。

最后,为了减少层级和复杂性,请避免频繁来回切换 USER 角色。

总结

本文探讨了 Docker 多阶段构建的强大功能,该功能可帮助开发者优化镜像大小并提高构建效率。多阶段构建支持构建环境和运行时环境的分离,允许在一个阶段编译应用程序,并在另一个阶段将其打包成最终镜像。这不仅可以通过排除不必要的构建依赖项来减小镜像整体大小,还能增强最终镜像的安全性。

本文深入探讨了有效的缓存设置,解释了 Docker 的缓存机制如何显著加快构建过程。通过策略性地利用层和缓存清除,开发者可以最大限度地缩短重建时间并优化工作流程。

此外,本文还提供了使用多阶段构建的实用技巧,例如利用 .dockerignore 文件从构建上下文中排除不必要的文件,高效组织 Dockerfile 指令以最大化层缓存,以及保持构建过程的清晰度以便于调试和维护。

总的来说,本文为希望充分利用 Docker 多阶段构建潜力的开发人员提供了全面的指南,并提供了可行的策略来增强他们的容器化流程。

相关文章:

  • 每天学一个 Linux 命令(34):wc
  • 组件通信-provide、inject
  • whl文件名后缀
  • 传奇各职业/战士/法师/道士/戒指爆率及出处产出地/圣战/法神/天尊/虹魔/魔血/麻痹/超负载/求婚/隐身/传送/复活/护身/祈祷/火焰
  • PyQt 或 PySide6 进行 GUI 开发文档与教程
  • 电商平台的订单状态设计流程
  • NHANES指标推荐:TyG指数
  • 计算机启动流程中,都干了啥事。比如文件挂在,操作系统加载,中断向量表加载,磁盘初始化在哪阶段。
  • K8S - 深入解析 Service 与 Ingress - 服务暴露与流量管理
  • 在多socket多核计算机上配置MPI和OpenMP
  • Protubuf入门 --- 01基本语法与编译使用
  • C语言数据类型与内存布局
  • 算法竞赛进阶指南.巡逻
  • 13分区排烟 无法远程启动 12-1-4,排烟管道出口未连接室外
  • Linux-07-Shell
  • python常用科学计算库及使用示例
  • 数字智慧方案6185丨智慧银行解决方案(51页PPT)(文末有下载方式)
  • 【免费】2010-2019年上市公司排污费数据
  • 迪米特法则(LoD)
  • Baklib内容中台落地实战指南
  • 五年来首次!香港金管局斥资465.39亿港元购买美元
  • 抢抓消费旺季:五一假期,多地党政主官调研外贸优品展销活动
  • 辽宁男篮被横扫这一晚,中国篮球的一个时代落幕了
  • 高速变道致连环车祸,白车“骑”隔离栏压住另一车,交警回应
  • 《水饺皇后》领跑五一档票房,《哪吒2》上座率仍居第一
  • 案件发回重审,李在明参选韩总统之路再添波折