We’re overhauling our CI testing pipeline. In a previous post we looked at using Vagrant to manage Windows VMs.
Here we’re using docker and QEMU to expand testing to ARM64. Our examples cover Rust and C#/.NET, but it’s easily adaptable to any language/framework of your liking.
QEMU
We’ve been wanting to extend testing to more “exotic” platforms, particularly ARM64/aarch64. This juicy Travis-CI issue got us heading in that direction. They’re originally using Debian “Jessie”, but “Stretch” is the first with ARM64 support.
On an Ubuntu 18.04 host, first install docker on Ubuntu then:
docker run --rm --privileged multiarch/qemu-user-static:register
docker run -it --rm multiarch/debian-debootstrap:arm64-stretch
In the resulting shell run uname -a
:
root@f190ea8ef8cc:/# uname -a
Linux f190ea8ef8cc 4.15.0-43-generic #46-Ubuntu SMP Thu Dec 6 14:45:28 UTC 2018 aarch64 GNU/Linux
Note aarch64. Pretty slick.
The “magic” here is Linux binfmt which forwards arbitrary executable formats to a user-space application- in this case QEMU.
Some more reading:
- https://www.tomaz.me/2013/12/02/running-travis-ci-tests-on-arm.html
- https://blog.hypriot.com/post/setup-simple-ci-pipeline-for-arm-images/
Rust
Our first guinea pig is one of our Rust libraries. Here’s a Dockerfile to install Rust in the ARM64 environment:
FROM multiarch/debian-debootstrap:arm64-stretch
RUN apt-get update && apt-get install -y \
build-essential \
ca-certificates \
clang \
cmake \
curl
ARG RUST_VER=1.32.0
# Make sure rustup and cargo are in PATH
ENV PATH "~/.cargo/bin:$PATH"
# Install rustup, skip latest toolchain and get a specific version
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none && \
~/.cargo/bin/rustup default $RUST_VER
clang
andcmake
are pre-requisites for runngca-certificates
is to deal with curl failing with:ERROR: The certificate of `XXX' is not trusted
This has been pushed to Docker Hub as jeikabu/debian-rust
so you can try it with:
docker run -it --rm jeikabu/debian-rust:arm64v8-stretch-1.32.0
Using cargo
to run our tests:
docker run -t -v $(pwd):/usr/src jeikabu/debian-rust:arm64v8-stretch-1.32.0 /bin/bash -c "cd /usr/src; cargo test"
NB: The exit code is 0
if all tests succeed.
.NET Core
Microsoft has an epic number of images related to .NET Core. Of particular interest is the Dockerfile for .NET Core 3.0 preview targetting ARM64. Using that to again enable testing on ARM64 via qemu:
FROM multiarch/debian-debootstrap:arm64-stretch
RUN apt-get update && apt-get install -y \
curl \
gnupg \
icu-devtools
# From:
# https://github.com/dotnet/dotnet-docker/blob/master/3.0/sdk/stretch/arm64v8/Dockerfile
ENV DOTNET_SDK_VERSION 3.0.100-preview-010184
RUN curl -SL --output dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-arm64.tar.gz \
&& dotnet_sha512='3fd7338fdbcc194cdc4a7472a0639189830aba4f653726094a85469b383bd3dc005e3dad4427fee398f76b40b415cbd21b462bd68af21169b283f44325598305' \
&& echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \
&& mkdir -p /usr/share/dotnet \
&& tar -zxf dotnet.tar.gz -C /usr/share/dotnet \
&& rm dotnet.tar.gz \
&& ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet
If you’re outside North America you might want to use the download URLs in the release notes. In China, downloading from https://download.visualstudio.microsoft.com was many times faster than using https://dotnetcli.blob.core.windows.net (like in the official Dockerfile). YMMV.
This image has also been pushed to Docker Hub as jeikabu/debian-dotnet-sdk
.
Let’s run our tests:
docker run -t -v $(pwd):/usr/src jeikabu/debian-dotnet-sdk:arm64v8-stretch /bin/bash -c "cd /usr/src; dotnet test"
icu-devtools
package is there otherwise you’ll get:
root@79106a1b502f:/# cd /usr/src
root@864d67ab40bb:/usr/src# dotnet clean
qemu: Unsupported syscall: 283
FailFast:
Couldn't find a valid ICU package installed on the system. Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support.
at System.Environment.FailFast(System.String)
at System.Globalization.GlobalizationMode.GetGlobalizationInvariantMode()
at System.Globalization.GlobalizationMode..cctor()
at System.Globalization.CultureData.CreateCultureWithInvariantData()
at System.Globalization.CultureData.get_Invariant()
at System.Globalization.CultureData.GetCultureData(System.String, Boolean)
at System.Globalization.CultureInfo..ctor(System.String, Boolean)
at System.Reflection.RuntimeAssembly.GetLocale()
at System.Reflection.RuntimeAssembly.GetName(Boolean)
at System.Reflection.Assembly.GetName()
at System.Diagnostics.Tracing.EventPipeController.GetAppName()
at System.Diagnostics.Tracing.EventPipeController..ctor()
at System.Diagnostics.Tracing.EventPipeController.Initialize()
at System.StartupHookProvider.ProcessStartupHooks()
qemu: uncaught target signal 6 (Aborted) - core dumped
Aborted (core dumped)
There’s several blogs and StackOverflow questions containing solutions that seem to be variants from the dotnet documentation for RHEL. We’re not trying to create pedantically diminutive images, so adding icu-devtools
package will suffice.
Travis
This should work on most Linux systems. Let’s try running it as part of our Travis CI.
Create qemu_arm64.sh
to run tests:
#!/usr/bin/env bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]] || [[ "$OSTYPE" == "linux-gnu" ]]; then
docker run --rm --privileged multiarch/qemu-user-static:register --reset
docker run -t -v $(pwd):/usr/src jeikabu/debian-dotnet-sdk:arm64v8-stretch /bin/bash -c "cd /usr/src && dotnet build && dotnet test --filter 'platform!=windows' --verbosity normal"
fi
after_success:
- ./scripts/qemu_arm64.sh
NB: if any test fails a non-0
value will be returned, but after_success
won’t fail the build. If we decide to keep this and once ARM64 stabilizes we could move it to an earlier phase and let it fail builds.
This is a pretty minimal example using a small project, but still takes just over 5 minutes on Travis. Larger projects may run afoul of build timeouts. Once again, YMMV.