Simplifier la création d’images de conteneur avec Ko
10 octobre 2022
0 minutes de lectureDans un article précédent, j’ai expliqué comment — et pourquoi — vous pourriez utiliser l’outil Jib du groupe Google Open Source pour créer des images de conteneur pour vos applications Java. Jib crée des images légères, basées sur la JVM et conformes à OCI, qui respectent les bonnes pratiques sans nécessiter de moteur d’exécution de conteneur comme Docker, et vous évite d’écrire et de gérer des Dockerfiles. Mais si vous développez des applications en Go ? Il existe un autre outil open source pour Go, qui fonctionne de façon similaire : Ko.
Remarque : Au cours de la rédaction de cet article, le projet Ko a été transféré d’un dépôt GitHub appartenant à Google vers sa propre organisation de premier niveau, « ko-build ». Nous avons mis à jour le texte de cet article en conséquence, mais vous pourrez encore trouver en ligne des références à « Google Ko » et ce nom pourra figurer dans des noms de modules pendant quelque temps. Rassurez-vous, il s’agit bien du même projet. Félicitations à l’équipe de Ko pour cette réussite qui a motivé le transfert !
Dans cet article, nous verrons comment utiliser Ko pour créer des images de conteneur sans Dockerfile, générer des SBOM et intégrer Kubernetes.
Qu’est-ce que Ko ?
Ko est un outil en ligne de commande fourni sous la forme d’un binaire unique, conçu pour remplacer l’exécution du compilateur go dans votre processus de développement. En plus de compiler votre application, il crée une image de conteneur ultralégère qui contient votre application. Comme Jib, Ko envoie l’image vers un registre ou la place dans le cache local d’images Docker, selon sa configuration et son mode d’exécution.
Ko propose également quelques fonctionnalités supplémentaires pour la création de nomenclatures logicielles (SBOM) et l’intégration à Kubernetes, afin de simplifier considérablement les cycles de développement et de déploiement itératifs.
Quels problèmes Ko cherche-t-il à résoudre ?
De nombreux développeurs, quel que soit le langage avec lequel ils travaillent, découvrent la création de conteneurs et se posent souvent beaucoup de questions sur la création d’images, par exemple :
Quelle image de base choisir, et est-elle conforme aux politiques de mon organisation ?
Comment combiner au mieux les commandes pour limiter le gonflement des couches ?
Quelle partie de mon application dois-je copier dans l’image pour pouvoir l’exécuter ?
Quels outils dois-je apprendre à utiliser pour créer l’image ? (Docker ? Buildah ? BuildKit ?)
Existe-t-il des annotations standard que je dois inclure conformément aux exigences de mon organisation ?
Les architectes et responsables d’applications souhaitent également faciliter l’adoption de la gouvernance et des normes par leurs équipes. Cependant, maintenir des pratiques uniformes peut s’avérer difficile lorsque chaque équipe a élaboré ses propres modèles de Dockerfile.
Les équipes de sécurité s’intéressent aussi de près au contenu des images et aux outils utilisés pour les créer. Par exemple, exposer le moteur Docker — ou le docker.sock de la machine hôte — à un nœud de compilation CI peut accorder des privilèges élevés aux environnements de compilation sur ces nœuds.
« Le démon [Docker]… dispose de nombreuses fonctionnalités qui vont bien au-delà de la création d’images et de l’interaction avec les registres. Sans outils de sécurité supplémentaires, tout utilisateur capable de lancer une commande docker build sur cette machine peut également exécuter une commande docker run pour lancer n’importe quelle commande sur la machine… Non seulement il peut lancer les commandes de son choix, mais s’il utilise ces privilèges pour mener une action malveillante, il sera difficile de déterminer qui en est responsable. »
— Container Security de Liz Rice, chapitre 6
À l’inverse, l’introduction de nouveaux outils de compilation peut être complexe et allonger la courbe d’apprentissage des personnes responsables des systèmes de compilation. Ko vise à répondre à chacun de ces enjeux, tout en offrant une expérience optimale aux développeurs qui l’utilisent.
Créer des images avec et sans Ko
Pour comprendre comment Ko relève les défis ci-dessus, commençons par comparer son fonctionnement aux étapes habituelles de compilation Go et Docker.
Créer une image sans Ko
Pour cet exemple, nous allons créer l’application classique du tutoriel Go : l’application Web « Hello World ».
1. Dans un répertoire vide, créez le fichier suivant sous le nom hello.go :
2. Nous allons compiler le binaire dans notre Dockerfile, une pratique courante :
Il s’agit d’une compilation en plusieurs étapes. La première étape, nommée build, est basée sur l’image officielle golang. Nous y copions le contenu de notre application (pour l’instant, il s’agit simplement du fichier hello.go) dans l’image, sous /go/src, puis exécutons l’outil go build avec les paramètres nécessaires pour produire un binaire statique.
Pour la deuxième étape, nous partons de l’image de base static:nonroot du projet Google Distroless, définissons nonroot comme utilisateur par défaut, copions le binaire hello depuis l’étape build dans le répertoire racine, définissons des métadonnées sur le port à exposer et indiquons que ./hello doit être exécuté par défaut au démarrage du conteneur.
Pourquoi deux étapes ?
Il est courant de compiler votre application dans le Dockerfile, car cela garantit que la même version du compilateur est utilisée sur le poste de développement et dans les compilations automatisées. Une erreur fréquente consiste toutefois à déployer l’image qui a servi à la compilation. Cela représente un risque de sécurité, car l’image déployée contient alors non seulement des outils superflus comme le compilateur, mais aussi le code source de votre application. Si un attaquant parvient à exploiter une vulnérabilité ou à obtenir une copie de votre image, il disposera de nombreuses informations et de nombreux outils pour étendre son attaque. En partant de l’image de base distroless/static:nonroot, nous obtenons un système de fichiers extrêmement minimal, avec un utilisateur non root par défaut.
3. Une fois notre Dockerfile prêt, nous pouvons utiliser la commande docker build pour créer une image dans le cache local et lui attribuer une étiquette :
Remarque : Le résultat peut varier selon la version de Docker que vous utilisez et selon que les améliorations BuildKit sont activées ou non.
4. Pour que d’autres personnes puissent utiliser cette image, nous devons l’envoyer vers un dépôt de registre. Dans cet exemple, j’exécute un dépôt local sur mon poste, au port 5000, à l’aide de l’image registry:2 de DockerHub. (C’est pour cette raison que j’ai attribué à l’image l’étiquette avec le préfixe localhost:5000/.)
Avec Docker, envoyer une image vers un registre est assez simple : il suffit d’utiliser la commande docker push :
Remarque : S’il s’agissait d’un registre géré, je devrais d’abord m’y authentifier avec docker login.
5. Enfin, pour exécuter notre image, nous pouvons utiliser docker run ou un déploiement kubectl approprié. Par souci de simplicité, nous allons choisir la première option :
et nous pouvons tester l’application avec une simple commande curl :
Voici à quoi pourraient ressembler les étapes que nous venons de suivre, représentées sous forme de schéma :

