Building multi-arch images
In the last post, we succeeded in building foreign arch images via qemu. As each architecture gets its own tag, it becomes a bit cumbersome to use them across architectures.
Luckily, buildah supports multi-arch manifests which allow the client to choose the suitable image for the current architecture by itself. Again, the DevConf.CZ 2020 presentation on building multi-arch container images with buildah by Nalin Dahyabhai is the best introduction that I could find.
In theory, it is a mere two lines to generate a manifest and upload it. For images that were just built via buildah and tagged as ‘image:tag-amd64’ and ‘image:tag-arm64’, just running the following is good enough:
buildah manifest create image:tag image:tag-amd64 image:tag-arm64
buildah manifest push image:tag docker://quay.io/repo/image:tag --all
Buildah will determine the architectures of the individual from the image manifests automatically.
Details
The devil is as always in the details, especially when it comes to the registries that should accept the resulting images.
Buildah will default to OCI-formatted images, which nearly no registry is
handling properly. The web interface of registry.gitlab.com will crash
internally, docker.io will pretend that the images are not multi-arch and
quay.io will not accept them at all. This can be changed via --format docker
or
export BUILDAH_FORMAT=docker
The registry at quay.io is quite strict in what it accepts. For example, image
architectures need to be correctly tagged for the manifest to be accepted.
Using aarch64
instead of arm64
will get the manifest rejected with a
less-than-helpful manifest invalid
error message. It is also a bit sensitive
to the specific combination of manifest versions for images and image lists, as
we will see in the next section.
Parallel builds
With the above setup, it is easy to build multi-arch images sequentially, i.e. build the single-arch images one after the other followed by the multi-arch manifest. The biggest disadvantage of that is that sequentially building all single-arch images is painfully slow. In contrast to a single native-arch image which might take around 5 minutes to build, building another 3 foreign-arch-on-qemu images and wrapping it up in a manifest can easily take multiple hours.
The whole process can be sped up quite a bit if the single-arch image builds and uploads are done in parallel, followed by a second stage for the multi-arch manifest. The only change that needs to be done is modifying the image references from localhost to the remote image registry like
buildah manifest create image:tag docker://quay.io/repo/image:tag-amd64 docker://quay.io/repo/image:tag-arm64
This works for registry.gitlab.com and docker.io, but not for quay.io. After a
lot of debugging, the issue seems to be related to manifest versions (buildah
issue, Red Hat
Bugzilla). Basically, for
quay.io, pulling down the single-arch images seems to downgrade the image
manifest version. These downgraded image manifests are then not accepted as
part of a multi-arch manifest. To work around that, it is possible to download
the images manually beforehand and upgrade the manifest versions again via
skopeo’s --format v2s2
parameter with
IMAGE=quay.io/repo/image:tag-amd64
skopeo copy docker://$IMAGE containers-storage:$IMAGE --format v2s2
With that in place, parallel multi-arch image building and uploading works flawlessly on the runners on gitlab.com for all registries.
The code in this post can be found in the container images repository.