
Podman--save-stages构建选项详解保留中间阶段镜像、复用构建缓存与阶段标签【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman本篇技术指南围绕 Podman 的--save-stages构建选项展开介绍它在多阶段构建中保留中间阶段镜像的行为、与--layers搭配实现后续构建缓存复用、以及与--stage-labels组合为各阶段镜像打上元数据标签的实战方案。读完本文你将掌握如何用podman build --save-stages调试多阶段构建、加速重复构建并理解该选项在 Buildah 执行器中的底层实现原理。一、--save-stages是什么--save-stages是podman build与podman farm build共用的一个布尔选项用于保留多阶段构建过程中的中间阶段镜像而不是在构建完成后将其删除。默认值为false。该选项的权威定义位于仓库的 save-stages.md其头部注释明确声明该选项文件同时被 podman-build.1.md.in 与 podman-farm-build.1.md.in 两个命令手册复用#### This option file is used in: #### podman build, farm build这意味着--save-stages既适用于本地单机构建podman build也适用于 Podman Farm 的多机并行构建podman farm build两处手册共享同一份选项说明。1.1 默认行为为什么中间镜像会被删除默认情况下构建引擎Buildah会在构建完成后删除中间阶段镜像以节省磁盘空间。例如一个经典的多阶段构建FROM golang:1.22 AS builder WORKDIR /src COPY . . RUN go build -o /app/myapp . FROM alpine:3.20 COPY --frombuilder /app/myapp /usr/local/bin/myapp ENTRYPOINT [myapp]在这个例子中builder阶段编译出的镜像在默认构建流程结束后会被清理只有最终基于alpine的镜像会被保留。这是大多数场景下的合理取舍——中间镜像只服务于构建过程不会对外交付。1.2 开启后的行为加上--save-stages之后podman build --save-stages -t myapp .builder阶段的镜像会被完整保留在本地镜像存储中与最终镜像一同存在。这些保留的镜像有两个典型用途调试多阶段构建当某一阶段失败或最终产物异常时可以直接基于中间阶段镜像启动容器排查例如podman run -it --rm builder阶段镜像ID sh检查编译环境、依赖版本或中间产物无需重新构建或手工模拟阶段环境。复用中间阶段后续构建可以直接引用这些已存在的阶段镜像配合--layers利用缓存避免重复执行代价高昂的阶段如依赖下载、编译。二、与--layers的组合中间层作为构建缓存--save-stages可以与--layers同时使用且后续带--layers的构建可以复用这些被保留的中间阶段镜像作为缓存# 第一次构建保存所有中间阶段镜像 podman build --save-stages --layers -t myapp . # 第二次构建--layers 会复用第一次构建保留的中间层缓存 podman build --layers -t myapp .其底层逻辑可以追溯到 stage_executor.go 中的提交决策逻辑// This is the last instruction for this stage, so we // should commit this container to create an image, but // only if its the last stage, if its used as the // basis for a later stage, if we are just always // saving stages due to --save-stages having been // specified, or if we need to use it for generating // custom build outputs. if lastStage || imageIsUsedLater || s.executor.saveStages { ... imgID, commitResults, err s.commit(ctx, createdBy, emptyLayer, s.output, s.executor.squash, lastStage lastInstruction)从这段源码可以看到一个阶段结束后是否被提交成镜像取决于四个条件中的任意一个成立条件含义lastStage当前阶段是最后一个阶段最终镜像必然提交imageIsUsedLater当前阶段的镜像被后续阶段通过COPY --from/FROM引用s.executor.saveStages用户显式传入--save-stages自定义 build output需要阶段镜像用于生成自定义构建产物也就是说即使某个阶段既不是最终阶段、也没有被后续阶段引用例如调试用或准备用的独立阶段只要开启--save-stages它也会被提交保存。这解释了为什么该选项能保留按默认规则本会被丢弃的中间镜像。此外executor.go 中还存在!b.layers !b.saveStages !r.OnlyBaseImage的组合判断表明中间镜像的清理与否与--layers层缓存及 base 镜像处理逻辑相互关联三者共同决定了中间产物的去留。三、与--stage-labels的组合为阶段镜像添加元数据--save-stages可以与--stage-labels一起使用。当两者同时开启时所有中间阶段镜像以及最终镜像都会带上描述性元数据标签便于识别、查询和管理多阶段构建产生的镜像。相关说明详见 stage-labels.md。3.1 标签内容启用后每个阶段镜像会被打上两个标签io.buildah.stage.name阶段别名来自FROM ... AS alias若该阶段未声明别名则使用阶段序号io.buildah.stage.base该阶段使用的基础镜像镜像 pullspec或当阶段以另一个阶段为基础时使用该基础阶段输出镜像的镜像 ID。3.2 前置依赖--stage-labels强制要求--save-stages注意--stage-labels不能脱离--save-stages单独使用。这一约束在 Podman 与 Buildah 两层 CLI 中都有校验在 cmd/podman/common/build.go 中if buildOpts.StageLabels !buildOpts.SaveStages { return nil, errors.New(--stage-labels requires --save-stages) }在 Buildah 侧 vendor/go.podman.io/buildah/pkg/cli/build.go 中也有完全相同的校验if iopts.StageLabels !iopts.SaveStages { return options, nil, nil, errors.New(--stage-labels requires --save-stages) }因此错误地在未开启--save-stages时单独使用--stage-labels构建会直接报错并终止。3.3 底层实现标签是如何注入的在 Buildah 的镜像构建执行器 executor.go 中每个阶段开始执行前会判断是否注入标签指令// Create stage labels for all stage images including final stage // Skip if stage has no instructions if b.saveStages b.stageLabels len(stage.Node.Children) 0 { // Wait for base stage if it references a previous stage if isStage, err : b.waitForStage(ctx, base, stages[:stageIndex]); isStage err ! nil { return , nil, false, fmt.Errorf(waiting for base stage %s: %w, base, err) } labelLine : b.buildStageLabelLine(stage, base, stages[:stageIndex]) prependInstructions slices.Concat([]string{labelLine}, prependInstructions) }这里的关键点是如果某阶段的 base 是另一个先前阶段会先等待基础阶段完成并拿到其输出镜像 ID再生成标签确保io.buildah.stage.base能准确记录基础阶段镜像的 ID。标签行由buildStageLabelLineexecutor.go生成func (b *executor) buildStageLabelLine(stage *imagebuilder.Stage, base string, stages imagebuilder.Stages) string { labelLine : LABEL labelLine fmt.Sprintf( %q%q, io.buildah.stage.name, stage.Name) // Check if base of the stage is another (previous) stage. // If yes, base is set as image ID of this stage. // If not original base name is set (pullspec). if otherStageIndex, _ : b.stageIndex(base, stages); otherStageIndex ! -1 { b.stagesLock.Lock() if imgID, ok : b.stageImageIDs[otherStageIndex]; ok { base imgID } b.stagesLock.Unlock() } labelLine fmt.Sprintf( %q%q, io.buildah.stage.base, base) return labelLine }可以看到标签实际上是通过在阶段指令前动态插入一条LABEL指令实现的io.buildah.stage.base的值会区分两种来源base 是外部镜像如FROM alpine:3.20直接记录 pullspecalpine:3.20base 是先前阶段如FROM builder AS final替换为该阶段输出镜像的镜像 ID。四、命令行与 API 层的参数传递4.1 CLI 参数定义--save-stages与--stage-labels在 vendor/go.podman.io/buildah/pkg/cli/common.go 中统一定义fs.BoolVar(flags.SaveStages, save-stages, false, save intermediate stage images.) fs.BoolVar(flags.StageLabels, stage-labels, false, add metadata labels to intermediate stage images (requires --save-stages).)两者均为布尔开关默认false。4.2 API / Bindings 支持该选项不只存在于 CLI。Podman 的 REST API 与 Go bindings 同样支持pkg/bindings/images/build.goGo bindings 的镜像构建接口暴露save-stages查询参数供程序化调用pkg/api/handlers/compat/images_build.go兼容 Docker API 的构建处理器解析该参数并传入 Buildah 构建选项pkg/api/server/register_images.go注册路由时声明该查询参数。这意味着你可以通过 REST API 或 SDK 在 CI 流水线中程序化地控制中间镜像的保留策略。五、完整使用示例与最佳实践5.1 调试多阶段构建# 保留中间阶段方便排查 podman build --save-stages -t myapp . # 查看保存了哪些阶段镜像观察 io.buildah.stage.* 标签 podman images --format table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.Labels}} | grep -i buildah # 直接进入中间阶段容器排查问题替换为实际的中间镜像 ID podman run -it --rm 中间阶段镜像ID /bin/sh5.2 借助--layers复用缓存加速后续构建# 首次全量构建并缓存中间阶段 podman build --save-stages --layers -t myapp:latest . # 后续构建命中缓存跳过未变更阶段 podman build --layers -t myapp:latest .5.3 结合--stage-labels标识阶段镜像podman build --save-stages --stage-labels -t myapp . # 按标签精确查询某个阶段镜像 podman images --filter labelio.buildah.stage.namebuilder执行后各阶段镜像的标签形如io.buildah.stage.namebuilder io.buildah.stage.basegolang:1.225.4 磁盘空间权衡需要留意的是--save-stages会显著增加本地镜像存储占用——每个被保留的中间阶段镜像都包含完整的文件系统与层数据。因此建议仅在需要调试或期望复用中间阶段的构建中使用在 CI 或日常重复构建场景中配合--layers使用用磁盘空间换取构建速度调试结束后及时清理不再需要的中间镜像如podman image prune或按标签定向删除避免镜像存储膨胀。六、小结--save-stages是 Podman 多阶段构建中一个低成本高价值的开关默认关闭时中间阶段镜像在构建后被清理以节省空间开启后所有阶段镜像含最终镜像被保留服务于调试与跨构建复用两个核心场景与--layers组合可将保留的中间层作为后续构建的缓存加速迭代与--stage-labels组合可为每个阶段镜像自动注入io.buildah.stage.name/io.buildah.stage.base元数据方便用podman images --filter label...查询与管理该选项在 Podman CLI、REST API、Go bindings 及 Buildah 构建执行器中均有完整实现支持且--stage-labels依赖--save-stages的约束在 Podman 与 Buildah 两层代码中均有校验。相关文档与源码路径选项说明见 save-stages.md 与 stage-labels.md命令手册见 podman-build.1.md.in 与 podman-farm-build.1.md.in实现源码见 cmd/podman/common/build.go、vendor/go.podman.io/buildah/pkg/cli/common.go 及 vendor/go.podman.io/buildah/imagebuildah/executor.go。【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考