|
| 1 | +--- |
| 2 | +weight: 13 |
| 3 | +i18n: |
| 4 | + title: |
| 5 | + en: Build Cross-Platform Images with Buildah and Merge-Image |
| 6 | + zh: 使用 Buildah 和 Merge-Image 构建跨平台镜像 |
| 7 | +title: Build Cross-Platform Images with Buildah and Merge-Image |
| 8 | +--- |
| 9 | + |
| 10 | +# Build Cross-Platform Images with Buildah and Merge-Image |
| 11 | + |
| 12 | +## Feature Overview |
| 13 | + |
| 14 | +This guide shows how to build per-architecture images with the `buildah` `Task`, then merge them into a single multi-architecture image with the `merge-image` `Task`. |
| 15 | + |
| 16 | +Typical flow: |
| 17 | + |
| 18 | +1. Build `linux/amd64` image tag (for example `:v1.0.0-amd64`) |
| 19 | +2. Build `linux/arm64` image tag (for example `:v1.0.0-arm64`) |
| 20 | +3. Merge source tags into one or more target tags (for example `:v1.0.0`, `:latest`) |
| 21 | + |
| 22 | +## Use Cases |
| 23 | + |
| 24 | +- Build and publish multi-architecture images from one code repository. |
| 25 | +- Keep architecture-specific build outputs while exposing one unified release tag. |
| 26 | +- Re-tag and publish multiple target tags in one merge step. |
| 27 | + |
| 28 | +## Prerequisites |
| 29 | + |
| 30 | +- `Tekton Pipelines` is installed. |
| 31 | +- `git-clone`, `buildah` (`0.9`), and `merge-image` (`0.1`) `Tasks` are installed. |
| 32 | +- You can push images to your registry. |
| 33 | +- Registry credentials are prepared as a Kubernetes `Secret`. |
| 34 | +- If you use native node-based builds (as shown in this guide), your cluster must have both `amd64` and `arm64` nodes available. |
| 35 | +- Optional local verification tools: `crane`, `jq`. |
| 36 | + |
| 37 | +## Step 1: Prepare Registry Credentials |
| 38 | + |
| 39 | +Create a generic secret with `config.json`: |
| 40 | + |
| 41 | +```yaml |
| 42 | +apiVersion: v1 |
| 43 | +kind: Secret |
| 44 | +metadata: |
| 45 | + name: registry-config |
| 46 | +data: |
| 47 | + config.json: <base64-encoded-registry-config-json> |
| 48 | +``` |
| 49 | +
|
| 50 | +## Step 2: Create Pipeline and Run PipelineRun |
| 51 | +
|
| 52 | +Update the following values before use: |
| 53 | +
|
| 54 | +- Git repository URL and revision |
| 55 | +- Architecture-specific source image tags and final target image tag |
| 56 | +- Crane runtime image, if you need to replace the default |
| 57 | +- Workspace and secret names |
| 58 | +
|
| 59 | +Example Pipeline: |
| 60 | +
|
| 61 | +```yaml |
| 62 | +apiVersion: tekton.dev/v1 |
| 63 | +kind: Pipeline |
| 64 | +metadata: |
| 65 | + name: e2e-multiarch-build |
| 66 | +spec: |
| 67 | + params: |
| 68 | + - name: git-url |
| 69 | + type: string |
| 70 | + description: Git repository URL |
| 71 | + - name: git-revision |
| 72 | + type: string |
| 73 | + default: main |
| 74 | + - name: image-amd64 |
| 75 | + type: string |
| 76 | + description: amd64 image reference |
| 77 | + - name: image-arm64 |
| 78 | + type: string |
| 79 | + description: arm64 image reference |
| 80 | + - name: target-image |
| 81 | + type: string |
| 82 | + description: Final multi-architecture image reference |
| 83 | + - name: tls-verify |
| 84 | + type: string |
| 85 | + default: "false" |
| 86 | + workspaces: |
| 87 | + - name: shared-workspace |
| 88 | + - name: git-credentials |
| 89 | + - name: registry-credentials |
| 90 | + tasks: |
| 91 | + - name: git-clone |
| 92 | + params: |
| 93 | + - name: url |
| 94 | + value: $(params.git-url) |
| 95 | + - name: revision |
| 96 | + value: $(params.git-revision) |
| 97 | + taskRef: |
| 98 | + resolver: hub |
| 99 | + params: |
| 100 | + - name: kind |
| 101 | + value: task |
| 102 | + - name: catalog |
| 103 | + value: catalog |
| 104 | + - name: name |
| 105 | + value: git-clone |
| 106 | + - name: version |
| 107 | + value: "0.9" |
| 108 | + workspaces: |
| 109 | + - name: output |
| 110 | + workspace: shared-workspace |
| 111 | + - name: basic-auth |
| 112 | + workspace: git-credentials |
| 113 | + |
| 114 | + - name: buildah-amd64 |
| 115 | + runAfter: |
| 116 | + - git-clone |
| 117 | + taskRef: |
| 118 | + resolver: hub |
| 119 | + params: |
| 120 | + - name: kind |
| 121 | + value: task |
| 122 | + - name: catalog |
| 123 | + value: catalog |
| 124 | + - name: name |
| 125 | + value: buildah |
| 126 | + - name: version |
| 127 | + value: "0.9" |
| 128 | + params: |
| 129 | + - name: IMAGES |
| 130 | + value: |
| 131 | + - $(params.image-amd64) |
| 132 | + - name: DOCKERFILE |
| 133 | + value: ./Dockerfile |
| 134 | + - name: TLS_VERIFY |
| 135 | + value: $(params.tls-verify) |
| 136 | + - name: FORMAT |
| 137 | + value: docker |
| 138 | + workspaces: |
| 139 | + - name: source |
| 140 | + workspace: shared-workspace |
| 141 | + - name: registryconfig |
| 142 | + workspace: registry-credentials |
| 143 | + |
| 144 | + - name: buildah-arm64 |
| 145 | + runAfter: |
| 146 | + - git-clone |
| 147 | + taskRef: |
| 148 | + resolver: hub |
| 149 | + params: |
| 150 | + - name: kind |
| 151 | + value: task |
| 152 | + - name: catalog |
| 153 | + value: catalog |
| 154 | + - name: name |
| 155 | + value: buildah |
| 156 | + - name: version |
| 157 | + value: "0.9" |
| 158 | + params: |
| 159 | + - name: IMAGES |
| 160 | + value: |
| 161 | + - $(params.image-arm64) |
| 162 | + - name: DOCKERFILE |
| 163 | + value: ./Dockerfile |
| 164 | + - name: TLS_VERIFY |
| 165 | + value: $(params.tls-verify) |
| 166 | + - name: FORMAT |
| 167 | + value: docker |
| 168 | + workspaces: |
| 169 | + - name: source |
| 170 | + workspace: shared-workspace |
| 171 | + - name: registryconfig |
| 172 | + workspace: registry-credentials |
| 173 | + |
| 174 | + - name: merge-image |
| 175 | + runAfter: |
| 176 | + - buildah-amd64 |
| 177 | + - buildah-arm64 |
| 178 | + taskRef: |
| 179 | + resolver: hub |
| 180 | + params: |
| 181 | + - name: kind |
| 182 | + value: task |
| 183 | + - name: catalog |
| 184 | + value: catalog |
| 185 | + - name: name |
| 186 | + value: merge-image |
| 187 | + - name: version |
| 188 | + value: "0.1" |
| 189 | + params: |
| 190 | + - name: craneImage |
| 191 | + value: registry.alauda.cn:60070/devops/tektoncd/hub/crane:latest |
| 192 | + - name: sourceImages |
| 193 | + value: |
| 194 | + - $(params.image-amd64) |
| 195 | + - $(params.image-arm64) |
| 196 | + - name: targetImages |
| 197 | + value: |
| 198 | + - $(params.target-image) |
| 199 | + - name: tlsVerify |
| 200 | + value: $(params.tls-verify) |
| 201 | + workspaces: |
| 202 | + - name: registry-config |
| 203 | + workspace: registry-credentials |
| 204 | +``` |
| 205 | +
|
| 206 | +Example PipelineRun: |
| 207 | +
|
| 208 | +```yaml |
| 209 | +apiVersion: tekton.dev/v1 |
| 210 | +kind: PipelineRun |
| 211 | +metadata: |
| 212 | + name: e2e-multiarch-build-run |
| 213 | +spec: |
| 214 | + pipelineRef: |
| 215 | + name: e2e-multiarch-build |
| 216 | + params: |
| 217 | + - name: git-url |
| 218 | + value: https://github.com/your-org/your-app |
| 219 | + - name: git-revision |
| 220 | + value: main |
| 221 | + - name: image-amd64 |
| 222 | + value: registry.example.com/team/app:v1.0.0-amd64 |
| 223 | + - name: image-arm64 |
| 224 | + value: registry.example.com/team/app:v1.0.0-arm64 |
| 225 | + - name: target-image |
| 226 | + value: registry.example.com/team/app:v1.0.0 |
| 227 | + - name: tls-verify |
| 228 | + value: "false" |
| 229 | + # Node placement is configured on the run resource. |
| 230 | + taskRunSpecs: |
| 231 | + - pipelineTaskName: buildah-amd64 |
| 232 | + podTemplate: |
| 233 | + # Force this task pod onto amd64 nodes. |
| 234 | + nodeSelector: |
| 235 | + kubernetes.io/arch: amd64 |
| 236 | + - pipelineTaskName: buildah-arm64 |
| 237 | + podTemplate: |
| 238 | + # Force this task pod onto arm64 nodes. |
| 239 | + nodeSelector: |
| 240 | + kubernetes.io/arch: arm64 |
| 241 | + workspaces: |
| 242 | + - name: shared-workspace |
| 243 | + volumeClaimTemplate: |
| 244 | + spec: |
| 245 | + accessModes: |
| 246 | + - ReadWriteOnce |
| 247 | + resources: |
| 248 | + requests: |
| 249 | + storage: 1Gi |
| 250 | + - name: git-credentials |
| 251 | + secret: |
| 252 | + secretName: git-credentials |
| 253 | + - name: registry-credentials |
| 254 | + secret: |
| 255 | + # Must match the Secret created in Step 1. |
| 256 | + secretName: registry-config |
| 257 | +``` |
| 258 | +
|
| 259 | +Key points in this example: |
| 260 | +
|
| 261 | +- The example uses hub resolver references for `git-clone`, `buildah`, and `merge-image`, so the corresponding catalog Tasks must be resolvable in your cluster. |
| 262 | +- `image-amd64` and `image-arm64` are explicit Pipeline parameters. This keeps the merge input unambiguous and matches how `merge-image` consumes `sourceImages`. |
| 263 | +- `taskRunSpecs` + `podTemplate.nodeSelector` are configured in the `PipelineRun` and are critical when your cluster has mixed CPU architectures. They make sure each build task runs on the matching node architecture. |
| 264 | +- By default, Tekton uses `coschedule: workspaces`, which creates an Affinity Assistant and tries to place TaskRuns sharing the same PVC-backed workspace on the same node. If you pin `buildah-amd64` and `buildah-arm64` to different architectures, this default behavior conflicts with the per-task `nodeSelector`. In that case, set `spec.pipeline.coschedule: disabled` in your `TektonConfig` so Tekton stops forcing shared-workspace TaskRuns onto one node. The operator will propagate that setting to the `feature-flags` ConfigMap automatically. |
| 265 | +- Disabling `coschedule` is necessary for cross-architecture scheduling, but it is not sufficient on its own. If the shared workspace is backed by a `ReadWriteOnce` PVC, Kubernetes still cannot mount that volume read-write on two different nodes at the same time. To run the amd64 and arm64 builds concurrently on different nodes, use a workspace backend that supports multi-node sharing such as `ReadWriteMany`. |
| 266 | +- `sourceImages` should point to the architecture-specific tags, while `targetImages` should be the final multi-arch tag or tags you want to publish. |
| 267 | +- `registry-credentials` workspace is reused by both `buildah` and `merge-image`; the workspace name mapping (`registryconfig` vs `registry-config`) is task-interface specific and must match each Task definition. |
| 268 | + |
| 269 | +## Step 3: Verify Merged Image Platforms |
| 270 | + |
| 271 | +After `PipelineRun` succeeds, you can verify the merged image platforms with `crane`, `podman`, or `skopeo`: |
| 272 | + |
| 273 | +Using `crane`: |
| 274 | + |
| 275 | +```bash |
| 276 | +crane manifest registry.example.com/team/app:v1.0.0 \ |
| 277 | + | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"' |
| 278 | +``` |
| 279 | + |
| 280 | +Using `podman`: |
| 281 | + |
| 282 | +```bash |
| 283 | +podman manifest inspect docker://registry.example.com/team/app:v1.0.0 \ |
| 284 | + | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"' |
| 285 | +``` |
| 286 | + |
| 287 | +Using `skopeo`: |
| 288 | + |
| 289 | +```bash |
| 290 | +skopeo inspect --raw docker://registry.example.com/team/app:v1.0.0 \ |
| 291 | + | jq -r '.manifests[].platform | "\(.os)/\(.architecture)"' |
| 292 | +``` |
| 293 | + |
| 294 | +Expected output includes: |
| 295 | + |
| 296 | +- `linux/amd64` |
| 297 | +- `linux/arm64` |
| 298 | + |
| 299 | +## Troubleshooting |
| 300 | + |
| 301 | +- If `buildah-amd64` and `buildah-arm64` share one PVC-backed workspace and are pinned to different architectures, check the Tekton Pipelines feature flag `coschedule`. |
| 302 | + Set `spec.pipeline.coschedule: disabled` in your `TektonConfig` when you need those TaskRuns to schedule onto different nodes; the default `workspaces` mode tries to co-locate them on one node through Affinity Assistant. For the exact setting location and behavior, see [Unable to Use Multiple PVC Workspaces in Tekton](../trouble_shooting/using-multiple-pvc-workspaces-failed.mdx). |
| 303 | +- Even with `coschedule: disabled`, a `ReadWriteOnce` PVC still cannot be mounted read-write from two different nodes simultaneously. For true parallel cross-architecture builds, bind the shared workspace to storage that supports `ReadWriteMany`. |
| 304 | +- Prefer keeping `sourceImages` and `targetImages` in the same registry. Cross-registry input is allowed but `merge-image` emits warning logs. |
| 305 | +- `merge-image` allows cross-repository merges. For cross-registry source or target images, it continues with warning logs, so make sure credentials are valid for every registry involved. |
| 306 | +- `sourceImages` and `targetImages` must be non-empty. Duplicate source references or duplicate source digests are skipped, and at least one unique source image must remain. |
| 307 | +- For self-signed registries: |
| 308 | + - `buildah`: mount CA files to `sslcertdir` |
| 309 | + - `merge-image`: mount CA files to `ca-bundle` and optionally set `caFileName` |
| 310 | + - set `TLS_VERIFY` / `tlsVerify` to `false` only in trusted environments |
| 311 | +- `buildah` parameters are uppercase (for example `IMAGES`, `CONTAINERFILE`), while `merge-image` uses lower camel case (for example `sourceImages`, `targetImages`). |
| 312 | + |
| 313 | +## Related |
| 314 | + |
| 315 | +- [Built-in Tool Images Selection Guide](./tool-images.mdx) |
0 commit comments