Construction de conteneurs optimisés pour Kubernetes

introduction

Les images de conteneur constituent le principal format de conditionnement permettant de définir des applications dans Kubernetes. Utilisées comme base pour les pods et autres objets, les images jouent un rôle important dans l’exploitation des fonctionnalités de Kubernetes afin d’exécuter efficacement des applications sur la plate-forme. Les images bien conçues sont sécurisées, hautement performantes et ciblées. Ils peuvent réagir aux données de configuration ou aux instructions fournies par Kubernetes et implémenter les ordinateurs d'extrémité utilisés par le système d'orchestration pour comprendre l'état de l'application interne.

Dans cet article, nous présenterons quelques stratégies pour créer des images de haute qualité et discuterons de quelques objectifs généraux pour vous guider dans la prise de décision lors de la conteneurisation des applications. Nous nous concentrerons sur la création d'images destinées à être exécutées sur Kubernetes, mais bon nombre des suggestions s'appliquent également à l'exécution de conteneurs sur d'autres plates-formes d'orchestration ou dans d'autres contextes.

Caractéristiques des images de conteneurs efficaces

Avant de passer en revue les actions spécifiques à prendre lors de la construction des images de conteneur, nous allons parler de ce qui fait une bonne image de conteneur. Quels devraient être vos objectifs lors de la conception de nouvelles images? Quelles caractéristiques et quels comportements sont les plus importants?

Certaines qualités à viser sont:

Un objectif unique et bien défini

Les images de conteneur doivent avoir un seul focus discret. Évitez de considérer les images de conteneur comme des machines virtuelles, où il peut être judicieux de regrouper les fonctionnalités associées. Au lieu de cela, traitez vos images de conteneur comme des utilitaires Unix, en vous concentrant strictement sur la réalisation de petites tâches. Les applications peuvent être coordonnées en dehors de la portée du conteneur pour composer des fonctionnalités complexes.

Conception générique avec la possibilité d'injecter la configuration au moment de l'exécution

Les images de conteneur doivent être conçues de manière à pouvoir être réutilisées dans la mesure du possible. Par exemple, la possibilité d'ajuster la configuration au moment de l'exécution est souvent nécessaire pour répondre aux exigences de base, telles que le test de vos images avant le déploiement en production. De petites images génériques peuvent être combinées dans différentes configurations pour modifier le comportement sans créer de nouvelles images.

Petite taille d'image

Les petites images présentent de nombreux avantages dans les environnements en cluster tels que Kubernetes. Ils téléchargent rapidement vers de nouveaux nœuds et disposent souvent d'un ensemble de packages installés plus petit, ce qui peut améliorer la sécurité. Les images de conteneur réduites facilitent le débogage des problèmes en minimisant la quantité de logiciels impliqués.

État géré en externe

Les conteneurs dans les environnements en cluster connaissent un cycle de vie très volatil comprenant des arrêts planifiés et non planifiés en raison de la rareté des ressources, de la mise à l'échelle ou des défaillances de nœuds. Pour maintenir la cohérence, faciliter la récupération et la disponibilité de vos services et éviter de perdre des données, il est essentiel que vous stockiez l'état de l'application dans un emplacement stable à l'extérieur du conteneur.

Facile à comprendre

Il est important d'essayer de garder les images de conteneur aussi simples et faciles à comprendre que possible. Lors du dépannage, vous pouvez facilement résoudre le problème en affichant la configuration de l'image du conteneur ou en testant son comportement, ce qui peut vous aider à atteindre une résolution plus rapidement. Considérer les images de conteneur comme un format de packaging pour votre application plutôt que comme une configuration de machine peut vous aider à trouver le bon équilibre.

Suivez les meilleures pratiques des logiciels conteneurisés

Les images doivent viser à fonctionner dans le modèle de conteneur au lieu d'agir contre lui. Évitez de mettre en œuvre des pratiques d'administration système classiques, telles que l'inclusion de systèmes init complets et la démonisation d'applications. Connectez-vous à la sortie standard afin que Kubernetes puisse exposer les données aux administrateurs au lieu d'utiliser un démon de journalisation interne. Chacune de celles-ci diffère des meilleures pratiques pour les systèmes d'exploitation complets.

Tirez pleinement parti des fonctionnalités de Kubernetes

