Bonnes pratiques en matière de création de conteneurs

Last reviewed 2023-02-28 UTC

Cet article décrit un ensemble de bonnes pratiques pour la création de conteneurs. Ces pratiques couvrent un large éventail d'objectifs, allant de la réduction de la durée de compilation à la création d'images plus petites et plus résilientes, dans le but de faciliter la création de conteneurs (par exemple avec Cloud Build) ainsi que leur exécution dans Google Kubernetes Engine (GKE).

Ces bonnes pratiques ne revêtent pas toutes la même importance. En effet, certaines ne vous seront peut-être pas nécessaires pour exécuter avec succès une charge de travail de production, mais d'autres en revanche vous seront indispensables. En particulier, l'importance des bonnes pratiques en matière de sécurité est subjective. Votre décision de les mettre en œuvre dépend de votre environnement et de vos contraintes.

Pour tirer pleinement parti de cet article, vous devez disposer de connaissances au sujet de Docker et de Kubernetes. Certaines des bonnes pratiques décrites ici s'appliquent également aux conteneurs Windows, mais la plupart supposent que vous travaillez avec des conteneurs Linux. Des conseils sur l'exécution et l'exploitation des conteneurs sont disponibles sur la page Bonnes pratiques pour l'exploitation de conteneurs.

Empaqueter une seule application par conteneur

Importance : HAUTE

Lorsque vous commencez à utiliser des conteneurs, évitez l'erreur courante consistant à traiter ces conteneurs comme des machines virtuelles (VM) pouvant exécuter plusieurs tâches simultanément. Si un conteneur peut effectivement fonctionner de cette façon, les avantages du modèle de conteneur s'en voient largement diminués. Prenons comme exemple une pile Apache/MySQL/PHP classique : vous pourriez être tenté d'exécuter tous les composants dans un même conteneur. Toutefois, la bonne pratique consiste à utiliser deux ou trois conteneurs différents : un pour Apache, un pour MySQL et éventuellement un pour PHP si vous utilisez PHP-FPM.

Étant donné qu'un conteneur est conçu pour avoir le même cycle de vie que l'application qu'il héberge, chacun de vos conteneurs ne doit contenir qu'une seule application. Lorsqu'un conteneur démarre, l'application doit démarrer également, et quand l'application s'arrête, le conteneur doit s'arrêter également. Le schéma suivant illustre cette bonne pratique.

Schéma illustrant le processus de démarrage sans image personnalisée.

Figure 1. Le conteneur de gauche suit la bonne pratique. Le conteneur de droite ne la suit pas.

Si vous avez plusieurs applications dans un conteneur, elles peuvent avoir des cycles de vie différents ou se trouver dans des états différents. Par exemple, vous pourriez vous retrouver avec un conteneur en cours d'exécution, mais dont l'un des composants principaux a planté ou ne répond pas. Sans vérification de l'état supplémentaire, le système global de gestion des conteneurs (Docker ou Kubernetes) n'a pas moyen de savoir si le conteneur est en bon état. Dans le cas de Kubernetes, cela signifie que si un composant principal ne répond pas, Kubernetes ne redémarrera pas automatiquement votre conteneur.

Vous pourriez être amené à voir les actions suivantes dans des images publiques, mais vous ne devez pas suivre leur exemple :

  • Utiliser un système de gestion de processus tel que supervisord pour gérer une ou plusieurs applications dans le conteneur.
  • Utiliser un script bash comme point d'entrée dans le conteneur et le faire générer plusieurs applications en tant que tâches d'arrière-plan. Pour savoir comment utiliser correctement les scripts bash dans les conteneurs, consultez la bonne pratique suivante : Traiter correctement le PID 1, le traitement de signal et les processus zombie.

Traiter correctement le PID 1, le traitement de signal et les processus zombie

Importance : HAUTE

Le cycle de vie des processus au sein d'un conteneur est contrôlé principalement par les signaux Linux. Conformément à la bonne pratique précédente, afin de lier étroitement le cycle de vie de votre application au conteneur dans lequel elle se trouve, assurez-vous que l'application traite correctement les signaux Linux. Le signal Linux le plus important est SIGTERM, car il met fin à un processus. Une application peut également recevoir un signal SIGKILL, servant à arrêter brutalement le processus, ou un signal SIGINT, envoyé lorsque vous saisissez Ctrl+C et généralement traité comme le signal SIGTERM.

