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'architecture 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 des environnements 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 création 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 vous 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 préférez créer vos images sur votre bureau macOS ou Windows, Docker Desktop est livré avec la prise en charge de la création 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.
Installez Docker 19.03 ou une version ultérieure
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 soutenue par de nombreux services en nuage, 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 une version 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 en avant-première technologique et offre des fonctionnalités de construction expérimentales telles que les constructions multi-architectures. Si vous avez activé docker pour qu'il soit exécuté 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 installe 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 :
- Utiliser le support d'émulation QEMU dans le noyau
- Construction sur plusieurs nœuds natifs coordonnée par un seul constructeur
Nous utilisons ici l'approche QEMU, qui est la moins chère des deux options, puisqu'elle ne nécessite qu'un seul hôte de compilation pour toutes les architectures ciblées. En outre, Docker n'utilise pas QEMU 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 constructeur bootstrap, 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. Point de terminaison : 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 .
[+] Construction 4.7s (9/9) FINIE
=> [interne] load build definition from Dockerfile
=> => transferring dockerfile : 31B
=> [interne] load .dockerignore
=> => transferring context : 2B
=> [linux/amd64 internal] load metadata for docker.io/library/alpine:latest
=> [linux/arm64 internal] 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:2171658620155679240babee0a7714f6509fae66898db422ad803b951257db78
=> 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
=> exportant vers l'image
=> => exportant les couches
=> => exportant le manifeste 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. Nom : 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. Plate-forme : 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 dans le registre comme une image unique, il s'agit en fait d'un manifeste contenant des liens vers les images spécifiques à la plate-forme. 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
C'est tout ! Nous disposons désormais d'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 nous permet d'économiser de l'argent sur nos serveurs CI/CD ainsi que sur nos infrastructures de staging et de production.
Bonus: vous pouvez optimiser davantage vos constructions Docker si le langage que vous utilisez dispose d'un bon support multi-architecture (comme Java ou Go). Par exemple, vous pouvez créer une application Spring Boot en compilant une seule plateforme :
1. FROM --platform=$BUILDPLATFORM amazoncorretto:11 as builder
2.
3. COPY . /srv/
4. WORKDIR /srv
5. Exécutez ./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. EXPOSER 8080
12.
13. ENTRYPOINT ["java", "-jar", "/srv/my-service-0.0.1-SNAPSHOT.jar"]