On ne démontre plus l’intérêt d’exécuter les pipelines d’intégration continue dans des conteneurs Docker plutôt qu’en natif sur une machine. On y gagne l’isolation, l’optimisation et la multiplicité des environnements, ainsi que la dissociation entre le socle technique et les environnements d’intégration continue.

On peut adopter deux stratégies différentes dans un exécuteur Docker :

  • Utiliser des images génériques (par exemple, une « Debian Bullseye Slim ») et installer les outils nécessaires à la volée, en prologue des tâches d’intégration continue.

  • Utiliser des images spécialisées qui contiennent déjà tous les outils nécessaires.

La première approche est plus simple si tous les outils nécessaires sont disponibles sous la forme de paquets « natifs » pour la distribution considérée, car on peut alors les déployer via le gestionnaire de paquet standard. Mais même lorsque c’est le cas, on pénalise lourdement le temps d’exécution du pipeline en installant les paquets à la demande (i.e. à chaque exécution de la tâche) et on provoque des téléchargements répétés qui encombrent le réseau.

La seconde approche élimine ces inconvénients (les images sont prêtes à l’emploi), mais nécessite plus de travail en amont, car il faut préparer et générer ces images spécialisées. Et à chaque fois qu’on voudra faire évoluer l’environnement (ajout d’une dépendance, changement de version d’un outil, utilisation d’un outil supplémentaire, etc.), il faudra générer une nouvelle version des images affectées. Dans ces conditions, gérer les scripts de génération des images dans un dépôt Git et profiter de l’existence d’un outil d’intégration continue pour automatiser la génération des images devient vite une évidence. Le dépôt Git permet d’historiser les évolutions, et l’intégration continue, de décharger le développeur d’une tâche ingrate (la génération et le transfert des images).

Pourquoi utiliser Kaniko ?

Utiliser des images Docker sur mesure et automatiser leur génération améliore l’efficacité de l’intégration continue. Mais comme le pipeline est joué par un exécuteur de type Docker, lui faire générer une image consiste à exécuter « Docker dans Docker » (« Docker in Docker » ou « dind » pour les intimes). Cette approche impose une condition notoirement problématique : le conteneur exécutant Docker doit être privilégié. Cette propriété brise l’isolation et le conteneur peut alors avoir un accès complet au système sous-jacent (le système hôte) et le corrompre de manière délibérée ou fortuite.

Kaniko est un outil dédié à la génération des images Docker (à l’instar de Buildah, il ne peut pas exécuter des conteneurs Docker) et il présente plusieurs avantages sur l’approche « dind » :

  • Contrairement à Docker, Kaniko n’a pas besoin de privilège particulier sur le système pour générer les images, car il travaille uniquement en espace utilisateur. Il ne nécessite donc pas de conteneur privilégié, ce qui sécurise le système sous-jacent.

  • Kaniko crée un répertoire nommé /kaniko qu’il n’intègre pas à l’image qu’il génère. Nous pouvons donc y placer les informations confidentielles nécessaires à Kaniko, telles que les « credentials » d’accès à un registre d’images – information stockée dans le fichier /kaniko/.docker/config.json – sans en laisser de trace dans l’image générée. Là encore, la sécurité de notre infrastructure s’en trouve renforcée.

  • Kaniko est capable de générer des images Docker plus rapidement que la commande docker elle-même.

Nous avons donc tout intérêt à utiliser Kaniko plutôt que l’approche « dind » dans nos pipelines d’intégration continue. D’autant plus que Kaniko a été conçu et développé par Google, que son code source est diffusé sous licence libre et publié sur Github. Le tout constitue un gage de sérieux et de relative pérennité.

Je me permets une petite disgression pour vous signaler que si Kaniko n’a pas besoin en lui-même de Docker, dans la pratique, son déploiement en natif est compliqué. Début 2019, je n’ai pas réussi à le compiler sur une distribution Debian standard. Pour y parvenir, j’aurais dû installer manuellement plusieurs outils non disponibles dans les dépôts de Debian et « polluer » ainsi mon système. Du coup, j’ai toujours privilégié l’image Docker de Kaniko fournie par Google. Trois ans plus tard, l’essai pourrait être réitéré, mais il ne présente guère d’intérêt dans le contexte de mes projets.

Dans les faits, deux images de Kaniko sont disponibles :

La première est celle préconisée en production. La seconde est préconisée en phase de mise au point, car elle dispose d’outils supplémentaires, fournis par Busybox (shell et moult commandes externes). Dans la pratique, le besoin de la version debug se fait toujours sentir à un moment ou un autre, notamment lorsqu’il devient nécessaire d’invoquer des commandes fournies par le shell dans le prologue du job (section before_script des jobs de Gitlab CI). Dans le cadre du projet sur lequel j’interviens actuellement, nous pourrions nous contenter de la version de production si Gitlab CI savait masquer les variables multi-lignes intégrant l’ensemble des caractères que l’on peut trouver dans un fichier JSON. Ce n’est pas le cas et nous sommes donc obligés de créer de toute pièce dans la section before_script le fichier /kaniko/.docker/config.json, ce qui nécessite l’invocation de diverses commandes. Nous devons donc utiliser l’image executor:debug.

La mise en œuvre de Kaniko dans Gitlab CI étant très bien documentée sur le site de Gitlab et moult blogs en anglais et français, je vais m’épargner un tutoriel qui n’aurait guère d’intérêt.

J’ai utilisé Kaniko pour la première fois début 2019, lorsque j’ai déployé la plateforme d’intégration continue élastique du projet libre Orfeo Toolbox (OTB). Trois ans plus tard, force est de constater que l’outil ronronne et se fait oublier. Je vous invite à vous faire une idée sur pièce en consultant le pipeline d’intégration continue du projet otb-build-env qui génère les images utilisées par l’intégration continue d’OTB.

Promotion d’images Docker dans un registre

Le workflow en vigueur sur mon projet actuel fait que les images Docker peuvent, ultérieurement à leur génération :

  • recevoir de nouveaux tags (par exemple, de version, tels que 1.0 ou 2.3) ;

  • être migrées d’un dépôt de développement (testing) à un dépôt de production (stable).

Manipuler ainsi les métadonnées et l’emplacement de l’image existante plutôt que d’en générer une nouvelle est ce qu’on appelle « faire de la promotion d’image ».

Kaniko ne permet pas de promouvoir des images Docker. Nous avons donc dû trouver un outil complémentaire pour réaliser cette tâche. Notre choix s’est porté sur l’outil crane du projet go-containerregistry. Il est documenté et trivial à mettre en œuvre. Un exemple d’utilisation dans Gitlab CI est fourni dans le fichier README.md du projet.