Les identificateurs de processus (PID) sont des identificateurs uniques que le noyau Linux attribue à chaque processus. Les PID sont associés à des espaces de noms, ce qui signifie qu'un conteneur possède son propre ensemble de PID mappés sur des PID sur le système hôte. Le premier processus lancé lors du démarrage d'un noyau Linux se voit attribuer le PID 1. Pour un système d'exploitation normal, ce processus est le système init, par exemple, systemd ou SysV. De même, le premier processus lancé dans un conteneur se voit attribuer le PID 1. Docker et Kubernetes utilisent des signaux pour communiquer avec les processus situés dans les conteneurs, principalement pour y mettre fin. Docker et Kubernetes ne peuvent envoyer de signaux qu'au processus du conteneur portant le PID 1.

Dans le contexte des conteneurs, les PID et les signaux Linux créent deux problèmes que vous devez prendre en compte.

Problème 1 : La façon dont le noyau Linux traite les signaux

Le noyau Linux traite les signaux différemment selon qu'il s'agit du processus portant le PID 1 ou d'autres processus. Les gestionnaires de signaux ne sont pas automatiquement enregistrés pour ce processus, ce qui signifie que des signaux tels que SIGTERM ou SIGINT n'auront aucun effet par défaut. Par défaut, vous devez tuer les processus à l'aide de SIGKILL pour empêcher tout arrêt progressif. En fonction de votre application, l'utilisation de SIGKILL peut entraîner des erreurs pour les utilisateurs, des écritures interrompues (pour les datastores) ou des alertes indésirables dans votre système de surveillance.

Problème 2 : La façon dont les systèmes init classiques traitent les processus orphelins

Les systèmes init classiques tels que systemd sont également utilisés pour supprimer (récupérer) les processus zombies orphelins. Les processus orphelins (processus dont les parents ont été tués) sont rattachés au processus portant le PID 1 qui devrait les récupérer à la mort de leurs parents. C'est du moins le comportement normal d'un système init. Dans un conteneur, en revanche, cette responsabilité incombe à tout processus portant le PID 1. Si ce processus ne traite pas la récupération de manière adéquate, vous risquez de manquer de mémoire ou d'autres ressources.

Ces problèmes ont plusieurs solutions communes décrites dans les sections suivantes.

Solution 1 : Exécuter en tant que PID 1 et enregistrer les gestionnaires de signaux

Cette solution ne traite que le premier problème. Elle est valable si votre application génère des processus enfants de manière contrôlée (ce qui est souvent le cas), évitant ainsi le deuxième problème.

Le moyen le plus simple de mettre en œuvre cette solution consiste à lancer un processus avec les instructions CMD et/ou ENTRYPOINT dans un fichier Dockerfile. Par exemple, dans le fichier Dockerfile suivant, nginx est le premier, mais aussi le seul processus à être lancé.

FROM debian:11

RUN apt-get update && \
    apt-get install -y nginx

EXPOSE 80

CMD [ "nginx", "-g", "daemon off;" ]

Parfois, vous devrez peut-être préparer l'environnement de votre conteneur pour que le processus s'exécute correctement. Dans ce cas, la bonne pratique consiste à demander au conteneur de lancer un script d'interface système lors de son démarrage. Ce script d'interface système est alors chargé de préparer l'environnement et de lancer le processus principal. Toutefois, si vous adoptez cette approche, c'est le script shell qui porte le PID 1, et non votre processus. C'est pourquoi vous devez utiliser la commande exec intégrée pour lancer le processus à partir du script shell. La commande exec remplace le script par le programme de votre choix. Votre processus hérite ensuite du PID 1.

Solution 2 : Activer le partage d'espace de noms de processus dans Kubernetes

Lorsque vous activez le partage d'espace de noms de processus pour un pod, Kubernetes utilise un seul espace de noms de processus pour tous les conteneurs de ce pod. Le conteneur d'infrastructure du pod Kubernetes devient le PID 1 et récupère automatiquement les processus orphelins.

Solution 3 : Utiliser un système init spécialisé

