Kaniko, un outil open-source développé par Google, prend le contrepied de cette approche, en permettant de construire directement et sans risque nos images au sein d’un cluster Kubernetes.
Faisons ensemble un premier pas vers une CI/CD 100% K8s.
Le principe de construction est simple : à partir d’un fichier descripteur (le Dockerfile), le daemon Docker, via la ligne de commande docker build, construit, couche par couche (layer) une image de conteneur et la stocke localement. La commande docker push vous permettra ensuite de publier l'image dans une registry.
Cette construction locale sert à la mise au point de vos conteneurs et est vite remplacée par un outil d’intégration continue (CI), qui effectue une tâche similaire (bien souvent là aussi à l’aide de la CLI Docker) de manière automatisée et répétable, en se basant sur le Dockerfile que vous aurez poussé dans un gestionnaire de sources.
Garder cette création en local, c’est s’exposer à des risques importants de type Bus Factor. Nous déconseillons bien sûr formellement cette approche.
Les principaux outils de CI proposent de démarrer un worker contenant le daemon Docker. Le build de votre image est donc identique en tout point à celui que vous avez exécuté sur votre machine et apporte la garantie que votre image finale sera identique dans les deux cas.
Mais que se passe-t-il si nous ne souhaitons pas payer pour cet élément d’infrastructure supplémentaire ? Comment faire pour maximiser l’utilisation de mes ressources, et utiliser mon cluster K8s hors prod en tant que worker ? Kubernetes fait tourner des architectures de conteneurs, cela devrait être possible. Plusieurs outils de CI proposent directement cette option, comme GitLab (documentation officielle).
Comment faire tourner Docker dans un conteneur ?
Il existe une image officielle Docker contenant… docker ! Parfait ! Déployons cette image sur notre cluster, dans un pod, et exécutons le build à dans cette image !
Malheureusement, tout n’est pas si simple. Comme vous pouvez le constater vous-même sur notre environnement de démo, la commande docker build ne fonctionne pas à l’intérieur d’un conteneur Docker. Le conteneur contient uniquement la ligne de commande (la CLI) et pas le daemon. Vous ne disposez donc pas du moteur d’exécution nécessaire à l’application des différentes lignes du Dockerfile.
Pourtant, K8s dispose de ce daemon. Lui faire exécuter des instructions devrait donc être possible.
En cherchant un peu (Stack Overflow mon amour), vous devriez rapidement tomber sur une solution nommée Docker in Docker (DinD), qui, à l’aide d’un volume “magique” monté dans votre conteneur, vous donne accès au daemon Docker de la machine hôte.
La CLI envoie des instructions au daemon via une socket, présente sur le disque de l’hôte. Partager ce socket à l’aide d’un binding de volume, permet à notre CLI DinD d’interagir avec le daemon Docker. Nous pouvons maintenant faire un docker build fonctionnel en DinD.
L’autre solution consiste à utiliser un conteneur avec des privilèges élevés, ce qui, dans les faits, est équivalent (les privilèges élevés permettent d’interagir avec l’hôte sous-jacent).
Attention, c'est une très mauvaise solution :
En bref, ce mode de fonctionnement est à proscrire au plus vite.
Kaniko répond simplement à notre besoin : à partir de sources variées, il permet de bâtir des images de conteneurs, et de les pousser dans un registry, en quelques lignes de paramétrage.
Il suffit d’exécuter le conteneur gcr.io/kaniko-project/executor (hébergé sur une registry GCP), de lui passer un --context contenant votre Dockerfile, vos fichiers source, permettant de bâtir votre image, et de lui donner en --destination une cible de registry.
Les contextes d’entrées sont variés :
Les destinations sont elles aussi riches :
Comment Kaniko parvient-il à construire des images, sans accès privilégié ? En utilisant au maximum les capacités du userspace.
L’image de base est téléchargée et extraite au sein du userspace, puis chaque instruction contenue dans le Dockerfile est exécutée au sein de ce même espace. A chaque étape, un snapshot du file system est réalisé, et sert de layer pour l’image en cours de construction.
L’image de base est finalement supprimée du userspace, et l’image finale poussée vers son registry cible.
Néanmoins, tout n’est pas parfait :
Pour améliorer les performances de Kaniko, nous vous recommandons d’utiliser des images de base légères, et de regrouper les commandes de type RUN ou ADD, ce qui minimise le nombre de snapshots intermédiaires.
D’autres projets open-source comme Buildah (issu de Podman) ou BuildKit (issu de Moby) permettent, comme Kaniko, de construire des images Docker sans accéder au daemon et sans disposer de droits root. A vous de choisir celui qui sied le mieux à vos besoins.
La documentation officielle de Kaniko est riche et pleine d’enseignements, et contient elle-même des liens vers des ressources externes.