Testing with multi-arch Docker images
Testing with multi-arch Docker images
With the introduction of Docker Buildx CLI plugin it became quite easy to test various platforms using single Dockerfile without any QEMU static builds. In this tutorial we’re going to use a multistage build with the simple C++ program that tests features of various platforms.
First, we need to register QEMU handlers. Please note that the handler registration won’t survive a reboot.
$ docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
There is no latest
alias for the docker/binfmt
and it’s recommended to grab the proper
tag on their homepage: https://hub.docker.com/r/docker/binfmt/tags?page=1&ordering=last_updated
The code to detect the platform endianness and its sizes for the basic types:
#include <iostream>
#ifndef PLATFORM
#define PLATFORM "N/A"
#endif
void platform() {
std::cout << "Platform: " << PLATFORM << std::endl;
#ifdef __BYTE_ORDER__
std::cout << "__BYTE_ORDER__\t\t= " << __BYTE_ORDER__ << " (";
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::cout << "__ORDER_BIG_ENDIAN__";
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
std::cout << "__ORDER_LITTLE_ENDIAN__";
#else
std::cout << "unknown";
#endif
std::cout << ")" << std::endl;
#else
std::cout << "__BYTE_ORDER__ is undefined" << std::endl;
#endif
#ifdef __FLOAT_WORD_ORDER__
std::cout << "__FLOAT_WORD_ORDER__\t= " << __FLOAT_WORD_ORDER__ << " (";
#if __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__
std::cout << "__ORDER_BIG_ENDIAN__";
#elif __FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__
std::cout << "__ORDER_LITTLE_ENDIAN__";
#else
std::cout << "unknown";
#endif
std::cout << ")" << std::endl;
#else
std::cout << "__FLOAT_WORD_ORDER__ is undefined" << std::endl;
#endif
std::cout << "sizeof(bool)\t\t= " << sizeof(bool)
<< " (" << 8 * sizeof(bool) << " bits)" << std::endl;
std::cout << "sizeof(char)\t\t= " << sizeof(char)
<< " (" << 8 * sizeof(char) << " bits)" << std::endl;
std::cout << "sizeof(wchar_t)\t\t= " << sizeof(wchar_t)
<< " (" << 8 * sizeof(wchar_t) << " bits)" << std::endl;
std::cout << "sizeof(short)\t\t= " << sizeof(short)
<< " (" << 8 * sizeof(short) << " bits)" << std::endl;
std::cout << "sizeof(int)\t\t= " << sizeof(int)
<< " (" << 8 * sizeof(int) << " bits)" << std::endl;
std::cout << "sizeof(long)\t\t= " << sizeof(long)
<< " (" << 8 * sizeof(long) << " bits)" << std::endl;
std::cout << "sizeof(long long)\t= " << sizeof(long long)
<< " (" << 8 * sizeof(long long) << " bits)" << std::endl;
std::cout << "sizeof(float)\t\t= " << sizeof(float)
<< " (" << 8 * sizeof(float) << " bits)" << std::endl;
std::cout << "sizeof(double)\t\t= " << sizeof(double)
<< " (" << 8 * sizeof(double) << " bits)" << std::endl;
std::cout << "sizeof(void*)\t\t= " << sizeof(void*)
<< " (" << 8 * sizeof(void*) << " bits)" << std::endl;
}
int main() {
platform();
}
The Dockerfile uses multistage build to compile and run the program:
FROM alpine AS builder
RUN apk add g++
WORKDIR /home
COPY platform.C .
# TARGETPLATFORM is provided by Buildx
ARG TARGETPLATFORM
RUN g++ "-DPLATFORM=\"$TARGETPLATFORM\"" platform.C -o platform -static
FROM alpine
WORKDIR /home
COPY --from=builder /home/platform .
ENTRYPOINT ["./platform"]
buildx
does support batch build, but only within the limited output options (like --push
).
For local testing, I’d prefer to avoid pushing, and rather build the image for the local Docker.
Unfortunately, that won’t work in the batch mode, therefore some shell scripting is required.
Let’s prepare all the required files first (within some temporary directory):
$ mkdir multiarch
$ cd multiarch
$ curl -O https://gitlab.com/nuald/nuald-blogspot-com/-/raw/master/site/adoc/platform.C
$ curl -o Dockerfile https://gitlab.com/nuald/nuald-blogspot-com/-/raw/master/site/adoc/multiarch-dockerfile
Get all supported platforms by the buildx
builder:
$ PLATFORMS=($(docker buildx inspect | sed -n -e 's/Platforms: \(.*\)/\1/p' | tr ',' "\n"))
$ echo ${PLATFORMS[*]}
linux/amd64 linux/386 linux/arm64 linux/riscv64 linux/ppc64le linux/s390x linux/arm/v7 linux/arm/v6
Build all images (please note that riscv64
platform is not supported by the official Alpine dockerhub):
$ for platform in ${PLATFORMS[@]}; do docker buildx build --platform $platform -o type=docker,name=multiarch/$platform .; done
The list of all official supported Docker images for alternative platforms is available in the documentation: https://github.com/docker-library/official-images#architectures-other-than-amd64
Finally, run all images (you could see that riscv64
image is not available):
$ for platform in ${PLATFORMS[@]}; do docker run -it --rm --platform $platform multiarch/$platform .; done
Platform: linux/amd64
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 8 (64 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 8 (64 bits)
Platform: linux/386
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 4 (32 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 4 (32 bits)
Platform: linux/arm64
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 8 (64 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 8 (64 bits)
Unable to find image 'multiarch/linux/riscv64:latest' locally
docker: Error response from daemon: pull access denied for multiarch/linux/riscv64, repository does not exist or may require 'docker login': denied: requested access to the resource is denied.
See 'docker run --help'.
Platform: linux/ppc64le
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 8 (64 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 8 (64 bits)
Platform: linux/s390x
__BYTE_ORDER__ = 4321 (__ORDER_BIG_ENDIAN__)
__FLOAT_WORD_ORDER__ = 4321 (__ORDER_BIG_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 8 (64 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 8 (64 bits)
Platform: linux/arm/v7
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 4 (32 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 4 (32 bits)
Platform: linux/arm/v6
__BYTE_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
__FLOAT_WORD_ORDER__ = 1234 (__ORDER_LITTLE_ENDIAN__)
sizeof(bool) = 1 (8 bits)
sizeof(char) = 1 (8 bits)
sizeof(wchar_t) = 4 (32 bits)
sizeof(short) = 2 (16 bits)
sizeof(int) = 4 (32 bits)
sizeof(long) = 4 (32 bits)
sizeof(long long) = 8 (64 bits)
sizeof(float) = 4 (32 bits)
sizeof(double) = 8 (64 bits)
sizeof(void*) = 4 (32 bits)
And surely, it all could be properly automated and integrated with the required CI/CD systems.
Comments
Post a Comment