Comme dans un environnement Linux plus classique, vous pouvez également utiliser un système init pour résoudre les problèmes que nous avons énoncés. Cependant, les systèmes init normaux tels que systemd ou SysV sont trop complexes et volumineux pour cet usage. C'est pourquoi nous vous recommandons d'utiliser un système init tel que tini, créé spécialement pour les conteneurs.

Si vous utilisez un système init spécialisé, le processus init porte le PID 1 et procède comme suit :

  • Il enregistre les gestionnaires de signaux corrects.
  • Il veille à ce que les signaux fonctionnent pour votre application.
  • Il récupère tout processus zombie éventuel.

Vous pouvez appliquer cette solution dans Docker lui-même en utilisant l'option --init de la commande docker run. Pour appliquer cette solution dans Kubernetes, vous devez installer le système init dans votre image de conteneur et l'utiliser comme point d'entrée pour votre conteneur.

Optimiser les conteneurs pour le cache de création de Docker

Importance : HAUTE

Le cache de création de Docker peut accélérer la création d'images de conteneurs. Les images sont construites couche par couche et, dans un fichier Dockerfile, chaque instruction crée une couche dans l'image résultante. Au cours d'une création et lorsque cela est possible, Docker réutilise une couche d'une version précédente et évite ainsi une étape potentiellement coûteuse. Docker ne peut utiliser son cache de création que si toutes les étapes de création précédentes l'ont également utilisé. Bien que ce comportement soit généralement bénéfique pour accélérer les créations, vous devez tenir compte de quelques scénarios.

Par exemple, pour tirer pleinement parti du cache de création de Docker, vous devez positionner les étapes de création qui changent souvent à la fin du fichier Dockerfile. Si vous les mettez au début, Docker ne peut pas utiliser son cache de création pour les autres étapes de création qui changent moins souvent. Dans la mesure où une nouvelle image Docker est généralement créée pour chaque nouvelle version de votre code source, ajoutez le code source à l'image aussi loin que possible dans le fichier Dockerfile. Dans le schéma suivant, vous pouvez voir que si vous modifiez STEP 1, Docker ne peut réutiliser que les couches de l'étape FROM debian:11. Toutefois, si vous modifiez STEP 3, Docker peut réutiliser les couches de STEP 1 et STEP 2.

Exemples d'utilisation du cache de création de Docker

Figure 2. Exemples d'utilisation du cache de création de Docker. En vert, les couches que vous pouvez réutiliser. En rouge, les couches devant être recréées.

Autre conséquence de la réutilisation des couches : si une étape de création repose sur un type de cache stocké sur le système de fichiers local, ce cache doit être généré au cours de la même étape de création. Si ce cache n'est pas généré, l'étape de création pourrait être exécutée avec un cache obsolète provenant d'une version précédente. Ce comportement est le plus souvent observé dans les gestionnaires de packages tels qu'apt ou yum : vous devez mettre à jour vos dépôts dans la même commande RUN que celle de votre installation de packages.

Si vous modifiez la deuxième étape RUN dans le fichier Dockerfile suivant, la commande apt-get update n'est pas réexécutée, ce qui vous laisse un cache apt obsolète.

FROM debian:11

RUN apt-get update
RUN apt-get install -y nginx

Au lieu de cela, fusionnez les deux commandes en une seule étape RUN :

FROM debian:11

RUN apt-get update && \
    apt-get install -y nginx

Supprimer les outils inutiles

Importance : MOYENNE

Pour protéger vos applications contre le piratage, essayez de réduire la surface d'attaque de votre application en y supprimant tout outil inutile. Supprimez par exemple les utilitaires tels que netcat que vous pouvez utiliser pour créer une interface système inversée à l'intérieur de votre système. Si netcat ne se trouve pas dans le conteneur, le hacker sera obligé de rebrousser chemin.

Cette bonne pratique est valable pour toute charge de travail, même si elle n'est pas en conteneur. La différence est que cette bonne pratique est optimisée pour les conteneurs plutôt que pour les VM classiques ou les serveurs Bare Metal.

En supprimant les outils inutiles, vous pouvez aussi potentiellement améliorer vos processus de débogage. Par exemple, si vous poussez suffisamment loin la mise en œuvre de cette bonne pratique, vous serez quasiment obligé d’utiliser des journaux complets et des systèmes de traçage et de profilage. Vous ne pourrez plus compter sur les outils de débogage locaux qui sont souvent hautement privilégiés.

Contenu du système de fichiers