Revenons maintenant au début pour voir comment procéder avec Ko.
Créer une image avec Ko
1. Commençons par le même fichier hello.go, dans un répertoire vide :
2. Ensuite, définissons le registre d’images dans une variable d’environnement et exécutons ko build :
Cette commande :
a compilé notre application en un binaire statique ;
l’a intégrée dans une image bien configurée ;
et l’a envoyée vers mon registre.
Aucun Dockerfile ni moteur d’exécution de conteneur n’a été nécessaire. Vous remarquerez également dans le résultat des références à la création et à la publication d’une SBOM ; nous y reviendrons plus tard.
Remarque : L’étiquette de l’image contient ici un hachage MD5 qui correspond, par défaut, au chemin d’importation de votre application Go. Comme nous n’avons pas de module Go complet dans cet exemple, ce hachage est simplement calculé à partir de hello.go. Consultez la documentation de Ko pour en savoir plus sur les étiquettes.
3. Nous allons maintenant exécuter l’image avec docker run :
... et la tester avec curl :
Voici à quoi pourrait ressembler le processus Ko sous forme de schéma :

En confiant à Ko les principales étapes de création, de configuration et d’envoi des images, nous avons réduit le nombre d’étapes à effectuer, la dépendance au moteur d’exécution de conteneur au moment de la compilation, ainsi que le niveau d’expertise et les besoins de maintenance associés au Dockerfile.
Des valeurs par défaut judicieuses, des adaptations déterministes
Les utilisateurs de Ko bénéficient des bonnes pratiques définies collectivement par la communauté open source. Mais que faire si les normes propres à votre organisation ne sont pas compatibles avec celles-ci ? Les options de ligne de commande de Ko et/ou son fichier de configuration ko.yaml peuvent alors vous être utiles.
Image de base personnalisée
Supposons, par exemple, que votre entreprise exige une image spécifique comme base pour toutes les applications Go. Il vous suffit d’ajouter une ligne defaultBaseImage: dans un fichier ko.yaml placé dans le répertoire racine, et d’y indiquer cette image.
Options du compilateur Go
Pour spécifier explicitement les options -ldflags du compilateur Go, comme dans notre exemple initial, ajoutez simplement une entrée builds: au même fichier ko.yaml, avec les paramètres appropriés indiqués dans la documentation de Ko.
Étiquettes Docker
Les étiquettes Docker, ou annotations OCI, permettent d’ajouter à une image des métadonnées utiles pour référencer des informations telles que le dépôt source d’origine, la compilation CI qui l’a créée ou toute autre donnée que votre équipe souhaite intégrer. Pour en savoir plus sur les étiquettes d’image et les cas où elles peuvent être utiles, consultez mon article Comment et quand utiliser les étiquettes Docker.
Ko permet d’ajouter des étiquettes avec l’option de ligne de commande --image-label :
Remarque : À la date de publication de cet article, il n’est pas possible de définir des étiquettes d’image dans le fichier de configuration .ko.yaml, mais une demande ouverte propose d’ajouter cette fonctionnalité.
Autres fonctionnalités intéressantes de Ko
SBOM des images
Comme nous l’avons vu plus haut, Ko génère et publie automatiquement une SBOM pour votre nouvelle image de conteneur. Par défaut, elle est au format SPDX, mais vous pouvez aussi choisir CycloneDX en ajoutant l’option --sbom=cyclonedx à la ligne de commande. Vous pouvez également désactiver cette fonctionnalité avec l’option --sbom=none. Vous trouverez ces paramètres et d’autres détails de configuration dans la documentation de Ko.
Une présentation détaillée des SBOM et de leur rôle dans la sécurisation de la chaîne d’approvisionnement dépasse le cadre de cet article. Pour en savoir plus, consultez Créer des SBOM pour sécuriser la chaîne d’approvisionnement open source.
Intégration à Kubernetes
Si vous devez tester votre image dans un cluster Kubernetes, vous avez certainement déjà rencontré les contraintes liées à la répétition de tâches telles que :
Créer mon image
La déployer dans mon registre (ou la charger d’une autre manière dans le cluster Kubernetes)
Mettre à jour le fichier YAML de mon déploiement avec le nouveau tag d’image
Redéployer avec
kubectl
Ko simplifie considérablement ces opérations en les automatisant dans une seule commande : ko apply. Il suffit de remplacer l’étiquette de l’image dans le fichier YAML de votre déploiement par une valeur au format spécial ko://. ko apply effectuera alors automatiquement toutes les étapes ci-dessus et déploiera l’image sur le cluster actuellement sélectionné dans le contexte de configuration de votre kubectl.
Imaginons, par exemple, que vous disposiez d’un environnement de test ou d’un cluster Kubernetes local et que vous deviez déployer et tester votre travail au fur et à mesure. Voici un manifeste de déploiement pour notre application « hello » :
Vous pouvez voir que la ligne image: contient ko://, suivi du chemin vers le module Go de notre application sur GitHub.
Il ne nous reste plus qu’à exécuter ko apply en lui indiquant le fichier YAML avec l’option -f, comme nous le ferions avec kubectl :
Nous pouvons maintenant tester notre application, la modifier et relancer ko apply -f myfile.yaml autant de fois que nécessaire. Ko se charge de tout le travail pour vous !
Comme vous pouvez vous y attendre, d’autres commandes Kubernetes pourraient également vous être utiles, notamment :
deletepour supprimer vos déploiements ; etresolvepour générer du YAML que vous pouvez transmettre àkubectlou à d’autres outils.
Consultez la https://github.com/ko-build/ko#kubernetes-integration pour accéder à la documentation complète.
Les défis liés à l’utilisation de Ko
Nous avons évoqué toutes les raisons qui pourraient vous inciter à adopter Ko dans vos workflows. Mais avant de vous lancer, parlons de certains défis que vous pourriez rencontrer en utilisant Ko.
Problématiques multiplateformes
Si vous développez sur une machine qui n’est pas basée sur Linux, l’absence d’un moteur de conteneurs peut compliquer quelque peu les choses. Une image de build basée sur un conteneur vous permet d’intégrer les outils de build ainsi que le système d’exploitation et ses bibliothèques dans une image de base commune, et de ne pas vous soucier du fait que la plateforme de votre poste de travail diffère peut-être de celle de votre environnement de production.
Prenons un exemple : la partie Go+Docker de l’exemple ci-dessus fonctionnera sur pratiquement toutes les plateformes, car, quel que soit l’endroit où vous effectuez le build, l’image de base golang fournit les bibliothèques Debian glibc nécessaires au compilateur Go pour créer le binaire statique. En revanche, si vous essayez d’exécuter l’exemple Ko avec les mêmes options ldflags sur une machine MacOS, vous obtiendrez des erreurs signalant l’absence de bibliothèques nécessaires aux liaisons statiques.
En effet, MacOS/Darwin ne comprend pas les bibliothèques nécessaires à la compilation croisée d’un binaire Linux statique (du moins au moment où j’écris ces lignes). Sans vouloir trop m’en prendre à Apple, des problèmes similaires peuvent se poser avec des architectures de processeur différentes sur les postes de travail Linux et Windows WLS.
Érosion des compétences
Les compétences et les connaissances nécessaires à la création et à la maintenance d’images de conteneurs bien conçues sont précieuses. L’utilisation d’outils qui en font abstraction ou les rendent inutiles peut réduire votre capacité à résoudre les problèmes en cas de défaillance. De fait, moins il y a de personnes dans une équipe qui maîtrisent les technologies de conteneurs, plus celle-ci dépend des personnes expérimentées pour le dépannage et la maintenance en cours d’exécution. Dans les cas extrêmes, cela peut entraîner un épuisement professionnel, des départs et/ou des pannes.
Relâchement de la vigilance en matière de sécurité
Si la création des images de conteneurs est entièrement automatisée et échappe au contrôle des développeurs, le respect de processus tels que l’analyse des vulnérabilités des images peut en pâtir. Des vulnérabilités peuvent s’introduire dans des images, des packages, des bibliothèques, etc. qui n’ont pas été mis à jour, exposant ainsi votre application aux attaques. Veillez donc à ce que ces analyses soient toujours effectuées par d’autres moyens automatisés, tels que :
Scripts de build ou Makefiles
Hooks Git
Étapes de build dans la CI
Si vous n’analysez pas encore vos images, Snyk propose gratuitement l’analyse des images de conteneurs pour détecter les vulnérabilités et vous fournir des conseils simples et pratiques pour y remédier.
Simplifier les images de conteneurs
Ko, comme d’autres outils de création d’images, peut considérablement simplifier la création d’images de conteneurs pour vos applications Go et aider vos équipes à rationaliser et à standardiser leur processus de création d’images. L’un des principaux avantages concerne vos outils de CI : vous n’avez plus besoin d’exposer un moteur de conteneurs à votre environnement de build, ce qui réduit considérablement les vecteurs d’attaque liés aux accès privilégiés dans votre SDLC. Que vous choisissiez ou non un outil de build comme Ko, veillez à ce que vos équipes maîtrisent les technologies de conteneurs ainsi que les risques de sécurité liés aux processus et aux outils utilisés pour créer leurs images.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
