Scott Rossillo
Ingénieur logiciel, équipe Inception
AWS a récemment annoncé un aperçu de sa nouvelle génération d'instances Amazon EC2 M6g qui sont alimentées par des processeurs AWS Graviton2 basés sur l'ARM 64 bits. Les avantages prévus en termes de performances et de prix par rapport à la dernière génération d'instances AWS x86-64 sont trop impressionnants pour être ignorés.
Alors que nous pourrions simplement utiliser Docker standard sur ARM pour construire des images pour ces nouveaux processeurs AWS Graviton, il y a de nombreux avantages à prendre en charge les deux architectures plutôt que d'abandonner le navire x86-64 :
- Les développeurs doivent pouvoir exécuter localement les images Docker générées par CI/CD. Dans un avenir proche, les machines de développement continueront à utiliser des processeurs x86-64.
- Partagez des conteneurs communs entre les clusters x86-64 et Graviton2.
- Exécutez des environnements d'essai sur ARM et de production sur x86-64 jusqu'à ce que Graviton2 soit sorti de la phase de prévisualisation.
- Une fois que les Graviton2 seront disponibles, revenez rapidement à la norme x86-64 si une migration de service vers ARM pose des problèmes.
La construction d'images Docker multi-architectures est encore une fonctionnalité expérimentale. Cependant, l'hébergement d'images multi-architectures est déjà bien pris en charge par le registre de Docker, à la fois en auto-hébergement et sur hub.docker.com. Votre kilométrage peut varier avec les implémentations de registres Docker tiers.
Dans cet article, nous allons montrer comment construire et publier des images Docker multi-architectures sur un hôte ARM Linux pour x86-64 (AMD64) et ARM64 afin que vous puissiez exécuter un conteneur Docker à partir de l'image sur l'une ou l'autre des architectures.
Remarque: si vous pouvez construire vos images sur votre bureau macOS ou Windows, Docker Desktop est livré avec la prise en charge de la construction d'images Docker multi-architectures. Cependant, si vous utilisez Linux, ou si vous voulez construire vos images Docker correctement, dans le cadre de votre pipeline CI/CD, lisez la suite.
Installer Docker 19.03 ou plus récent
Pour commencer, nous allons avoir besoin d'un hôte Linux ARM64 capable d'exécuter Docker 19.03 ou une version ultérieure. Vous pouvez également utiliser un hôte x86-64.
Cependant, comme nous cherchons à bénéficier des économies de coûts de l'ARM, nous en utiliserons un comme serveur de construction avec Ubuntu 19.10. Ubuntu est une distribution Linux populaire supportée par de nombreux services cloud, mais d'autres distributions récentes devraient également fonctionner. Cependant, vous devrez vous assurer que vous utilisez un noyau Linux 5.x ou plus récent. Sur AWS, vous pouvez utiliser l'AMI Ubuntu 19.10.
Sur Ubuntu, installez docker.io
pour le dépôt Ubuntu. Nous installons également binfmt-support
et qemnu-user-static
. QEMU permet à un seul hôte de construire des images pour plusieurs architectures et binfmt-support
ajoute la prise en charge de plusieurs formats binaires au noyau Linux. Notez que binfmt-support
version 2.1.43 ou ultérieure est nécessaire.
Ajoutez votre utilisateur au groupe Docker pour que les commandes puissent être exécutées à partir de votre compte utilisateur. N'oubliez pas de redémarrer ou de vous déconnecter et de vous reconnecter après l'exécution :
1. #!/bin/bash #Installer Docker et les dépendances mult-arch
2.
3. sudo apt-get install binfmt-support qemu-user-static
4. sudo apt-get install docker.io
5. sudo usermod -aG docker $USERp
6. sudo reboot
Installer Docker Buildx
Ensuite, nous devons installer la commande buildx
de Docker. Buildx est un aperçu technologique et offre des fonctionnalités de construction expérimentales telles que les constructions multi-architectures. Si vous avez activé l'exécution de Docker en tant qu'utilisateur, vous pouvez l'installer en tant qu'utilisateur normal, plutôt qu'en tant que root.
Installez le plugin de ligne de commande buildx
pour Docker. Le code ci-dessous installera la dernière version pour ARM 64 bits.
1. #!/bin/bash
2. #Installer buildx pour arm64 et activer le plugin Docker CLI
3.
4. sudo apt-get install jq
5. mkdir -p ~/.docker/cli-plugins
6. BUILDX_URL=$(curl https://api.github.com/repos/docker/buildx
/releases/latest | jq -r .assets[].browser_download_url | grep arm64
7. wget $BUILDX_URL -O ~/.docker/cli-plugins/docker-build
8. chmod +x ~/.docker/cli-plugins/docker-buildx
Construire des images multi-architectures
La construction d'images multi-architectures (la documentation de Docker parle d'images multi-plateformes) nécessite un constructeur soutenu par le pilote
docker-container
et prend en charge deux stratégies de construction d'images multi-plateformes :
- Utilisation du support d'émulation QEMU dans le noyau
- Construction sur plusieurs nœuds natifs coordonnée par un seul constructeur
Ici, nous utilisons l'approche QEMU car c'est la moins chère des deux options, puisqu'elle ne nécessite qu'un seul hôte de construction pour toutes les architectures ciblées. De plus, Docker n'utilise pas QEMU ici pour créer des machines virtuelles entièrement fonctionnelles. Nous utilisons le mode utilisateur de QEMU, donc seuls les appels système doivent être émulés.
Au fur et à mesure de l'évolution de vos besoins en matière de CI/CD, vous souhaiterez peut-être investir dans une ferme de construction composée de nœuds natifs afin d'accélérer le processus de construction.
Créons un bootstrap le constructeur, vous pouvez lui donner le nom que vous voulez :
1. $ docker buildx create --name mbuilder
2. mbuilder
3.
4. $ docker buildx use mbuilder
5.
6. $ docker buildx inspect --bootstrap
7. Nom : mbuilder
8. Driver : docker-container
9.
10. Nœuds :
11. Nom : mbuilder0
12. Endpoint : unix:///var/run/docker.sock
13. Statut : en cours d'exécution
14. Plateformes : linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le,
linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
Parfait, nous avons maintenant un constructeur capable de cibler linux/arm64 linux/amd64, et d'autres architectures !
Notez que l'image dont vous vous inspirez doit également prendre en charge les architectures que vous envisagez de cibler. Ceci peut être vérifié en utilisant :
$ docker buildx imagetools inspect alpine
Dockerfile :
FROM alpine
RUN apk add util-linux
CMD ["lscpu"]
$ docker buildx build --platform linux/amd64,linux/arm64 -t foo4u/demo-mutliarch:2 --push .
[+] Building 4.7s (9/9) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile : 31B
=> [interne] load .dockerignore
=> => transfering context : 2B
=> [linux/amd64 interne] load metadata for docker.io/library/alpine:latest
=> [linux/arm64 interne] load metadata for docker.io/library/alpine:latest
=> [linux/amd64 1/2] FROM docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> => resolve docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db7826 => CACHED [linux/amd64 2/2] RUN apk add util-linux
=> [linux/arm64 1/2] FROM docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> => resolve docker.io/library/alpine@sha256:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> CACHED [linux/arm64 2/2] RUN apk add util-linux
=> exporting to image
=> => exporting layers
=> => exporting manifest sha256 :cb54200a7c04dded134ca9e3e6a0e434c2fdf851fb3a7226941d0983ad5bfb88
=> => exporting config sha256 :307b885367f8ef4dc443dc35d6ed3298b9a3a48a846cf559a676c028a359731b
=> => exporting manifest sha256:6f4fe17def66ef5bc79279448e1cb77a1642d460ed58d5dc60d0e472c023e2eb
=> => exporting config sha256:26e6b092c7c1efffe51ce1d5f68e3359ab44152d33df39e5b85cd4ff6cfed3d4
=> => exporting manifest list sha256 :3b4e4135b92017e5214421543b813e83a77fcea759af8067c685b70a5d978497
=> => pushing layers
=> => pushing manifest for docker.io/foo4u/demo-mutliarch:2
Il se passe beaucoup de choses ici, alors décortiquons-les :
1. Docker transfère le contexte de construction à notre conteneur de construction
2. Le constructeur construit une image pour chaque architecture que nous avons demandée avec l'argument --platform
3. Les images sont poussées vers Docker Hub
4. Buildx génère un fichier JSON de manifeste qui est poussé vers Docker Hub en tant que balise d'image.
Utilisons imagetools
pour inspecter l'image Docker générée:
1. $ docker buildx imagetools inspect foo4u/demo-mutliarch:2
2. Name : docker.io/foo4u/demo-mutliarch:2
3. MediaType : application/vnd.docker.distribution.manifest.list.v2+json
4. Digest : sha256:3b4e4135b92017e5214421543b813e83a77fcea759af8067c685b70a5d978497
5.
6. Manifeste :
7. Name: docker.io/foo4u/demo-mutliarch:2@sha256:cb54200a7c04dded134ca9e3e6a0e434c2fdf851fb3a7226941d0983ad5bfb88
8. MediaType : application/vnd.docker.distribution.manifest.v2+json
9. Plate-forme : linux/amd64
10.
11. Nom : docker.io/foo4u/demo-12. mutliarch:2@sha256:6f4fe17def66ef5bc79279448e1cb77a1642d460ed58d5dc60d0e472c023e2eb
12. MediaType : application/vnd.docker.distribution.manifest.v2+json
13. Plateforme : linux/arm64
Ici, nous pouvons voir que foo4u/demo-multiarch:2
est un manifeste JSON pointant vers les manifestes pour chacune des plateformes que nous avons ciblées pendant la construction. Bien que l'image apparaisse sur le registre comme une seule image, il s'agit en fait d'un manifeste contenant des liens vers les images spécifiques à chaque plateforme. Buildx a construit et publié une image par architecture et a ensuite généré un manifeste les reliant entre elles.
Docker utilise ces informations lors de l'extraction de l'image pour télécharger l'image appropriée à l'architecture d'exécution de la machine.
Exécutons l'image sur x86-64 / amd64 :
$ docker run --rm foo4u/demo-mutliarch:2
Impossible de trouver l'image 'foo4u/demo-mutliarch:2' localement
2 : Pulling from foo4u/demo-mutliarch
e6b0cf9c0882 : Existe déjà
Statut : Téléchargement d'une image plus récente pour foo4u/demo-mutliarch:2
Architecture : x86_64
Exécutons maintenant l'image sur arm64 :
$ docker run --rm foo4u/demo-mutliarch:2
Impossible de trouver l'image 'foo4u/demo-mutliarch:2' localement
2 : Pulling from foo4u/demo-mutliarch
Status : Téléchargement d'une image plus récente pour foo4u/demo-mutliarch:2
Architecture : aarch64
Voilà, c'est fait ! Nous avons maintenant une image Docker entièrement fonctionnelle que nous pouvons exécuter sur nos serveurs x86-64 existants ou sur nos nouveaux serveurs ARM 64 !
En conclusion, il n'est pas si difficile de commencer à utiliser des images Docker multi-architectures sur Linux. Nous pouvons même utiliser un serveur ARM pour construire les images, ce qui peut nous permettre d'économiser de l'argent sur nos serveurs CI/CD ainsi que sur notre infrastructure de staging et de production.
Bonus: vous pouvez encore optimiser vos compilations Docker si le langage que vous utilisez a un bon support multi-architecture (comme Java ou Go). Par exemple, vous pouvez construire une application Spring Boot avec une compilation sur une seule plateforme:
1. FROM --platform=$BUILDPLATFORM amazoncorretto:11 as builder
2.
3. COPY . /srv/
4. WORKDIR /srv
5. RUN ./mvnw -DskipTests=true package spring-boot:repackage
6.
7. FROM amazoncorretto:11
8.
9. COPY --from=builder /srv/target/my-service-0.0.1-SNAPSHOT.jar /srv/
10.
11. EXPOSE 8080
12.
13. ENTRYPOINT ["java", "-jar", "/srv/my-service-0.0.1-SNAPSHOT.jar"]