La première partie de cette bonne pratique traite du contenu de l'image de conteneur. Éliminez autant de choses que possible dans votre image. Si vous pouvez compiler votre application en un seul binaire statiquement lié et que vous ajoutez ce binaire à l'image scratch, vous obtenez alors une image finale contenant seulement votre application et rien d'autre. En réduisant le nombre d'outils contenus dans votre image, vous limitez les dégâts que pourrait causer un éventuel hacker dans votre conteneur. Pour plus d'informations, consultez la section Créer la plus petite image possible de cet article.

Sécurité du système de fichiers

Ne plus avoir d'outils dans votre image ne suffit pas : vous devez empêcher les hackers potentiels d'y installer leurs propres outils. Pour cela, vous pouvez avoir recours à deux méthodes à la fois :

  • Évitez les exécutions en mode root au sein du conteneur : cette méthode offre une première couche de sécurité et pourrait, par exemple, empêcher des pirates informatiques de modifier les fichiers du répertoire racine à l'aide d'un gestionnaire de packages intégré à votre image (comme apt-get ou apk). Pour que cette méthode soit utile, vous devez désactiver ou désinstaller la commande sudo. Ce sujet est traité plus en détail dans la section Éviter les exécutions en mode root.

  • Lancez le conteneur en mode lecture seule, à l'aide de l'option --read-only de la commande docker run ou de l'option readOnlyRootFilesystem dans Kubernetes.

Créer la plus petite image possible

Importance : MOYENNE

Créer une image plus petite offre des avantages tels que des temps d'importation et de téléchargement plus courts, ce qui est particulièrement important pour la durée de démarrage à froid d'un pod dans Kubernetes : plus l'image est petite, plus le nœud peut la télécharger rapidement. Cependant, il peut être difficile de créer une petite image, car vous pourriez inclure par inadvertance des dépendances de création ou des couches non optimisées dans votre image finale.

Utiliser la plus petite image de base possible

L'image de base est celle référencée dans l'instruction FROM de votre fichier Dockerfile. Toutes les autres instructions du fichier Dockerfile sont créées par-dessus cette image. Plus l'image de base est petite, plus l'image résultante est petite et plus elle peut être téléchargée rapidement. Par exemple, l'image alpine:3.17 pèse 23 Mo de moins que l'image ubuntu:22.04.

Vous pouvez même utiliser l'image de base scratch qui est une image vide sur laquelle vous pouvez créer votre propre environnement d'exécution. Si votre application est un binaire statiquement lié, vous pouvez utiliser l'image de base scratch comme suit :

FROM scratch
COPY mybinary /mybinary
CMD [ "/mybinary" ]

La vidéo suivante sur les bonnes pratiques de Kubernetes présente d'autres stratégies permettant de créer de petits conteneurs tout en réduisant votre exposition aux failles de sécurité.

Désencombrer l'image

Pour réduire la taille de votre image, n'installez-y que ce qui est strictement nécessaire. Il peut être tentant d'installer des packages supplémentaires pour les supprimer ultérieurement. Toutefois, cette approche ne suffit pas. Étant donné que chaque instruction du fichier Dockerfile crée une couche, la suppression des données de l'image à une étape ultérieure à celle qui les a créées ne permet pas de réduire la taille de l'image globale (les données seront toujours présentes, mais seront dissimulées dans une couche plus profonde). Considérez l'exemple suivant :

Fichier Dockerfile inadéquat Fichier Dockerfile adéquat

FROM debian:11
RUN apt-get update && \ apt-get install -y \ [buildpackage] RUN [build my app] RUN apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

