news 2026/10/1 2:05:11

devops-exercises 容器实战:多阶段构建(Multi-Stage Builds)改造指南与镜像瘦身原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
devops-exercises 容器实战:多阶段构建(Multi-Stage Builds)改造指南与镜像瘦身原理
  • 文档
  • 教程
  • DevOps
  • 运维

【免费下载链接】devops-exercises

Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP, DNS, Elastic, Network, Virtualization. DevOps Interview Questions

项目地址:https://gitcode.com/GitHub_Trending/de/devops-exercises
点击查看免费下载

本指南以 topics/containers/solutions/multi_stage_builds.md 为核心,完整还原"将单阶段 Dockerfile 改造为多阶段构建"的经典练习与官方解答,并结合仓库内容器专题资料,深入剖析多阶段构建的工作原理、层(layer)机制与收益。读完本文,你将能够独立把"构建工具 + 运行产物"混杂在一起的 Dockerfile 拆分为构建阶段与运行阶段,产出体积更小、更安全的容器镜像。

一、练习目标与背景

本节练习的 Objective 只有一句话:Learn about multi-stage builds(学习多阶段构建)。它属于 topics/containers/README.md 中"Exercises → Misc"练习列表的一部分(Multi-Stage Builds条目),题目要求明确:

不实际构建镜像、不运行任何容器,仅凭下面的 Dockerfile,将其转换为使用多阶段构建的形式。

"不构建、不运行"意味着这是一道纸面推演题:考察的是你对 Dockerfile 指令语义、镜像分层模型以及多阶段构建(COPY --from)语法的理解,而不是命令行操作能力。

二、原题还原:单阶段 Dockerfile 的问题所在

练习给出的原始 Dockerfile 如下(仓库原文,保留原样):

FROM nginx RUN apt-get update \ && apt-get install -y curl python build-essential \ && apt-get install -y nodejs \ && apt-get clean -y RUN mkdir -p /my_app ADD ./config/nginx/docker.conf /etc/nginx/nginx.conf ADD ./config/nginx/k8s.conf /etc/nginx/nginx.conf.k8s ADD app/ /my_cool_app WORKDIR /my_cool_app RUN npm install -g ember-cli RUN npm install -g bower RUN apt-get update && apt-get install -y git \ && npm install \ && bower install \ RUN ember build — environment=prod CMD [ “/root/nginx-app.sh”, “nginx”, “-g”, “daemon off;” ]

在动手改造之前,先拆解这个 Dockerfile 暴露出的核心问题:

  1. 基础镜像职责混乱:FROM nginx本意是提供一个 Web 服务器运行环境,但紧接着就在同一个镜像里安装了python、build-essential、nodejs、git等构建期工具。这些工具对最终运行 nginx + 静态页面毫无作用,只会让镜像体积成倍膨胀。
  2. 构建工具链被永久打包进运行时镜像:npm install -g ember-cli、npm install -g bower、npm install、bower install、ember build这一整套前端构建链路,只需要在"产出 dist 静态文件"的那一刻存在。把它们留在最终镜像里,既是体积浪费,也扩大了攻击面(镜像里多一个包管理器,就多一份被利用的风险)。
  3. 层数过多、难以维护:每次RUN、ADD都会生成一个新的镜像层(可参考 topics/containers/solutions/image_layers.md 中"FROM、RUN → 新层;EXPOSE、ENV、WORKDIR → 元数据"的结论)。该 Dockerfile 有十余条指令,其中大半都与最终运行无关。
  4. 原文存在笔误,不可直接构建:ember build — environment=prod中的—是全角破折号,应为--environment=prod;CMD [ “...”, ... ]使用了全角引号,应替换为 ASCII 引号。这也是题目要求"不实际构建"的原因之一——直接拿原文去docker image build会失败。

从仓库的 topics/containers/README.md 对多阶段构建的解释可以确认这一点:

