La majorité des clouds providers proposent des solutions basées sur des architectures processeurs ARM, tel que Graviton chez AWS ou Tau T2A chez GCP. Les avantages de tels processeurs sont multiples : efficacité énergétique, couts réduits, performances… Ils sont de plus tout à fait adaptés aux environnements conteneurisés.
Exécuter vos workloads Kubernetes sur des processeurs ARM parait donc être un bonne idée. Cela rentre aussi dans une approche FinOps, car l’utilisation de processeurs arm en lieu et place de processeurs x86 va permettre des réductions de coûts non négligeables (de l’ordre de 20% avec des processeurs Graviton) à performances égales voire supérieures.
Si la mise en place d’une architecture arm peut paraitre assez simple sur de nouveaux clusters, qu’en est-il de la migration d’une architecture existante amd64 vers une architecture arm64 ?
Considérations
Avant de se lancer dans la migration vers une architecture arm, quelques considérations sont à prendre en compte vis-à-vis des applications qui tournent sur votre cluster.
Langages et librairies
La majorité des langages supportent maintenant des architectures ARM. Les langages interprétés (NodeJS, Python…) ou byte-code compilés (Java, .Net) devraient fonctionner sans modifications majeures. Attention cependant si vous utilisez des librairies ou fragments de codes natifs (JNI), une recompilation sera necessaire.
Les langages compilés (C/C++, Go…) supportent pour la plus grande majorité les architectures ARM mais ils devront être recompilés.
Images Docker
De manière générale nos applications packagées pour s’executer dans des conteneurs utilisent une image de base (le FROM du Dockerfile). Attention à bien vérifier que cette image aussi supporte ARM. C’est le cas de la majorité des standards et cela peut se vérifier rapidement en se connectant sur le registry depuis lequel elles sont tirées. Par exemple pour l’image officielle OpenJDK sur Dockerhub, on remarque que les 2 types d’architectures sont bien supportées :
Services tiers
Des services tiers tels que Prometheus ou ArgoCD peuvent aussi tourner sur nos cluster Kubernetes afin d’assurer diverses taches (observabilité, déploiement, sécurité…). Il faudra donc s’assurer là aussi que ces services sont déployables sur une architecture ARM.
Système d’exploitation
Si vous utilisez des services cloud entièrement managés tel que EKS Fargate ou GKE Autopilot il n’y aura aucun impact. Par contre si vous avez des noeuds que vous managez vous-même, une migration du système d’exploitation sera nécessaire.
Construction : Docker multi-architecture
Maintenant que vous vous êtes assuré que vos applications sont bien éligibles à une plateforme arm, il s’agit de les reconstruire afin qu’elles puissent tourner sur ce type d’architecture.
Principe
La meilleure solution pour pouvoir instancier vos conteneurs sur une architecture ARM n’est pas d’effectuer un build de vos images pour ce type d’architecture spécifiquement, mais plutôt d’utiliser une méthode de build multi-architecture. Votre image sera alors construite en même temps et à partir de la même source (DockerFile) pour une liste d’architecture que vous aurez prédéfinie.
Cela permettra de déployer ces nouvelles images sur vos clusters nouvellement configurés avec des noeuds arm, mais aussi de pouvoir lancer indifféremment vos containers sur des architectures plus classiques type amd64 pour du développement ou des tests par exemple.
Ce sera le runtime de conteneur qui, au moment du pull, récupérera les layers de l’image correspondant au type d’architecture sur lequel il tourne :
Mise en place
Solution 1 : Docker buildx
Une approche courante pour construire des images Docker multi-architecture consiste à utiliser le plugin docker buildx (https://docs.docker.com/build/architecture/#buildx).
Ce plugin se base sur QEMU (Quick Emulator) pour construire des images multi-architectures. QEMU est un émulateur de processeur qui permet d'exécuter du code destiné à une architecture spécifique depuis un autre type d’architecture. Cela va permettre de construire des images Docker pour des architectures différentes de celle de l'hôte.
Concrètement, tout ce que vous aurez à faire est d’installer le plugin docker buildx (vérifier la compatibilité avec votre version de docker), et de lancer un build en listant les plateformes cibles :
docker buildx create --use --name mybuild node-amd64
mybuild
docker buildx create --append --name mybuild node-arm64
docker buildx build --platform linux/amd64,linux/arm64 .
Le build et le push de l’image peuvent se faire avec une seule et même instruction :
docker buildx build --tag my-user/my-image --platform linux/arm64/v8,linux/amd64 --push .
Pour plus de détails vous pouvez vous référer à la procédure Docker: https://docs.docker.com/build/building/multi-platform/
Solution 2 : Manisfest
Une seconde solution, plus complexe, est la méthode “Do It Yourself”. Elle consiste à une création manuelle du manifest d'image après avoir effectué 2 builds, un pour chaque type d’architecture.
Elle fait aussi intervenir 3 registres d’images, car chacune des images construites doit être poussée dans son propre registre avant d’être poussée une 3ème fois dans un registre multi-architecture.
Donc pour résumer :
1 - On construit et on pousse une image pour chaque architecture
# AMD64
$ docker build -t my-user/my-image-amd64 --build-arg ARCH=amd64/ .
$ docker push my-user/my-image-amd64
# ARM64V8
$ docker build -t my-user/my-image-arm --build-arg ARCH=arm64v8/ .
$ docker push my-user/my-image-arm
2 - On créé un manifeste à partir de chacune des images
docker manifest create \
my-user/my-image \
--amend my-user/my-image-amd64 \
--amend my-user/my-image-arm
3 - On pousse le nouveau manifest
docker manifest push my-user/my-image
Remarque : il est aussi possible de jouer sur les tags des différentes images pour n’utiliser qu’un seul registry
Cette solution peut être utile si vous construisez vos images avec un autre outils que Docker, tels que Kaniko ou Buildah.
Un post sur le blog de Docker détaille ces 2 méthodes: https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/
Hosting : Cluster Kubernetes Hybride
Vous avez maintenant des images Docker multi-architectures capable de tourner indifféremment sur de l’amd64 ou de l’arm. Mais il est possible que pour une raison ou une autre, certaines de vos applications n’aient pas pu être construites pour architecture arm et que vous deviez donc conserver des noeuds type amd64 pour celles-ci.
Dans ce cas pas de panique, vous pouvez jouer sur les teintes et node selector (ou node affinity) de Kubernetes.
Je m’explique. Les clusters Kubernetes managés sont capables de gérer plusieurs groupes de noeuds, chacun de ces groupes pouvant s’appuyer sur des propriétés différentes (type d’instance, nombre d’instances…). Il est alors tout à fait possible de créer 2 groupes de noeuds distincts, l’un comportant des machines de type amd64 et l’autre de type arm. Pour garder la maitrise sur les workloads qui vont être déployés par la suite sur l’un ou l’autre groupe de noeuds, on appliquera une teinte sur l’un des groupes :
(Si vous n’êtes pas familier avec la notion de teinte et de node selector je vous invite à consulter à la documentation Kubernetes https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ et https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/)
Par défaut tous nos pods seront ainsi déployés sur le node group arm. On utilisera alors un node selector ainsi qu’une tolération au niveau des pods que l’on souhaite assigner à un type d’architecture amd64.
tolerations:
- effect: NoSchedule
key: node-arch-type
value: amd64
nodeSelector:
kubernetes.io/arch: amd64
Cela permet de continuer de faire tourner sans risque vos applications nos compatibles arm sur des noeuds amd64.
Remarque: on peut tout à fait envisager le mécanisme inverse, à savoir un déploiement par défaut sur des noeuds type amd64 (non teintés) et prévoir une sélection d’applications à déployer sur des noeuds type arm (grâce aux teintes et node selectors). Cela permet dans un cluster existant amd64, d’envisager une stratégie de migration de vos applications par lot.
Conclusion
Félicitations ! Vous avez maintenant un cluster Kubernetes capable d’héberger plusieurs types d’architectures, et des applications pouvant se déployer indifféremment sur l’une ou l’autre tout en gérant vous même cette répartition.
Ce type d'architecture de plus en plus répandu mérite vraiment de s'y intéresser, surtout dans le cas de la mise en place d'une nouvelle plateforme.
Pour ce qui est de la migration d'une plateforme existante, le ROI est de manière générale très intéressant mais une stratégie de migration doit impérativement être mise en place.