FROM debian:11
RUN apt-get update && \ apt-get install -y \ [buildpackage] && \ [build my app] && \ apt-get autoremove --purge \ -y [buildpackage] && \ apt-get -y clean && \ rm -rf /var/lib/apt/lists/*

Dans la version inadéquate du fichier Dockerfile, le [buildpackage] et les fichiers situés dans /var/lib/apt/lists/* existent toujours dans la couche correspondant à la première exécution RUN. Cette couche fait partie de l'image et doit être importée et téléchargée avec le reste, même si les données qu'elle contient ne sont pas accessibles dans l'image obtenue.

Dans le fichier Dockerfile adéquat, tout se déroule dans une seule couche qui ne contient que votre application compilée. Le [buildpackage] et les fichiers /var/lib/apt/lists/* n'existent nulle part dans l'image obtenue, pas même dans une couche plus profonde.

Pour plus d'informations sur les couches d'image, voir la section Optimiser les conteneurs pour le cache de création de Docker de cet article.

Un autre excellent moyen de désencombrer votre image consiste à utiliser des versions sur plusieurs niveaux (présentées dans Docker 17.05). Les versions sur plusieurs niveaux vous permettent de créer votre application dans un premier conteneur de "version" et d'utiliser le résultat dans un autre conteneur, tout en utilisant le même fichier Dockerfile.

Processus de création sur plusieurs niveaux de Docker

Figure 3. Processus de compilation sur plusieurs niveaux de Docker.

Dans le fichier Dockerfile suivant, le binaire hello est créé dans un premier conteneur et injecté dans un second. Comme le second conteneur est basé sur scratch, l'image obtenue ne contient que le binaire hello, et non le fichier source et les fichiers objets nécessaires lors de la compilation. Le binaire doit être statiquement lié pour pouvoir fonctionner sans aucune bibliothèque externe dans l'image scratch.

FROM golang:1.20 as builder

WORKDIR /tmp/go
COPY hello.go ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s' -o hello

FROM scratch
CMD [ "/hello" ]
COPY --from=builder /tmp/go/hello /hello

Essayer de créer des images avec des couches communes

Si vous devez télécharger une image Docker, Docker vérifie d'abord que vous avez déjà certaines des couches présentes dans l'image. Si vous avez ces couches, elles ne sont pas téléchargées. Cela peut se produire si vous avez précédemment téléchargé une autre image ayant la même base que celle que vous téléchargez actuellement. Résultat : la quantité de données téléchargées est beaucoup moins importante pour la deuxième image.

Au niveau organisationnel, vous pouvez tirer parti de cette quantité réduite en fournissant à vos développeurs un ensemble d'images de base communes et standards. Vos systèmes ne devront télécharger chaque image de base qu'une seule fois. Après le téléchargement initial, seules les couches qui rendent chaque image unique sont requises. En somme, plus vos images auront de choses en commun, plus elles seront rapides à télécharger.

Essayer de créer des images avec des couches communes

Figure 4. Créer des images avec des couches communes.

Rechercher des failles dans les images

Importance : MOYENNE

Les failles logicielles constituent un problème qui est bien compris dans le milieu des serveurs physiques et des machines virtuelles. Une manière courante de remédier à ces failles consiste à utiliser un système d'inventaire centralisé répertoriant les packages installés sur chaque serveur. Abonnez-vous aux flux de failles des systèmes d'exploitation en amont pour être informé des failles affectant vos serveurs et les corriger en conséquence.

Toutefois, comme les conteneurs sont supposés être immuables (pour en savoir plus, consultez la section Conteneurs sans état et immuables), ne les corrigez pas en cas de faille. La bonne pratique consiste à recréer l'image, y compris les correctifs, et à la redéployer. Les conteneurs ont un cycle de vie beaucoup plus court et une identité moins bien définie que les serveurs. De ce fait, il n'est pas conseillé d'utiliser un système d'inventaire centralisé similaire pour détecter les failles présentes dans les conteneurs.

Pour vous aider à résoudre ce problème, Artifact Analysis peut rechercher les failles de sécurité présentes dans vos images en se basant sur des packages surveillés publiquement. Les options suivantes sont disponibles :

Analyse automatique des failles

Lorsqu'elle est activée, cette fonctionnalité identifie les failles de package de vos images de conteneurs. Les images sont analysées lors de leur importation dans Artifact Registry ou Container Registry, et les données sont surveillées en permanence pour détecter de nouvelles failles pendant un maximum de 30 jours après le transfert de l'image. Vous pouvez agir sur les informations rapportées par cette fonctionnalité de plusieurs manières :

  • Créez une tâche de type Cron qui répertorie les failles et déclenche le processus pour les corriger, lorsqu'un correctif existe.
  • Dès qu'une faille est détectée, utilisez l'intégration Cloud Pub/Sub pour déclencher le processus de correction utilisé par votre organisation.
API On-Demand Scanning

Lorsque cette option est activée, vous pouvez analyser manuellement les images locales ou les images stockées dans Artifact Registry ou Container Registry. Cette fonctionnalité vous permet de détecter et de corriger les failles dès le début de votre pipeline de compilation. Par exemple, vous pouvez utiliser Cloud Build pour analyser une image après sa création, puis bloquer l'importation dans Artifact Registry si l'analyse détecte des failles à un niveau de gravité spécifié. Si vous avez également activé l'analyse automatique des failles, Artifact Registry analyse aussi les images que vous importez dans le registre.

Nous vous conseillons d'automatiser le processus de correction et de vous appuyer sur le pipeline d'intégration continue existant qui a été initialement utilisé pour générer l'image. Si vous avez confiance en votre pipeline de déploiement continu, vous pouvez également déployer automatiquement l'image fixe lorsque celle-ci est prête. Cependant, la plupart des gens préfèrent avoir une étape de vérification manuelle avant le déploiement. Le processus suivant vous le permet :

  1. Stockez vos images dans Artifact Registry et activez l'analyse des failles.
  2. Configurez une tâche qui extrait régulièrement les nouvelles failles depuis Artifact Registry et qui déclenche une recréation des images si nécessaire.
  3. Une fois les nouvelles images créées, demandez à votre système de déploiement continu de les déployer vers un environnement de préproduction.
  4. Recherchez manuellement des problèmes éventuels dans l'environnement de préproduction.
  5. Si aucun problème n'est détecté, déclenchez manuellement le déploiement en production.

Ajouter des tags pertinents aux images

Importance : MOYENNE

Les images Docker sont généralement identifiées par deux composants : leur nom et leur tag. Par exemple, pour l'image google/cloud-sdk:419.0.0, google/cloud-sdk est le nom et 419.0.0 est le tag. Le tag latest est utilisé par défaut si vous n'en fournissez pas dans vos commandes Docker. La paire nom/tag est toujours unique. Cependant, vous pouvez réaffecter un tag à une image différente si nécessaire.

Lorsque vous créez une image, il vous appartient de lui ajouter des tags appropriés. Suivez une stratégie cohérente et fiable pour l'ajout des tags. Documentez votre stratégie d'ajout de tags afin que les utilisateurs de l'image puissent la comprendre facilement.

Les images de conteneurs constituent un moyen d'empaqueter et de publier un logiciel. L'ajout d'un tag à l'image permet aux utilisateurs d'identifier une révision spécifique de votre logiciel afin de le télécharger. Pour cette raison, associez étroitement le système d'ajout de tags sur les images de conteneur à la stratégie de publication de votre logiciel.

Ajout de tag avec gestion sémantique des versions

Un moyen courant de publier un logiciel consiste à ajouter des tags (comme dans la commande git tag) à une version particulière du code source avec un numéro de version. La Spécification de gestion sémantique des versions propose une manière de gérer les numéros de version "proprement". Selon ce système, le numéro de version du logiciel contient trois parties : X.Y.Z, où :

  • X est la révision majeure incrémentée seulement pour les modifications incompatibles de l'API ;
  • Y est la révision mineure incrémentée pour les nouvelles fonctionnalités ;
  • Z est la révision de correctif incrémentée pour les corrections de bugs.

Chaque incrément dans le numéro de version mineure ou le numéro de version de correctif doit correspondre à une modification rétrocompatible.

Si vous utilisez ce système ou un système semblable, ajoutez des tags à vos images en suivant la stratégie suivante :

  • Le tag latest fait toujours référence à l'image la plus récente (éventuellement la plus stable). Ce tag est déplacé dès qu'une nouvelle image est créée.
  • Le tag X.Y.Z fait référence à une révision spécifique du logiciel. Ne le déplacez pas vers une autre image.
  • Le tag X.Y fait référence à la dernière version de correctif de la branche mineure X.Y du logiciel. Il est déplacé quand une nouvelle révision de correctif est publiée.
  • Le tag X doit faire référence à la dernière version de correctif de la dernière version mineure de la branche principale X. Il est déplacé lorsqu'une nouvelle révision de correctif ou une nouvelle révision mineure est publiée.

L'utilisation de cette stratégie offre aux utilisateurs la possibilité de choisir la version du logiciel qu'ils souhaitent utiliser. Ils peuvent choisir une révision "X.Y.Z" spécifique et avoir la garantie que l'image ne changera jamais, ou obtenir des mises à jour automatiquement en choisissant un tag moins spécifique.

Ajout de tag avec hachage de commit Git

Si vous disposez d'un système de livraison continue avancé et que vous publiez souvent votre logiciel, vous n'utilisez probablement pas les numéros de version décrits dans la spécification de gestion sémantique des versions. Dans ce cas, une manière courante de gérer les numéros de version consiste à utiliser le hachage SHA-1 de commit Git (ou une version abrégée de celui-ci) comme numéro de version. De par sa conception, le hachage de commit Git est immuable et fait référence à une version spécifique de votre logiciel.

Vous pouvez utiliser ce hachage de commit comme numéro de version de votre logiciel, mais également en tant que tag pour l'image Docker créée à partir de cette version spécifique de votre logiciel. De cette manière, les images Docker sont traçables : dans ce cas, en effet, le tag de l'image est immuable et vous savez instantanément quelle version de votre logiciel est exécutée dans un conteneur donné. Dans votre pipeline de livraison continue, automatisez la mise à jour du numéro de révision utilisé pour vos déploiements.

Envisager sérieusement l'utilisation d'une image publique

Importance : non applicable

L'un des meilleurs avantages de Docker est le grand nombre d'images disponibles au public pour tous types de logiciels. Ces images vous permettent de faire vos premiers pas rapidement. Toutefois, lorsque vous concevez une stratégie de conteneur pour votre organisation, vous pouvez avoir des contraintes que les images disponibles publiquement ne peuvent pas respecter. Voici quelques exemples de contraintes pouvant rendre impossible l'utilisation d'images publiques :

  • Vous souhaitez maîtriser exactement ce que contiennent vos images.
  • Vous ne voulez pas dépendre d'un dépôt externe.
  • Vous souhaitez maîtriser strictement les failles de votre environnement de production.
  • Vous voulez le même système d'exploitation de base dans chaque image.

La réponse à toutes ces contraintes est la même et est malheureusement coûteuse : vous devez créer vos propres images. Créer vos propres images est faisable pour un nombre limité d'images, mais ce nombre a tendance à croître rapidement. Pour avoir une chance de gérer un tel système à grande échelle, songez à utiliser ce qui suit :

  • Un moyen automatisé de créer des images de manière fiable, même pour des images de création rare. Les déclencheurs de compilation dans Cloud Build sont un bon moyen d'y parvenir.
  • Une image de base standardisée. Google fournit des images de base que vous pouvez utiliser.
  • Un moyen automatisé de propager les mises à jour de l'image de base en images "enfants".
  • Un moyen de traiter les failles de vos images. Pour en savoir plus, consultez la page Rechercher des failles dans les images.
  • Un moyen de faire respecter vos normes internes sur les images créées par les différentes équipes de votre organisation.

Plusieurs outils sont disponibles pour vous aider à appliquer des stratégies aux images que vous créez et déployez :

  • container-diff peut analyser le contenu des images et même comparer deux images entre elles.
  • container-structure-test peut tester si le contenu d'une image est conforme à un ensemble de règles que vous définissez.
  • Grafeas est une API de métadonnées d'artefact dans laquelle vous stockez des métadonnées concernant vos images pour vérifier ultérieurement si ces images sont conformes à vos règles.
  • Kubernetes dispose de contrôleurs d'admission que vous pouvez utiliser pour vérifier un certain nombre de conditions préalables avant de déployer une charge de travail dans Kubernetes.

Vous voudrez peut-être également adopter un système hybride, c'est-à-dire utiliser une image publique telle que Debian ou Alpine comme image de base, et tout créer par-dessus cette image. Vous pourrez également utiliser des images publiques pour certaines de vos images non cruciales et créer vos propres images pour d'autres cas. Il n'existe pas de bonnes ou mauvaises réponses à ces questions, mais vous devez néanmoins y répondre.

Remarque au sujet des licences

Avant d'inclure des bibliothèques et des packages tiers dans votre image Docker, assurez-vous que les licences respectives vous le permettent. Les licences tierces peuvent également imposer des restrictions sur la redistribution qui s'appliquent lorsque vous publiez une image Docker dans un registre public.

Étape suivante

Découvrez des architectures de référence, des schémas et des bonnes pratiques concernant Google Cloud. Consultez notre Centre d'architecture cloud.