Multi-stages builds allow you to produce smaller container images by splitting the build process into multiple stages. ... the whole build process of the application might be using packages and libraries you don't really need for running the application later. Moreover, the build process might produce different artifacts which not all are needed for running the application.

三、标准解法:把构建阶段与运行阶段彻底分离

练习给出的参考解答(仓库原文)如下:

FROM node:6 RUN mkdir -p /my_cool_app RUN npm install -g ember-cli RUN npm install -g bower WORKDIR /my_cool_app RUN npm install ADD app/ /my_cool_app RUN bower install RUN ember build — environment=prod FROM nginx RUN mkdir -p /my_cool_app ADD ./config/nginx/docker.conf /etc/nginx/nginx.conf ADD ./config/nginx/k8s.conf /etc/nginx/nginx.conf.k8s # Copy build artifacts from the first stage COPY — from=0 /my_cool_app/dist /my_cool_app/dist WORKDIR /my_cool_app CMD [ “/root/nginx-app.sh”, “nginx”, “-g”, “daemon off;” ]

改造后的 Dockerfile 由两个阶段组成,中间以第二个FROM nginx为分界:

阶段基础镜像职责最终产物
阶段 0(构建阶段)node:6安装 ember-cli / bower,拉取应用源码,执行npm install、bower install、ember build/my_cool_app/dist静态构建产物
阶段 1(运行阶段)nginx放置 nginx 配置,接收阶段 0 的构建产物,作为 Web 服务器运行最终交付的镜像

核心衔接语句是COPY --from=0 /my_cool_app/dist /my_cool_app/dist:它把第一个阶段(索引 0)构建出的dist目录复制进第二个阶段。从第二个FROM开始,前一个阶段中的所有工具(node、npm、bower、ember-cli、build-essential、git 等)都被丢弃,最终镜像里只保留 nginx 运行环境 + 配置文件 + 静态产物。

这里同样需要注意原文笔误:COPY — from=0 ...与ember build — environment=prod中的—应写为--,引号应使用 ASCII 引号。可直接运行的正确版本如下:

# Stage 0: build stage FROM node:6 RUN mkdir -p /my_cool_app RUN npm install -g ember-cli RUN npm install -g bower WORKDIR /my_cool_app ADD app/ /my_cool_app RUN npm install RUN bower install RUN ember build --environment=prod # Stage 1: runtime stage FROM nginx RUN mkdir -p /my_cool_app ADD ./config/nginx/docker.conf /etc/nginx/nginx.conf ADD ./config/nginx/k8s.conf /etc/nginx/nginx.conf.k8s # Copy build artifacts from the first stage COPY --from=0 /my_cool_app/dist /my_cool_app/dist WORKDIR /my_cool_app CMD ["/root/nginx-app.sh", "nginx", "-g", "daemon off;"]

四、核心语法深入:COPY --from 与阶段引用

多阶段构建的关键在于跨阶段复制产物,COPY --from是唯一的衔接手段。仓库解答采用COPY --from=0,即以阶段索引引用第一个阶段。除索引外,Dockerfile 还支持两种更可读的引用方式:

  • 按索引引用:COPY --from=0 ...,阶段按出现顺序从 0 编号,简单但可读性差——一旦前面插入新阶段,编号全部错位。
  • 按名称引用:在阶段前加AS别名,例如FROM node:6 AS builder,随后COPY --from=builder /my_cool_app/dist /my_cool_app/dist。这在生产 Dockerfile 中更常见,便于维护者快速理解产物来源。

可推断的进阶用法还包括:

  • --target定向构建:docker image build --target builder -t myapp:build .只构建到指定阶段为止,可用于只出构建产物而不出运行时镜像的 CI 流水线;
  • 仅构建阶段:多阶段文件在docker image build时,默认只产出最后一个阶段,但--target可以让中间阶段也可单独被构建与调试。

五、为什么多阶段构建能瘦身:镜像分层机制