Au-delà de la conformité au modèle de conteneur, il est important de comprendre et de réconcilier avec l'environnement et les outils fournis par Kubernetes. Par exemple, fournir des points de terminaison pour les contrôles de réactivité et d’activité ou ajuster les opérations en fonction des modifications apportées à la configuration ou à l’environnement peut aider vos applications à tirer parti de l’environnement de déploiement dynamique de Kubernetes.

Maintenant que nous avons défini certaines des qualités qui définissent des images de conteneur hautement fonctionnelles, nous pouvons approfondir nos stratégies pour vous aider à atteindre ces objectifs.

Réutiliser les couches de base partagées minimales

Nous pouvons commencer par examiner les ressources à partir desquelles sont construites les images de conteneur: images de base. Chaque image conteneur est construite soit à partir d'unparent image, une image utilisée comme point de départ, soit à partir du calque abstraitscratch, un calque d'image vide sans système de fichiers. Unbase image est une image de conteneur qui sert de base pour les images futures en définissant le système d'exploitation de base et en fournissant les fonctionnalités de base. Les images sont composées d'un ou de plusieurs calques d'image construits les uns sur les autres pour former une image finale.

Aucun utilitaire ou système de fichiers standard n'est disponible lorsque vous travaillez directement à partir descratch, ce qui signifie que vous n'avez accès qu'à des fonctionnalités extrêmement limitées. Alors que les images créées directement à partir descratch peuvent être très simplifiées et minimales, leur objectif principal est de définir des images de base. En règle générale, vous souhaitez créer vos images de conteneur au-dessus d'une image parent définissant un environnement de base dans lequel vos applications s'exécutent, de sorte que vous n'ayez pas à créer un système complet pour chaque image.

Bien qu’il existe des images de base pour diverses distributions Linux, il est préférable de choisir les systèmes que vous choisissez. Chaque nouvelle machine devra télécharger l’image parent et toutes les couches supplémentaires que vous avez ajoutées. Pour les images de grande taille, cela peut consommer une quantité importante de bande passante et allonger considérablement le temps de démarrage de vos conteneurs lors de leur première utilisation. Il n’existe aucun moyen de réduire une image utilisée en tant que parent en aval dans le processus de construction du conteneur. Il est donc judicieux de commencer par un parent minimal.

Les environnements riches en fonctionnalités tels que Ubuntu permettent à votre application de s'exécuter dans un environnement familier, mais certains compromis sont à prendre en compte. Les images Ubuntu (et les images de distribution conventionnelle similaires) ont tendance à être relativement grandes (plus de 100 Mo), ce qui signifie que toutes les images de conteneur créées à partir de ces images hériteront de ce poids.

Alpine Linux est une alternative populaire pour les images de base car il regroupe avec succès de nombreuses fonctionnalités dans une très petite image de base (~ 5 Mo). Il inclut un gestionnaire de paquets avec des référentiels importants et contient la plupart des utilitaires standard que vous pouvez attendre d'un environnement Linux minimal.

Lorsque vous concevez vos applications, essayez de réutiliser le même parent pour chaque image. Lorsque vos images partagent un parent, les machines exécutant vos conteneurs ne téléchargeront le calque parent qu'une seule fois. Ensuite, ils n'auront plus qu'à télécharger les calques qui diffèrent d'une image à l'autre. Cela signifie que si vous avez des fonctionnalités ou des fonctionnalités communes que vous souhaitez intégrer à chaque image, il peut être judicieux de créer une image parente commune à hériter. Les images qui partagent une lignée aident à réduire la quantité de données supplémentaires que vous devez télécharger sur de nouveaux serveurs.

Gestion des couches de conteneur

Une fois que vous avez sélectionné une image parent, vous pouvez définir votre image de conteneur en ajoutant un logiciel supplémentaire, en copiant des fichiers, en exposant les ports et en choisissant des processus à exécuter. Certaines instructions dans le fichier de configuration d'image (unDockerfile si vous utilisez Docker) ajouteront des couches supplémentaires à votre image.

