Kaniko, et Gitlab-ci le duo gagnant pour k3s sur ARM

ARM, ou les limites de la portabilité des conteneurs

Utiliser une architecture ARM, cela signifie devoir utiliser des binaires qui soient compilés pour fonctionner sur cette architecture. Si je vous dit qu’une image de conteneur embarque tous les binaires pour que le conteneur fonctionne, vous saisissez le problème de portabilité que pose l’ARM.

Effectivement, si vous souhaitez déployer une image de conteneur avec des binaires compilés en x86 sur une architecture ARM cela ne fonctionnera tout bonnement pas… Il faut donc prévoir des images particulières dédiées aux architecture ARM (on est face à l’une des limites de la portabilité apportée par les conteneur).

Différentes solutions existent et permettent de construire des images ARM, aujourd’hui je vous propose de parcourir deux outils que j’ai retenu pour leur simplicité et leur facilité de mise en place, Gitlab CI et Kaniko.

Gitlab-ci sur ARM

L’installation se fait de marnière simple en utilisant un chart helm fourni par Gitlab, je vous renvoie directement sur la documentation associée Gitlab Runner Helm Chart, Si vous avez lu mon article précédent (Voir l’article “On déploie k3s sur raspberry !), vous vous en doutez, dans mon cas je déploie le runner sur mon cluster k3s à base de raspberry.

La seule chose particulière à faire dans le cas de l’utilisation d’arm va consister à préciser que vous souhaiter utiliser des images de conteneurs ARM pour votre runner, ce qui donne :

runners:
  config: |
    [[runners]]
      environment = ["FF_GITLAB_REGISTRY_HELPER_IMAGE=1"]
      [runners.kubernetes]
        image = "arm64v8/ubuntu:16.04"
        helper_image = "registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:arm64-775dd39d"
      [runners.kubernetes.pod_labels]
        "app.kubernetes.io/instance" = "gitlab"    

Pour le reste, je vous laisser regarder la documentation pour ajuster au cas par cas en fonction de vos besoin (notamment les consommations de ressources). Une fois que c’est fait, vous avez alors des runners gitlab prêts à exécuter vos pipelines de CI sur un environnement ARM.

Kubernetes sans docker

Venons en maintenant au fait de ne pas avoir docker sur kubernetes. Dans la plupasrt des cas, vous n’aurez pas de soucis, et tout ce passera bien pour déployer vos application sans docker. En revanche il y a un cas qui peut poser un problème, c’est si vous utilisez notamment du “docker in docker” dans vos pipelines de CI dans un cluster kubernetes. Avec un autre moteur de conteneur cela ne fonctionnera tout simplement pas et il vous faut une autre solution.

Dans mon cas j’ai décidé d’utiliser Kaniko, Kaniko est une application en Go, qui est capable de construire des images de conteneurs sans avoir de dépendances avec un environnement d’éxécution de conteneurs. Vous devenez donc indépendant du moteur de conteneur utilisé dans votre cluster kubernetes.


Kaniko est une solution open-source qui fait partie des “GoogleContainerTools”, on y retrouve des projets intéressants comme Jib, Skaffold, ou les images de conteneur “distroless”.


Kaniko

Je vais prendre pour exemple ce blog, qui utilise hugo pour générer un site web depuis du code markdown. Malheureusement il n’y a pas d’image docker officielle fourni par hugo (même s’il en existe une supportée par la communauté). Et on trouve encore plus difficilement d’image ARM.

J’ai donc décidé d’utiliser ma propre image (le projet est disponible ici hugo-arm), pour ce faire j’ai un Dockerfile très simple, qui va simplement récupérer le binaire que je souhaite embarquer dans mon conteneur :

FROM arm64v8/alpine

RUN wget https://github.com/gohugoio/hugo/releases/download/v0.81.0/hugo_0.81.0_Linux-ARM64.tar.gz && \
    tar -zxvf hugo_0.81.0_Linux-ARM64.tar.gz && \
    chmod +x hugo && \
    mv hugo /usr/bin/hugo && \
    hugo version

Vous noterez l’utilisation d’une image de base ARM, et la récupération de la version ARM du binaire de hugo


Regardons maintenant comment nous allons construire cette image au sein de notre runner gitlab hébergé sur notre machine ARM, avec le pipeline suivant :

stages:
  - build

build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.5.1-debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE/hugo:latest

L’utilisation de kaniko se fait donc de manière très simple avec la commande suivante :

/kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE/hugo:latest

Les variables permettent de définir le contexte d’éxécution du build de l’image, l’emplacement du dockerfile à utiliser, et le nom de l’image qui sera générée à l’issue du build.

Cette image sera poussée sur le registre et sera donc utilisable sur n’importe quelle machine avec un processeur ARM, comme un Raspberry, un Mac avec processeur Apple M1, ou un serveur ARM chez votre cloud provider préféré.

Mais dans la vraie vie on a réellement ce genre de contrainte ?

Aujourd’hui, entre les annonces de la communauté Kubernetes (cf. Don’t Panic: Kubernetes and Docker) et celles d’Apple (cf. Apple Silicon M1 Chips and Docker), ces environnements et ces contraintes vont donc être de plus en plus présents dans les anneés à venir.

J’espère que cet article vous aura permis d’y voir plus clair si comme moi vous avez ces contraintes dans votre environnement. Si vous avez d’autres contraintes et que vous avez réussi a mettre en place des solutions, n’hésitez pas à me contacter, je serais ravi d’échanger avec vous sur le sujet.