要理解收益,需要先回顾镜像层模型。topics/containers/README.md 对此有系统描述:

  • 容器镜像是一个只读层(read-only layers)的集合,每个层由一条或多条 Dockerfile 指令产生,每个层有基于其内容的哈希 ID;
  • 每个层只是相对于前一层的差异集(a set of differences from the layer before it),层与层叠加在一起;
  • 创建容器时,会在底层镜像之上新增一个可写的容器层(container layer),所有运行时写入都落在这个薄层上,容器删除后该层随之消失,底层镜像保持不变;
  • 多个容器可以共享同一个底层镜像,各自拥有独立的可写层与数据状态。

把这一模型套到单阶段 Dockerfile 上:apt-get install留下的包管理器缓存、node/npm/bower/ember-cli 二进制、git 等全部固化成永久层,无论运行阶段是否需要,都会被docker pull传输、被docker image ls占用的磁盘空间记录下来。

多阶段构建的瘦身逻辑正是"分层模型 + 阶段裁剪"的组合拳:

  1. 构建阶段的每一条指令仍然产生层,但只存在于构建过程的临时镜像中;
  2. 进入第二个FROM后,构建阶段的一切层都被丢弃,只保留COPY --from显式复制出来的文件;
  3. 最终镜像只有运行阶段(nginx 基础层 + 配置层 + 复制产物层)的少量层。

仓库解答对此的总结非常精炼:

Multi-stages builds allow you to produce smaller container images by splitting the build process into multiple stages as we did above. The app image doesn't contain anything related to the build process except the actual app.

也就是说,最终的应用镜像里除了应用本身(dist 静态产物),不再包含任何与构建过程相关的东西。

六、多阶段构建 vs 手动清理:为什么前者是更优解

针对"构建期工具残留"问题,有人会想到在单阶段里追加清理指令,例如安装完再apt-get purge、npm cache clean --force。仓库 README 明确指出了这条路的两个缺陷:

  1. 你必须精确知道该删什么(You need to know what to remove exactly and that might be not as straightforward as you think)——依赖的传递依赖、缓存目录、临时文件散布在各处,很难删干净;
  2. 清理动作本身又会产生新的层(You add new layers which are not really needed)——清理指令照样写入镜像,等于为"瘦身"额外支付了层数成本。

因此更优的做法是:一个阶段负责构建(build process),把相关产物/输出传递给运行应用的阶段(one stage is passing the relevant artifacts/outputs to the stage that runs the application)。这与仓库在 topics/containers/README.md 中"True or False? In multi-stage builds, artifacts can be copied between stages → True. This allows us to eventually produce smaller images."的判断完全一致:阶段间复制产物,最终得到更小的镜像。

七、验证与对比:如何证明镜像确实变小了

练习虽然要求"不构建、不运行",但改造完成后,你可以用仓库其他练习中提到的手段做可验证的对比实验(参考 topics/containers/solutions/image_layers.md 的验证思路):

# 1. 分别构建单阶段版本与多阶段版本(注意用修正后的语法) docker image build -f Dockerfile.single -t myapp:single . docker image build -f Dockerfile.multi -t myapp:multi . # 2. 对比镜像体积(多阶段版本应显著更小) docker image ls | grep myapp # 3. 查看每个阶段/指令产生的层及其大小 docker image history myapp:multi # 4. 用 inspect 核对镜像层数量(RootFS.Layers) docker image inspect myapp:multi

对应 Podman 引擎的命令为podman image build、podman image history、podman image inspect,仓库 topics/containers/README.md 中多处提到 Docker 与 Podman 命令可互换使用。一个经验性判断标准(来自仓库)是:docker image history中通常创建新层的指令具有非零大小,不过不能单独依赖这一点(例如某些RUN ls -l结果大小可能为 0),需要结合inspect的 RootFS 层数交叉验证。

八、落地实操与配套最佳实践