Pour bon nombre des raisons évoquées dans la section précédente, il est important de garder à l'esprit la façon dont vous ajoutez des couches à vos images en raison de la taille, de l'héritage et de la complexité de l'exécution qui en résultent. Pour éviter de créer des images volumineuses et lourdes, il est important de bien comprendre l’interaction des couches de conteneur, la manière dont le moteur de construction met en cache les couches et comment les différences subtiles dans des instructions similaires peuvent avoir un impact important sur les images que vous créez.

Comprendre les couches d'image et le cache de construction

Docker crée un nouveau calque d'image chaque fois qu'il exécute une instructionRUN,COPY ouADD. Si vous générez à nouveau l'image, le moteur de génération vérifiera chaque instruction pour voir si elle contient un calque d'image mis en cache pour l'opération. S'il trouve une correspondance dans le cache, il utilise la couche d'image existante plutôt que d'exécuter à nouveau l'instruction et de reconstruire la couche.

Ce processus peut réduire considérablement les temps de construction, mais il est important de comprendre le mécanisme utilisé pour éviter les problèmes potentiels. Pour les instructions de copie de fichiers telles queCOPY etADD, Docker compare les sommes de contrôle des fichiers pour voir si l'opération doit être exécutée à nouveau. Pour les instructions deRUN, Docker vérifie si une couche d'image existante est mise en cache pour cette chaîne de commande particulière.

Bien que cela ne soit pas immédiatement évident, ce comportement peut entraîner des résultats inattendus si vous ne faites pas attention. Un exemple courant consiste à mettre à jour l'index de paquetage local et à installer les paquetages en deux étapes distinctes. Nous allons utiliser Ubuntu pour cet exemple, mais le principe de base s’applique également aux images de base pour d’autres distributions:

Exemple d'installation de package Dockerfile

FROM ubuntu:18.04
RUN apt -y update
RUN apt -y install nginx
. . .

Ici, l'index du package local est mis à jour dans une instructionRUN (apt -y update) et Nginx est installé dans une autre opération. Cela fonctionne sans problème lors de sa première utilisation. Toutefois, si le fichier Dockerfile est mis à jour ultérieurement pour installer un package supplémentaire, il peut y avoir des problèmes:

Exemple d'installation de package Dockerfile

FROM ubuntu:18.04
RUN apt -y update
RUN apt -y install nginx php-fpm
. . .

Nous avons ajouté un deuxième package à la commande d'installation exécutée par la deuxième instruction. Si un laps de temps important s'est écoulé depuis la génération de l'image précédente, la nouvelle génération peut échouer. C'est parce que l'instruction de mise à jour de l'index du package (RUN apt -y update) a changénot, donc Docker réutilise la couche d'image associée à cette instruction. Puisque nous utilisons un ancien index de package, la version du packagephp-fpm que nous avons dans nos enregistrements locaux peut ne plus être dans les référentiels, ce qui entraîne une erreur lors de l'exécution de la deuxième instruction.

Pour éviter ce scénario, assurez-vous de consolider toutes les étapes interdépendantes en une seule instructionRUN afin que Docker réexécute toutes les commandes nécessaires lorsqu'un changement se produit:

Exemple d'installation de package Dockerfile

FROM ubuntu:18.04
RUN apt -y update && apt -y install nginx php-fpm
. . .

L'instruction met maintenant à jour le cache de package local chaque fois que la liste de packages est modifiée.

Réduction de la taille de la couche d'image en modifiant les instructions d'exécution

L'exemple précédent montre comment le comportement de mise en cache de Docker peut renverser les attentes, mais il y a d'autres choses à garder à l'esprit concernant la façon dont les instructions deRUN interagissent avec le système de couches de Docker. Comme mentionné précédemment, à la fin de chaque instructionRUN, Docker valide les modifications en tant que couche d'image supplémentaire. Afin de contrôler l'étendue des couches d'image générées, vous pouvez nettoyer les fichiers inutiles dans l'environnement final qui seront validés en prêtant attention aux artefacts introduits par les commandes que vous exécutez.

