Créer des images de conteneur Java avec Jib
17 août 2021
0 minutes de lectureSi vous travaillez avec des images de conteneur depuis un certain temps, vous connaissez probablement ces fichiers omniprésents qui décrivent, couche par couche, les étapes nécessaires à la création d’une image : les Dockerfiles. Saviez-vous qu’il existe de plus en plus d’outils permettant de créer des images conformes à OCI sans Dockerfile ? Dans cet article, nous allons découvrir Jib, un outil entièrement développé en Java qui crée des images hautement optimisées sans que vous ayez à vous soucier de rédiger correctement un Dockerfile.
Je pars du principe que vous comprenez déjà la construction d’images et que vous avez au moins quelques notions des Dockerfiles. Si vous débutez, vous pouvez consulter mon article sur les workflows pilotés par les développeurs, dans lequel j’explique en détail la création d’images de conteneur.
Le problème
En tant que développeur Java, en plus de votre code applicatif et de vos dépendances de bibliothèques, vous avez sans doute l’habitude de suivre les versions du JDK et de la JVM que vous ciblez. Cela peut inclure la version du serveur d’applications Web sur lequel vous vous appuyez, ainsi que certaines configurations d’exécution, comme le réglage du ramasse-miettes et les connexions à la base de données. En revanche, vous n’avez peut-être pas eu à gérer des aspects liés au système d’exploitation, comme l’installation de paquets, les permissions du système de fichiers, l’UID/GID utilisé à l’exécution ou d’autres détails de configuration de ce type, traditionnellement pris en charge par les équipes des opérations et de la sécurité qui vous fournissent des plateformes.
Avec l’adoption des environnements d’exécution de conteneurs et des systèmes d’orchestration qui s’appuient sur eux, bon nombre de ces préoccupations de bas niveau relèvent désormais pleinement des équipes de développement, sous la forme de fichiers de configuration d’infrastructure as code (IaC) intégrés aux dépôts de code des applications et des services. Ces fichiers prennent différentes formes, comme le YAML de Kubernetes, le HCL de Terraform, le JSON de CloudFormation et, bien sûr, les Dockerfiles, qui définissent la structure de l’image de conteneur dans laquelle votre application s’exécutera.
Empaqueter votre application dans une image et la faire tourner dans un conteneur n’est généralement pas très difficile. En revanche, veiller à ce que l’image soit bien construite, optimisée, sécurisée et conforme aux normes de votre organisation peut s’avérer compliqué. Les développeurs doivent désormais se familiariser avec les outils d’analyse au niveau du système d’exploitation, respecter des normes d’étiquetage des images, apprendre les bonnes pratiques Dockerfile les plus récentes et assumer bien d’autres responsabilités. Des outils de lint et d’analyse comme Hadolint, Dockle et notre solution Snyk Container peuvent vous aider dans ces tâches. Mais si nous pouvions automatiser la plupart, voire la totalité, de ces nouvelles exigences et du code passe-partout ?
Voici Jib : un outil de création d’images de conteneur entièrement développé en Java
Jib est un outil open source entièrement développé en Java qui crée des images de conteneur conformes à OCI (Docker v2), sans Dockerfile ni même environnement d’exécution de conteneur. Jib peut être utilisé comme outil CLI autonome, mais il s’utilise le plus souvent sous forme de plugin de build Maven ou Gradle. Avec ce plugin, il vous suffit d’exécuter la même commande de build mvn ou gradle que vous utilisez déjà pour créer vos artefacts .jar, .war, etc., afin de créer — et éventuellement de déployer — une image OCI prête à exécuter votre application dans un conteneur.
À partir d’une application Spring Boot, l’ajout de Jib se résume à insérer ce fragment XML dans votre fichier pom.xml Maven (la documentation complète est ici), puis à relancer la commande mvn package. Vous pouvez définir ici quelques options pour préciser où placer le conteneur. Par souci de simplicité, cet exemple déploie l’image dans un registre que nous utiliserons ensuite pour l’exécuter. Vous pouvez aussi enregistrer l’image dans un fichier local **.**tar ou, si un démon Docker est en cours d’exécution et accessible, dans le cache d’images local de Docker (comme le ferait une commande Docker build).
Remarque : La sortie ci-dessus contient quelques lignes de « warning » intéressantes, dont nous parlerons un peu plus loin.
Sur une machine disposant d’un environnement d’exécution de conteneur, il suffit maintenant de lancer le conteneur : docker run --rm -it -p 8080:8080 myimage:tag
Si vous utilisez Gradle, consultez la documentation officielle pour découvrir comment procéder dans votre fichier build.gradle.
Et voilà : aucun Dockerfile ni outil supplémentaire n’est nécessaire pour créer des images. Vous pouvez utiliser les mêmes outils de build que ceux dont vous vous servez déjà pour créer vos artefacts Java. Cela simplifie la vie des développeurs et facilite considérablement la prise en charge des agents de build CI, puisqu’il n’est pas nécessaire d’exposer le socket Docker ni de gérer d’autres outils de build.
Examinons l’image de plus près
D’accord, le processus de création d’images est peut-être plus simple, mais si vous êtes comme moi la première fois que je l’ai découvert, vous vous posez sans doute des questions comme…
Comment Jib crée-t-il les images ?
Comme tout outil de création d’images, Jib crée un ensemble de couches du système de fichiers contenant l’environnement d’exécution Java, votre application et toutes ses dépendances, ainsi que les métadonnées utilisées par le moteur de conteneur pour savoir comment démarrer la JVM.
Voici les informations sur les couches de cet exemple, affichées par la commande docker image history :
Remarquez que les dates CREATED des quatre premières couches indiquent toutes « il y a 51 ans ». C’est un effet secondaire de la stratégie de build reproductible de Jib, qui vise à créer exactement les mêmes hachages de couches pour des builds réalisés à partir du même code source. Pour en savoir plus, consultez leur FAQ.
Comme l’indiquent les commentaires des couches, nous avons plusieurs couches provenant de l’image de base AdoptOpenJDK par défaut — nous y reviendrons plus bas —, suivies, dans l’ordre, de :
Dépendances de bibliothèques définies dans vos fichiers Maven/Gradle
Fichiers de ressources
Fichiers de classes issus de la compilation de votre application
Fichiers d’arguments de la JVM.
Avec un peu plus de détails, examinons les fichiers présents dans chacune de ces quatre couches à l’aide de l’outil open source dive :
Dépendances
Cette couche contient tous les fichiers .jar ajoutés au dossier /app/libs.
Ressources
On trouve ici /app/resources et tous les fichiers associés provenant de nos dossiers de ressources.
Classes
Les fichiers .class issus de la phase de compilation du build se trouvent dans cette couche, sous /app/classes.
Fichiers d’arguments JVM
Enfin, quelques fichiers contenant les arguments utilisés au démarrage de la JVM dans le conteneur se trouvent dans le dossier de premier niveau /app. Dans cet exemple, si vous examiniez le contenu de ces deux fichiers, vous y trouveriez le classpath d’exécution et les informations sur la classe principale.
Remarque : Selon la structure de votre build Maven/Gradle, les images générées par Jib peuvent comporter davantage de couches. Consultez la FAQ de Jib pour en savoir plus sur les autres couches possibles.
Quelle image de base Jib utilise-t-il et que faire si je veux utiliser ma propre image ?
Par défaut, Jib utilise l’image de base officielle Docker Hub adoptopenjdk:jre-8 pour les builds de fichiers JAR, ou l’image de base officielle Docker Hub jetty pour les builds de fichiers WAR. Vous pouvez configurer ce choix dans les paramètres du plugin. Consultez la documentation officielle pour en savoir plus, notamment sur la configuration de ENTRYPOINT, USERou d’autres déclarations personnalisées, si nécessaire. Cette documentation explique également pourquoi il est conseillé de définir votre propre image de base et d’utiliser un hachage précis pour garantir la reproductibilité des builds. De nombreuses organisations imposent aux équipes de développement des images de base internes « approuvées », auditées et renforcées par les équipes de sécurité et des opérations. Voici un exemple de modification du fichier pom.xml Maven pour utiliser une telle image.
Jib prend également en charge des paramètres supplémentaires, comme l’étiquetage normalisé des images. Supposons, par exemple, que votre organisation exige que toutes les images contiennent les étiquettes suivantes :
URL Git du code source
Identifiant/hachage du commit Git
Version de build du projet Maven
Si le fichier pom.xml a déjà accès à ces données, la configuration du plugin Jib pour ajouter les étiquettes est très simple :
En inspectant l’image créée, on constate que les étiquettes ont bien été appliquées, comme si elles avaient été ajoutées avec des lignes LABELS dans un Dockerfile (j’utilise ici l’excellent outil en ligne de commande jq pour extraire uniquement ce tableau de la réponse).
Imaginez maintenant que toute la complexité de ces configurations normalisées soit définie dans un POM parent à l’aide de <pluginManagement> et d’autres configurations Maven habituelles. Les développeurs n’auraient plus à se soucier de tout ce code XML passe-partout, ni même à le consulter, et les architectes pourraient imposer et mettre à jour les normes à l’échelle de l’organisation sans déranger les équipes !
Comment m’assurer que tout est sécurisé ?
L’un des défis posés par les outils de création d’images plus généralistes, comme les Dockerfiles, est qu’ils permettent de faire pratiquement tout ce que l’on veut dans le cadre du build : installer des paquets, utiliser ADD pour télécharger des fichiers depuis des serveurs Web quelconques, et bien d’autres opérations qui doivent être examinées et contrôlées. Avec Jib, bon nombre de ces choix sont appliqués automatiquement. Pour les remplacer, il faut modifier explicitement la configuration Maven/Gradle ; ces changements sont clairement visibles lors d’une revue de code, comme le serait la modification d’une dépendance ou de toute autre configuration du build.
Pour l’analyse de sécurité, l’un des grands avantages de Snyk Container est de recommander différentes images de base présentant moins de vulnérabilités. Depuis aujourd’hui, cette fonctionnalité fonctionne même sans Dockerfile permettant d’identifier l’image de base utilisée. Les images créées avec Jib — ou avec tout autre outil sans Dockerfile — peuvent donc bénéficier des mêmes recommandations.
Pour suivre les étapes et analyser vos propres conteneurs Java, créez un compte gratuit et découvrez comment installer l’outil d’analyse Snyk.
Pour cet exemple, j’ai défini openjdk:8u121-jre comme image de base, exécuté mvn package, puis récupéré l’image sur mon ordinateur portable. Il ne me reste plus qu’à lancer snyk container test pour l’analyser.
Comme vous pouvez le voir, l’analyse a automatiquement détecté openjdk:8u181-jre-stretch comme image de base (il s’agit d’une balise alias de openjdk:8u181-jre) et a signalé ses 410 vulnérabilités. Elle recommande également de remplacer l’image de base, avec plusieurs options allant d’une mise à niveau mineure à la dernière balise openjdk:8-jre — qui présente environ deux fois moins de problèmes —, jusqu’à des JVM plus récentes sans aucune vulnérabilité.
Vous voyez donc que nous pouvons non seulement détecter les vulnérabilités présentes dans l’image utilisée, mais aussi obtenir des conseils pour les corriger en choisissant une image de base plus récente, sans avoir à écrire la moindre ligne de Dockerfile.
Pour conclure
Comme vous pouvez le constater, Jib facilite considérablement la conteneurisation des applications Java pour les développeurs, en éliminant le besoin d’apprendre la syntaxe des Dockerfiles ou d’installer des outils inhabituels. Il aide également les architectes à gérer la standardisation grâce aux mécanismes bien connus de hiérarchie des projets Maven ou Gradle. Puisque Jib crée des images conformes à OCI, vous pouvez aussi tirer parti d’outils conformes aux normes du secteur — comme Snyk Container — pour inspecter, déployer et exécuter votre application.
Si vous prenez également en charge des projets qui ne sont pas en Java, d’autres outils de ce domaine pourraient vous intéresser, comme Buildah, Bazel, Earthly et plusieurs projets liés à BuildKit. Par ailleurs, mon collègue Pas Apicella a récemment publié un article sur les Cloud Native Build Packs que vous devriez absolument lire.
N’oubliez pas de créer votre compte gratuit et de commencer dès aujourd’hui à tester vos conteneurs, vos dépendances open source et votre code IaC !
Sécurisez votre infrastructure dès la source
Snyk automatise la sécurité et la conformité de l’IaC dans vos workflows, et détecte les ressources dont la configuration a dérivé ou qui sont manquantes.
