Working with modules in Azure IoT Edge is the process of creating custom code, put it into a container and deploy it to the Edge device. Deployment can happen on different processor architectures and this is exactly what has to be considered when building the docker images from the Dockerfile.

Building for each platform

When you create a new custom edge module using the dotnet templates described in the Azure IoT Edge documentation, you’ll notice that you have a Docker folder for each target platform. One for Windows and one for Linux. Often you add a third one targeting devices like the Raspberry PI 3 which runs on ARM. I always have taken this as given to create different images for each platform, I also included in the CI/CD process generation of deployment manifests for each of the target platforms, ending up in having something like this dariuszparys/monitor-module:57-linux-x64 as container reference.

Multi-Architecture Manifests

There are times in a devs life you encounter the moment of WOW and think why didn’t I know that before? This moment I was thinking about how the other Azure IoT Edge relevant containers just leverage a single container reference among all platforms. If you start the microsoft/azureiotedge-agent through iotedgectl start it pulls down the correct container image for the running docker platform. If I run docker with windows containers it will get the correct container image and the same is true running on RPI3 on ARM. The magic happens through the availability of the Image Manifest V 2, Schema 2 which allows to push a manifest file into the registry supporting this manifest version and point to the specific docker images for the platforms you support.

Basically it is a header structure to point to the corresponding platform specific container image. You still create containers for all platforms you support, but you can now have a single point of reference. Let me give you an example.

Example of a Manifest file

I have three different docker containers all created for the platforms Windows and Linux and the processor architectures of x64 and ARM. I push those with specific tags into my docker registry resulting in this repository list

1
2
3
4
5
6
7
dariuszparys/some-edge-module
Tag Name
1.0.0-preview018-linux-amd64
1.0.0-preview018-linux-arm32v7
1.0.0-preview018-windows-amd64

Now I can define a manifest to declare a single tag like

1
1.0.0-preview018

The definition of the manifest can be done in several ways. There is an open pull request implementing a manifest subcommand for the docker client, which I haven’t used. I have chosen the Manifest Tool from Phil Estes which allows you to write a YAML file and use that as input to push to the docker registry of your choice (should support the manifest schema of course).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
image: dariuszparys/some-edge-module:1.0.0-preview018
manifests:
-
image: dariuszparys/some-edge-module:1.0.0-preview018-linux-amd64
platform:
architecture: amd64
os: linux
-
image: dariuszparys/some-edge-module:1.0.0-preview018-linux-arm32v7
platform:
architecture: arm
os: linux
-
image: dariuszparys/some-edge-module:1.0.0-preview018-windows-amd64
platform:
architecture: amd64
os: windows

As you can see in the above YAML it is corresponding the schema definition. Now it just has to be pushed to the registry.

1
manifest-tool push from-spec manifest.yml

DevOps

With the possibilities of multi-architecture deployment of containers it should be of course integrated into my CI workflow as well. Integrating a custom tool like the manifest-tool is a no brainer. If you build containers for multiple architectures and don’t leverage this approach so far, I can strongly suggest in having this approach in your development process. If you are already building multi-arch docker images and have suggestions on how to improve this or have tips I should incorporate please contact me.