Sommaire
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.
Construire une image de conteneur
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).
Construire une image sur K8s, le problème
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.
Construire une image sur K8s en utilisant DinD est une mauvaise idée
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 :
- Le support de Docker dans Kubernetes est abandonné depuis la V1.22 sortie en Décembre 2021.
- La sécurité de votre cluster pourrait être compromise… En effet, depuis le DinD, c’est le daemon de l'hôte qui est manipulable. Il est donc possible d’arrêter des conteneurs démarrés depuis K8s, ou, pire, permettre à des personnes mal intentionnées de démarrer leur propre conteneur directement sur votre infrastructure. C’est pour cette raison que cette option est généralement inenvisageable sur des clusters publics ou partagés.
En bref, ce mode de fonctionnement est à proscrire au plus vite.
Construire l'image Docker dans K8s avec Kaniko une solution dans les règles de l'art.
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 :
- Dockerfile
- Répertoire
- Bucket S3, Minio
- Git
Les destinations sont elles aussi riches :
- Docker Hub
- AWS ECS
- Google GCR
Kaniko règle les problèmes en reposant sur le userspace K8s
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 :
- en l’état, Kaniko n’est pas capable de construire des images Windows.
- les performances de Kaniko sont faibles, comparées à celles du build Docker classique. En particulier parce que Kaniko ne peut pas utiliser les images présentes dans le daemon Docker et doit systématiquement les télécharger !
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.
Quelques autres constructeurs d’images modernes à explorer
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.
Pour en savoir plus
La documentation officielle de Kaniko est riche et pleine d’enseignements, et contient elle-même des liens vers des ressources externes.