把多阶段构建用到真实的"容器化应用"流程中,可以结合仓库另一练习 topics/devops/solutions/containerize_app.md 给出的完整链路:

  1. git clone一个待容器化的开源项目;
  2. 编写 Dockerfile(可用任意基础镜像);
  3. docker image build -t web_app:latest .构建;
  4. docker image ls验证镜像存在;
  5. (可选)docker login后docker image tag/docker image push推送到 registry;
  6. docker container run -d -p 80:3000 web_app:latest运行;
  7. docker container ls、docker logs <容器ID/名称>验证应用在线。

将多阶段改造嵌入该流程时,建议一并遵循仓库 README 中列出的 Containerfile/Dockerfile 最佳实践:

  • FROM 指定 tag:不写 tag 意味着永远拉取latest,可能随时间变化产生意外结果;
  • 只包含将要使用的包:Keep images small!(仅包含应用运行所需内容);
  • 用&&合并 RUN 指令:减少层数(见 image_layers.md 中把多条dd合并为一条RUN ... && ...的做法);
  • 安装后清理缓存:如apt-get clean;
  • 使用.dockerignore:默认情况下构建上下文会包含目录下所有文件,.dockerignore用于排除无关文件,避免把源码、密钥等带入镜像;
  • 不把敏感信息写入环境变量。

九、总结

回到练习的两个问题,可以这样作答:

  1. 如何改造:将 Dockerfile 拆为两个阶段——FROM node:6的构建阶段负责安装构建工具、执行依赖安装与ember build,产出/my_cool_app/dist;FROM nginx的运行阶段只负责放置 nginx 配置,并通过COPY --from=0 /my_cool_app/dist /my_cool_app/dist接收构建产物,最后以CMD启动 nginx。注意原文中的—与全角引号需修正为--与 ASCII 引号才可实际构建。
  2. 收益是什么:多阶段构建通过拆分构建流程,让最终镜像只包含应用运行所需内容,不携带任何构建期工具链,从而显著减小镜像体积、降低层数、缩小攻击面;相比"安装后再手动清理"的方案,它不需要精确枚举待删除文件,也不会为清理动作额外增加层。这正是 topics/containers/README.md 中"artifacts can be copied between stages → smaller images"结论的实践落点。

如需继续练习,可回到 multi_stage_builds.md 原题 独立作答,再用本解答对照;也可以继续完成 topics/containers/ 下working_with_images.md、image_layers.md、containerized_web_server.md等系列练习,从镜像操作、分层模型到完整应用容器化,系统掌握容器构建全链路。

  • 文档
  • 教程
  • DevOps
  • 运维

【免费下载链接】devops-exercises

Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP, DNS, Elastic, Network, Virtualization. DevOps Interview Questions

项目地址:https://gitcode.com/GitHub_Trending/de/devops-exercises
点击查看免费下载

相关推荐

上一篇:如何在5分钟内创建专业图表:免费在线流程图编辑器的完整指南
下一篇:OpenProject 手把手教程:快速跑起来项目管理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 2:03:00

Syncthing 中继实战:strelaysrv 私有部署、连通性验证与排错清单

Syncthing 中继实战&#xff1a;strelaysrv 私有部署、连通性验证与排错清单 【免费下载链接】syncthing Open Source Continuous File Synchronization 项目地址: https://gitcode.com/GitHub_Trending/sy/syncthing 两台设备都在 NAT 后面&#xff0c;UDP hole-punch …

作者头像 李华
网站建设 2026/10/1 1:59:32

基于YOLOv8的非机动车闯红灯识别系统实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:58:43

马德拉岛旅行全攻略:徒步、美食、自驾与避坑指南

第一次降落在这座被旅行者称作“大西洋珍珠”的小岛上时&#xff0c;我心里只有一个念头&#xff1a;这地方怎么一开始没被人发现&#xff1f;马德拉&#xff0c;距离里斯本大约一个半小时航班&#xff0c;藏在北大西洋深处&#xff0c;却同时拥有悬崖、云海、月桂林、火山岩池…

作者头像 李华
网站建设 2026/10/1 1:58:42

交通违规目标检测数据集实战指南:时空耦合与法规嵌入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华