With arm64 based laptops getting very popular it has become a need for the container developers to build a multi architecture images e.g. build amd64 on arm64 machine.
In my case I use Apple M1, as part of my day job I build,test and deploy container applications; by default my macbook produces linux/arm64 images. If I need to deploy the same image to any cloud services e.g. Google Cloud Run or Google Kubernetes Engine , then I need to have the linux/amd64 image as well.
For such use cases the we usually write multiple Dockerfile for each architecture, a manifest file etc., there nothing wrong with that approach but then it will soon become too hard to maintain.
So what we need is a tool chain that can,
Have same process to build image locally as well as for cloud usage. I found GoReleaser to be apt for this requirement.
Is declarative, typically mapping to standard steps that will be part of building a golang application i.e. test, build and push image to container registry. Drone help to setup a build pipeline in declarative way and has plugins for all major tools/platforms including one for GoReleaser.
Build multi architecture images. Buildx helps us to build multi architecture image using the standard docker build semantics. Adding to our tools there is a Drone plugin Drone Buildx Plugin that allow us to just plug it into our pipeline.
The end to end demo of this blog post is available on my GitHub Repo.
Clone it one to your laptop for quick reference,
git clone https://github.com/kameshsampath/go-hello-world.git \
&& cd "$(basename "$_" .git)"
export DEMO_HOME="$PWD"
For the rest of the post we will refer to this cloned folder as $DEMO_HOME
.
For this demo we will be using a short lived container registry called ttl.sh for pushing and pulling the demo app image.
Download and Drone CLI to your path.
NOTE: You are welcome to use your own registry. Please check the Drone Buildx Plugin documentation on how to configure the extra parameters like
username
andpassword
.
Let us setup some environment variables that we will be using as part of the demo
# a unique uid as image identifier, it needs to be in the lowercase
export IMAGE_NAME=$(uuidgen | tr '[:upper:]' '[:lower:]')
# short lived image for 10 mins
export IMAGE_TAG=10m
And for convenience we will save these values on to .env
file which we will use to pass environment variables to Drone Pipelines and Docker runs.
envsubst < "$DEMO_HOME/.env.example" | tee "$DEMO_HOME/.env"
NOTE: The example above uses envsubst to update the file. If you don't have envsubst installed you can manually update the
.env
file.
Let us examine the Drone pipeline .drone.yml
that we have,
It is very simple Drone Pipeline that has three steps ,
- test that runs the golang tests
- build that uses the GoReleaser to build the golang application
- push that pushes the application to the container registry that we had configured earlier.
The push steps also specifies the platforms linux/arm64
and linux/amd64
that instructs the docker buildx build to perform multi architecture build that will produce a container image that is compatible with the respective platforms.
The push step uses the Dockerfile above to perform the build.
There are few important things to note,
The comment
# syntax=docker/dockerfile:1.4
instructs the docker build to use buildx Dockerfile semantics from docker/dockerfile:1.4 . Though it does not have big significance in our case but we ensure the we use buildx to perform the builds. Buildx is default from Docker v18.x and above.The
ARG TARGETARCH
allows the docker build to know the architecture thats is being build for e.g. amd64 or arm64 etc.,The base image
gcr.io/distroless/base
allows to build a tiny image with just the binary of our application in it :).
That's pretty much need to build our multi architecture golang application, run the following drone command to start that pipeline that will build push the image to the container registry.
drone exec --trusted --env-file=.env
Once the build is done run the following command to start the built container locally,
docker-compose up
Doing a simple curl
to http://localhost:8080 that should say "Hello World".
Lastly, a small caveat when using GoReleaser. GoReleaser, when cross compiling the code, creates distribution folders like linux_arm64 and linux_amd64_v1
under the dist folder, where the built binaries for the platforms (linux/arm64
and linux/amd64
) are saved.
The folder names follows a pattern like <platform>_<arch>_<version>
with version
being optional. For amd64
GoReleaser creates a binary folder like linux_amd64_v1
that made it difficult to pick the binary by using the arg TARGETARCH
from within the Dockerfile; as TARGETARCH
just returns only the arch name without version. In our case just amd64
.
As a workaround we need to write GoReleaser build post hook script as shown below that will rename the folder linux_amd64_v1
to linux_amd64
which then allows us to pick the right binary based on the architecture using the TARGETARCH
,
COPY server_linux_${TARGETARCH}/server /bin/server
NOTE Special thanks to my son Rithul Kamesh who volunteered to write that little script for me 😊.
To summarize what we did,
- Use Buildx to build multi architecture container images
- Leveraged Drone Pipelines to define a declarative builds
- Used GoReleaser to have standard configuration to build golang applications
- Finally leveraged GoReleaser, Buildx via Drone plugins to perform reproducible multi architecture builds
I have also written Drone CI Docker Extension that allows you run this demo or any other Drone pipelines right from your Docker for Desktop. If you have minute please do try it out and let me know your valuable feedback/improvements.
Top comments (0)