使用 Buildah 和 Merge-Image 构建跨平台镜像

功能概述

本指南演示如何使用 buildah Task 按架构构建镜像,然后使用 merge-image Task 将它们合并为一个多架构镜像。

典型流程:

  1. 构建 linux/amd64 镜像标签(例如 :v1.0.0-amd64
  2. 构建 linux/arm64 镜像标签(例如 :v1.0.0-arm64
  3. 将源标签合并为一个或多个目标标签(例如 :v1.0.0:latest

使用场景

  • 从一个代码仓库构建并发布多架构镜像。
  • 保留按架构区分的构建产物,同时对外提供一个统一的发布标签。
  • 在一次合并步骤中重新打标签并发布多个目标标签。

前提条件

  • 已安装 Tekton Pipelines
  • 已安装 git-clonebuildah0.9)和 merge-image0.1Tasks
  • 你可以将镜像推送到镜像仓库。
  • 镜像仓库凭据已准备为 Kubernetes Secret
  • 如果你使用基于节点原生的构建(如本指南所示),则集群必须同时具备 amd64arm64 节点。
  • 可选的本地验证工具:cranejq

第 1 步:准备镜像仓库凭据

使用 config.json 创建一个通用 Secret:

apiVersion: v1
kind: Secret
metadata:
  name: registry-config
data:
  config.json: <base64-encoded-registry-config-json>

第 2 步:创建 Pipeline 并运行 PipelineRun

使用前请更新以下值:

  • Git 仓库 URL 和 revision
  • 按架构区分的源镜像标签以及最终目标镜像标签
  • Crane 运行时镜像(如果你需要替换默认值)
  • Workspace 和 Secret 名称

示例 Pipeline:

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: e2e-multiarch-build
spec:
  params:
    - name: git-url
      type: string
      description: Git repository URL
    - name: git-revision
      type: string
      default: main
    - name: image-amd64
      type: string
      description: amd64 image reference
    - name: image-arm64
      type: string
      description: arm64 image reference
    - name: target-image
      type: string
      description: Final multi-architecture image reference
    - name: tls-verify
      type: string
      default: "false"
  workspaces:
    - name: shared-workspace
    - name: git-credentials
    - name: registry-credentials
  tasks:
    - name: git-clone
      params:
        - name: url
          value: $(params.git-url)
        - name: revision
          value: $(params.git-revision)
      taskRef:
        resolver: hub
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: git-clone
          - name: version
            value: "0.9"
      workspaces:
        - name: output
          workspace: shared-workspace
        - name: basic-auth
          workspace: git-credentials

    - name: buildah-amd64
      runAfter:
        - git-clone
      taskRef:
        resolver: hub
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: buildah
          - name: version
            value: "0.9"
      params:
        - name: IMAGES
          value:
            - $(params.image-amd64)
        - name: DOCKERFILE
          value: ./Dockerfile
        - name: TLS_VERIFY
          value: $(params.tls-verify)
        - name: FORMAT
          value: docker
      workspaces:
        - name: source
          workspace: shared-workspace
        - name: registryconfig
          workspace: registry-credentials

    - name: buildah-arm64
      runAfter:
        - git-clone
      taskRef:
        resolver: hub
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: buildah
          - name: version
            value: "0.9"
      params:
        - name: IMAGES
          value:
            - $(params.image-arm64)
        - name: DOCKERFILE
          value: ./Dockerfile
        - name: TLS_VERIFY
          value: $(params.tls-verify)
        - name: FORMAT
          value: docker
      workspaces:
        - name: source
          workspace: shared-workspace
        - name: registryconfig
          workspace: registry-credentials

    - name: merge-image
      runAfter:
        - buildah-amd64
        - buildah-arm64
      taskRef:
        resolver: hub
        params:
          - name: kind
            value: task
          - name: catalog
            value: catalog
          - name: name
            value: merge-image
          - name: version
            value: "0.1"
      params:
        - name: craneImage
          value: registry.alauda.cn:60070/devops/tektoncd/hub/crane:latest
        - name: sourceImages
          value:
            - $(params.image-amd64)
            - $(params.image-arm64)
        - name: targetImages
          value:
            - $(params.target-image)
        - name: tlsVerify
          value: $(params.tls-verify)
      workspaces:
        - name: registry-config
          workspace: registry-credentials

示例 PipelineRun:

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: e2e-multiarch-build-run
spec:
  pipelineRef:
    name: e2e-multiarch-build
  params:
    - name: git-url
      value: https://github.com/your-org/your-app
    - name: git-revision
      value: main
    - name: image-amd64
      value: registry.example.com/team/app:v1.0.0-amd64
    - name: image-arm64
      value: registry.example.com/team/app:v1.0.0-arm64
    - name: target-image
      value: registry.example.com/team/app:v1.0.0
    - name: tls-verify
      value: "false"
  # Node placement is configured on the run resource.
  taskRunSpecs:
    - pipelineTaskName: buildah-amd64
      podTemplate:
        # Force this task pod onto amd64 nodes.
        nodeSelector:
          kubernetes.io/arch: amd64
    - pipelineTaskName: buildah-arm64
      podTemplate:
        # Force this task pod onto arm64 nodes.
        nodeSelector:
          kubernetes.io/arch: arm64
  workspaces:
    - name: shared-workspace
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
    - name: git-credentials
      secret:
        secretName: git-credentials
    - name: registry-credentials
      secret:
        # Must match the Secret created in Step 1.
        secretName: registry-config

本示例中的关键点:

  • 示例使用了 git-clonebuildahmerge-image 的 hub resolver 引用,因此这些对应的 catalog Tasks 必须能在你的集群中被解析。
  • image-amd64image-arm64 是显式的 Pipeline 参数。这样可以使合并输入更清晰,并与 merge-image 消费 sourceImages 的方式保持一致。
  • taskRunSpecs + podTemplate.nodeSelector 配置在 PipelineRun 中;当你的集群包含混合 CPU 架构时,这一点至关重要。它们可以确保每个构建任务运行在匹配的节点架构上。
  • 默认情况下,Tekton 使用 coschedule: workspaces,这会创建一个 Affinity Assistant,并尝试将共享同一个基于 PVC 的 workspace 的 TaskRuns 调度到同一个节点上。如果你将 buildah-amd64buildah-arm64 固定到不同架构,这种默认行为会与按任务设置的 nodeSelector 冲突。在这种情况下,请在 TektonConfig 中设置 spec.pipeline.coschedule: disabled,这样 Tekton 就不会再强制共享 workspace 的 TaskRuns 运行在同一节点上。operator 会自动将该设置传播到 feature-flags ConfigMap。
  • 对于跨架构调度而言,禁用 coschedule 是必要的,但仅靠这一点还不够。如果共享 workspace 由 ReadWriteOnce PVC 提供支持,那么 Kubernetes 仍然无法在两个不同节点上同时以读写方式挂载该卷。要在不同节点上并行运行 amd64 和 arm64 构建,请使用支持多节点共享的 workspace 后端,例如 ReadWriteMany
  • sourceImages 应指向按架构区分的标签,而 targetImages 应为你希望发布的最终多架构标签。
  • registry-credentials workspace 会被 buildahmerge-image 共同复用;workspace 名称映射(registryconfig vs registry-config)由 Task 接口决定,必须与各自的 Task 定义匹配。

第 3 步:验证合并后的镜像平台

PipelineRun 成功后,你可以使用 cranepodmanskopeo 验证合并后的镜像平台:

使用 crane

crane manifest registry.example.com/team/app:v1.0.0 \
  | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"'

使用 podman

podman manifest inspect docker://registry.example.com/team/app:v1.0.0 \
  | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"'

使用 skopeo

skopeo inspect --raw docker://registry.example.com/team/app:v1.0.0 \
  | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"'

预期输出应包含:

  • linux/amd64
  • linux/arm64

故障排查

  • 如果 buildah-amd64buildah-arm64 共享同一个基于 PVC 的 workspace,并且被固定到不同架构,请检查 Tekton Pipelines 的 feature flag coschedule。当你需要这些 TaskRuns 调度到不同节点时,请在 TektonConfig 中设置 spec.pipeline.coschedule: disabled;默认的 workspaces 模式会通过 Affinity Assistant 尝试将它们共同放置在同一个节点上。关于确切的设置位置和行为,请参见 Unable to Use Multiple PVC Workspaces in Tekton
  • 即使设置了 coschedule: disabledReadWriteOnce PVC 仍然不能在两个不同节点之间同时以读写方式挂载。若要实现真正的跨架构并行构建,请将共享 workspace 绑定到支持 ReadWriteMany 的存储。
  • 建议将 sourceImagestargetImages 保持在同一个镜像仓库中。允许跨镜像仓库输入,但 merge-image 会输出警告日志。
  • merge-image 允许跨仓库合并。对于跨镜像仓库的源镜像或目标镜像,它会继续执行但输出警告日志,因此请确保所有涉及的镜像仓库凭据都有效。
  • sourceImagestargetImages 不能为空。重复的源引用或重复的源 digest 会被跳过,并且至少必须保留一个唯一的源镜像。
  • 对于自签名镜像仓库:
    • buildah:将 CA 文件挂载到 sslcertdir
    • merge-image:将 CA 文件挂载到 ca-bundle,并可选设置 caFileName
    • 仅在受信任的环境中将 TLS_VERIFY / tlsVerify 设为 false
  • buildah 参数使用大写(例如 IMAGESCONTAINERFILE),而 merge-image 使用 lower camel case(例如 sourceImagestargetImages)。

相关内容