En général, le chaînage des commandes en une seule instructionRUN offre un contrôle considérable sur la couche qui sera écrite. Pour chaque commande, vous pouvez configurer l’état de la couche (apt -y update), exécuter la commande principale (apt install -y nginx php-fpm) et supprimer tous les artefacts inutiles pour nettoyer l’environnement avant sa validation. Par exemple, de nombreux Dockerfiles enchaînentrm -rf /var/lib/apt/lists/* à la fin des commandesapt, supprimant les index des packages téléchargés, pour réduire la taille de la couche finale:

Exemple d'installation de package Dockerfile

FROM ubuntu:18.04
RUN apt -y update && apt -y install nginx php-fpm && rm -rf /var/lib/apt/lists/*
. . .

Pour réduire davantage la taille des couches d’image que vous créez, il peut être utile d’essayer de limiter d’autres effets secondaires non désirés des commandes que vous exécutez. Par exemple, en plus des packages explicitement déclarés,apt installe également les packages «recommandés» par défaut. Vous pouvez inclure--no-install-recommends dans vos commandesapt pour supprimer ce comportement. Vous devrez peut-être expérimenter pour savoir si vous comptez sur l’une des fonctionnalités fournies par les packages recommandés.

Nous avons utilisé les commandes de gestion de paquets dans cette section à titre d'exemple, mais ces mêmes principes s'appliquent à d'autres scénarios. L’idée générale est de créer les conditions préalables, d’exécuter la commande minimale viable, puis de nettoyer les artefacts inutiles en une seule commandeRUN afin de réduire la surcharge de la couche que vous produisez.

Utilisation de versions multi-étapes

Multi-stage builds ont été introduits dans Docker 17.05, permettant aux développeurs de contrôler plus étroitement les images d'exécution finales qu'ils produisent. Les versions en plusieurs étapes vous permettent de diviser votre fichier Dockerfile en plusieurs sections représentant des étapes distinctes, chacune avec une instructionFROM pour spécifier des images parentes distinctes.

Les sections précédentes définissent des images pouvant être utilisées pour créer votre application et préparer les actifs. Ceux-ci contiennent souvent des outils de construction et des fichiers de développement nécessaires à la production de l'application, mais non nécessaires à son exécution. Chaque étape ultérieure définie dans le fichier aura accès aux artefacts produits par les étapes précédentes.

La dernière instructionFROM définit l'image qui sera utilisée pour exécuter l'application. En règle générale, il s'agit d'une image réduite qui installe uniquement les exigences d'exécution requises, puis copie les artefacts d'application générés par les étapes précédentes.

Ce système vous permet de moins vous soucier de l'optimisation des instructionsRUN dans les étapes de construction puisque ces couches de conteneur ne seront pas présentes dans l'image d'exécution finale. Vous devez néanmoins faire attention à la manière dont les instructions interagissent avec la mise en cache des couches dans les étapes de construction, mais vos efforts peuvent viser à réduire le temps de construction plutôt que la taille de l'image finale. Il est toujours important de prêter attention aux instructions lors de la dernière étape pour réduire la taille de l’image, mais en séparant les différentes étapes de la construction de votre conteneur, il est plus facile d’obtenir des images rationalisées sans la complexité de Dockerfile.

Fonctionnalité de la portée au niveau du conteneur et du pod

Bien que les choix que vous faites en ce qui concerne les instructions de construction de conteneurs soient importants, les décisions générales relatives à la conteneurisation de vos services ont souvent un impact plus direct sur votre réussite. Dans cette section, nous allons parler un peu plus de la meilleure façon de faire la transition de vos applications d’un environnement plus conventionnel vers une plate-forme conteneur.

Conteneurisation par fonction

En règle générale, il est recommandé de regrouper chaque fonctionnalité indépendante dans une image de conteneur distincte.

Cela diffère des stratégies courantes utilisées dans les environnements de machines virtuelles, dans lesquels les applications sont fréquemment regroupées dans la même image afin de réduire la taille et de minimiser les ressources nécessaires à l'exécution de la machine virtuelle. Les conteneurs étant des abstractions légères qui ne virtualisent pas l’ensemble de la pile de système d’exploitation, ce compromis est moins convaincant pour Kubernetes. Ainsi, même si une machine virtuelle de pile Web peut regrouper un serveur Web Nginx avec un serveur d’applications Gunicorn sur une seule machine pour desservir une application Django, dans Kubernetes, celles-ci peuvent être fractionnées dans des conteneurs distincts.

Concevoir des conteneurs qui implémentent une fonctionnalité distincte pour vos services offre de nombreux avantages. Chaque conteneur peut être développé indépendamment si des interfaces standard entre services sont établies. Par exemple, le conteneur Nginx pourrait potentiellement être utilisé pour créer un proxy vers un certain nombre de backends différents ou pourrait être utilisé comme équilibreur de charge si une configuration différente lui était attribuée.

Une fois déployée, chaque image de conteneur peut être mise à l’échelle de manière indépendante pour prendre en charge diverses contraintes de ressources et de charge. En divisant vos applications en plusieurs images de conteneur, vous gagnez en flexibilité en développement, organisation et déploiement.

Combinaison d'images de conteneur dans des pods

Dans Kubernetes,pods est la plus petite unité pouvant être directement gérée par le plan de contrôle. Les pods consistent en un ou plusieurs conteneurs ainsi que des données de configuration supplémentaires pour indiquer à la plate-forme comment ces composants doivent être exécutés. Les conteneurs d'un pod sont toujours planifiés sur le même nœud de travail du cluster et le système redémarre automatiquement les conteneurs en échec. L'abstraction du pod est très utile, mais elle introduit une autre couche de décisions sur la manière de regrouper les composants de vos applications.

Comme les images de conteneur, les modules deviennent également moins flexibles lorsque trop de fonctionnalités sont regroupées dans une seule entité. Les pods eux-mêmes peuvent être mis à l'échelle à l'aide d'autres abstractions, mais les conteneurs qu'ils contiennent ne peuvent pas être gérés ou mis à l'échelle indépendamment. Par conséquent, pour continuer à utiliser notre exemple précédent, les conteneurs distincts Nginx et Gunicorn ne devraient probablement pas être regroupés dans un seul conteneur, de manière à pouvoir être contrôlés et déployés séparément.

Cependant, il existe des scénarios où il est logique de combiner des conteneurs fonctionnellement différents en une unité. En général, ces situations peuvent être classées comme des situations dans lesquelles un conteneur supplémentaire prend en charge ou améliore les fonctionnalités principales du conteneur principal ou l'aide à s'adapter à son environnement de déploiement. Certains modèles courants sont:

  • Sidecar: le conteneur secondaire étend les fonctionnalités principales du conteneur principal en jouant un rôle d’utilitaire de support. Par exemple, le conteneur sidecar peut transmettre des journaux ou mettre à jour le système de fichiers lorsqu'un référentiel distant est modifié. Le conteneur principal reste concentré sur sa responsabilité principale, mais est renforcé par les fonctionnalités fournies par le sidecar.

  • Ambassador: un conteneur ambassador est chargé de découvrir et de se connecter à des ressources externes (souvent complexes). Le conteneur principal peut se connecter à un conteneur ambassador sur des interfaces connues à l'aide de l'environnement du pod interne. L'ambassadeur extrait le trafic des ressources principales et des serveurs proxy entre le conteneur principal et le pool de ressources.

  • Adaptor: un conteneur d'adaptateur est chargé de normaliser les interfaces, les données et les protocoles des conteneurs principaux pour s'aligner sur les propriétés attendues par les autres composants. Le conteneur principal peut utiliser des formats natifs et le conteneur d'adaptateur convertit et normalise les données pour communiquer avec le monde extérieur.

Comme vous l'avez peut-être remarqué, chacun de ces modèles prend en charge la stratégie de création d'images de conteneur principal génériques standard pouvant ensuite être déployées dans divers contextes et configurations. Les conteneurs secondaires aident à combler le fossé entre le conteneur principal et l'environnement de déploiement spécifique utilisé. Certains conteneurs side-car peuvent également être réutilisés pour adapter plusieurs conteneurs principaux aux mêmes conditions environnementales. Ces modèles tirent parti du système de fichiers partagé et de l'espace de noms réseau fourni par l'abstraction du pod, tout en permettant un développement indépendant et un déploiement flexible de conteneurs normalisés.

Conception pour la configuration d'exécution

Il existe une certaine tension entre le désir de créer des composants normalisés réutilisables et les exigences liées à l’adaptation des applications à leur environnement d’exécution. La configuration d'exécution est l'une des meilleures méthodes pour combler le fossé entre ces préoccupations. Les composants sont conçus pour être à la fois généraux et flexibles. Le comportement requis est décrit lors de l'exécution en fournissant au logiciel des informations de configuration supplémentaires. Cette approche standard fonctionne aussi bien pour les conteneurs que pour les applications.

Construire avec une configuration d’exécution en tête vous oblige à penser à l’avenir lors du développement de l’application et de la conteneurisation. Les applications doivent être conçues pour lire des valeurs à partir de paramètres de ligne de commande, de fichiers de configuration ou de variables d’environnement lors du lancement ou du redémarrage. Cette logique d'analyse et d'injection de configuration doit être implémentée dans le code avant la conteneurisation.

Lors de l'écriture d'un fichier Docker, le conteneur doit également être conçu avec une configuration d'exécution à l'esprit. Les conteneurs disposent d'un certain nombre de mécanismes pour fournir des données au moment de l'exécution. Les utilisateurs peuvent monter des fichiers ou des répertoires à partir de l'hôte en tant que volumes dans le conteneur pour activer la configuration basée sur les fichiers. De même, les variables d'environnement peuvent être passées dans le runtime du conteneur interne au démarrage du conteneur. Les instructions DockerfileCMD etENTRYPOINT peuvent également être définies de manière à ce que les informations de configuration d'exécution soient transmises en tant que paramètres de commande.

Étant donné que Kubernetes manipule des objets de niveau supérieur, tels que des pods, au lieu de gérer directement les conteneurs, des mécanismes sont disponibles pour définir la configuration et l'injecter dans l'environnement du conteneur au moment de l'exécution. KubernetesConfigMaps etSecrets vous permettent de définir séparément les données de configuration, puis de projeter les valeurs dans l'environnement du conteneur en tant que variables d'environnement ou fichiers au moment de l'exécution. Les ConfigMaps sont des objets d'usage général destinés à stocker des données de configuration susceptibles de varier en fonction de l'environnement, de l'étape de test, etc. Les secrets offrent une interface similaire mais sont spécialement conçus pour les données sensibles, telles que les mots de passe des comptes ou les informations d'identification de l'API.

En comprenant et en utilisant correctement les options de configuration d'exécution disponibles dans chaque couche d'abstraction, vous pouvez créer des composants flexibles qui prennent exemple sur des valeurs fournies par l'environnement. Cela permet de réutiliser les mêmes images de conteneur dans des scénarios très différents, ce qui réduit les coûts de développement en améliorant la flexibilité des applications.

Mise en œuvre de la gestion des processus avec des conteneurs

Lors de la transition vers des environnements basés sur des conteneurs, les utilisateurs commencent souvent par transférer des charges de travail existantes, avec peu ou pas de modifications, vers le nouveau système. Ils conditionnent les applications dans des conteneurs en encapsulant les outils qu'ils utilisent déjà dans la nouvelle abstraction. Bien qu'il soit utile d'utiliser vos modèles habituels pour que les applications migrées soient opérationnelles, le fait d'abandonner des implémentations précédentes dans des conteneurs peut parfois conduire à une conception inefficace.

Traiter les conteneurs comme des applications, pas des services

Des problèmes surviennent fréquemment lorsque les développeurs implémentent une fonctionnalité de gestion de services significative dans des conteneurs. Par exemple, l’exécution de services systemd dans le conteneur ou la démonisation de serveurs Web peuvent être considérées comme des pratiques recommandées dans un environnement informatique normal, mais elles sont souvent en conflit avec les hypothèses inhérentes au modèle de conteneur.

Les hôtes gèrent les événements du cycle de vie du conteneur en envoyant des signaux au processus fonctionnant en tant que PID (ID de processus) 1 à l'intérieur du conteneur. Le premier processus lancé est le PID 1, qui serait le système init dans les environnements informatiques traditionnels. Toutefois, comme l'hôte ne peut gérer que le PID 1, l'utilisation d'un système init traditionnel pour gérer les processus au sein du conteneur signifie parfois qu'il est impossible de contrôler l'application principale. L’hôte peut démarrer, arrêter ou tuer le système init interne, mais ne peut pas gérer directement l’application principale. Les signaux propagent parfois le comportement souhaité à l'application en cours d'exécution, mais cela ajoute de la complexité et n'est pas toujours nécessaire.

La plupart du temps, il est préférable de simplifier l'environnement d'exécution dans le conteneur afin que le PID 1 exécute l'application principale au premier plan. Dans les cas où plusieurs processus doivent être exécutés, le PID 1 est responsable de la gestion du cycle de vie des processus ultérieurs. Certaines applications, comme Apache, traitent cela de manière native en créant et en gérant des travailleurs qui gèrent les connexions. Pour d'autres applications, un script wrapper ou un système d'initialisation très simple commedumb-init ou le système d'initialisationtini inclus peut être utilisé dans certains cas. Quelle que soit l'implémentation que vous choisissez, le processus exécuté en tant que PID 1 dans le conteneur doit répondre de manière appropriée aux signauxTERM envoyés par Kubernetes pour se comporter comme prévu.

Gestion de la santé des conteneurs dans Kubernetes

Les déploiements et services Kubernetes offrent une gestion du cycle de vie des processus de longue durée et un accès fiable et persistant aux applications, même lorsque les conteneurs sous-jacents doivent être redémarrés ou que les implémentations changent. En retirant la responsabilité de surveiller et de maintenir la santé du service hors du conteneur, vous pouvez utiliser les outils de la plate-forme pour gérer des charges de travail saines.

Pour que Kubernetes puisse gérer correctement les conteneurs, il doit comprendre si les applications qui y sont exécutées sont saines et capables d’effectuer un travail. Pour ce faire, les conteneurs peuvent implémenter des sondes d'activité: points de terminaison réseau ou commandes pouvant être utilisées pour signaler la santé de l'application. Kubernetes vérifiera périodiquement les sondes d'activité définies pour déterminer si le conteneur fonctionne comme prévu. Si le conteneur ne répond pas correctement, Kubernetes le redémarre pour tenter de rétablir la fonctionnalité.

Kubernetes fournit également des sondes de préparation, une construction similaire. Plutôt que d'indiquer si l'application contenue dans un conteneur est saine, les tests de disponibilité déterminent si l'application est prête à recevoir du trafic. Cela peut être utile lorsqu'une application conteneurisée a une routine d'initialisation qui doit être terminée avant d'être prête à recevoir des connexions. Kubernetes utilise des sondes de disponibilité pour déterminer s'il convient d'ajouter ou de supprimer un pod d'un service.

La définition de points de terminaison pour ces deux types de sondes peut aider Kubernetes à gérer efficacement vos conteneurs et éviter que des problèmes de cycle de vie des conteneurs affectent la disponibilité du service. Les mécanismes permettant de répondre à ces types de demandes d'intégrité doivent être intégrés à l'application elle-même et exposés dans la configuration de l'image Docker.

Conclusion

Dans ce guide, nous avons abordé certaines considérations importantes à garder à l'esprit lorsque
exécute des applications conteneurisées dans Kubernetes. Pour réitérer, certaines des suggestions de
que nous avons examinées étaient:

  • Utilisez un minimum d'images partageables pour créer des images avec une surcharge minime et réduire le temps de démarrage

  • Utilisez des versions en plusieurs étapes pour séparer les environnements de génération de conteneur et d'exécution

  • Combinez les instructions de Dockerfile pour créer des couches d'image propres et éviter les erreurs de mise en cache des images.

  • Containerize en isolant des fonctionnalités discrètes pour permettre une évolutivité et une gestion flexibles

  • Concevoir des modules pour qu'ils assument une responsabilité unique et ciblée

  • Regroupez les conteneurs auxiliaires pour améliorer la fonctionnalité du conteneur principal ou pour l’adapter à l’environnement de déploiement

  • Construisez des applications et des conteneurs pour répondre à la configuration d'exécution afin de permettre une plus grande flexibilité lors du déploiement

  • Exécuter des applications en tant que processus principaux dans des conteneurs afin que Kubernetes puisse gérer les événements du cycle de vie

  • Développer des points de terminaison d'intégrité et de vitalité au sein de l'application ou du conteneur afin que Kubernetes puisse surveiller l'intégrité du conteneur

Tout au long du processus de développement et de mise en œuvre, vous devrez prendre des décisions qui peuvent affecter la robustesse et l’efficacité de votre service. Comprendre les différences entre les applications conteneurisées et les applications conventionnelles et leur fonctionnement dans un environnement de cluster géré peut vous aider à éviter certains pièges courants et à tirer parti de toutes les fonctionnalités fournies par Kubernetes.