Construire des conteneurs optimisés pour Kubernetes

De Get Docs
Aller à :navigation, rechercher

Introduction

Les images de conteneur sont le principal format de packaging pour définir des applications dans Kubernetes. Les images sont utilisées comme base pour les pods et autres objets, et jouent un rôle important dans l'exploitation efficace des fonctionnalités de Kubernetes. Les images bien conçues sont sécurisées, hautement performantes et ciblées. Ils sont capables de réagir aux données de configuration ou aux instructions fournies par Kubernetes. Ils implémentent des points de terminaison que le déploiement utilise pour comprendre l'état de leur 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 aider à guider vos décisions 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 de ces 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 création d'images de conteneurs, 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 le fait de bien faire une petite chose. Les applications peuvent être coordonnées en dehors de la portée d'un conteneur individuel pour fournir des fonctionnalités plus 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 en pensant à la réutilisation lorsque cela est 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 tester vos images avant de les déployer 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 images plus petites présentent un certain nombre d'avantages dans des environnements en cluster tels que Kubernetes. Ils se téléchargent rapidement sur de nouveaux nœuds et disposent souvent d'un plus petit ensemble de packages installés, ce qui peut améliorer la sécurité. Les images de conteneur simplifiées simplifient 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, y compris 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 en dehors 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, la possibilité d'afficher directement les configurations et les journaux, ou de tester le comportement du conteneur, peut vous aider à trouver une solution plus rapidement. Considérer les images de conteneur comme un format d'emballage pour votre application plutôt qu'une configuration de machine peut vous aider à trouver le bon équilibre.

Suivez les meilleures pratiques en matière de 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 conventionnelles, telles que l'inclusion de systèmes d'initialisation complets et la démonisation d'applications. Connectez-vous à stdout afin que Kubernetes puisse exposer les données aux administrateurs au lieu d'utiliser un démon de journalisation interne. Ces recommandations divergent largement des meilleures pratiques pour les systèmes d'exploitation complets.

Exploitez pleinement les fonctionnalités de Kubernetes

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

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

Réutiliser des couches de base minimales et partagées

Nous pouvons commencer par examiner les ressources à partir desquelles les images de conteneur sont construites : les images de base. Chaque image de conteneur est construite soit à partir d'une image parente, une image utilisée comme point de départ, soit à partir de la couche abstraite scratch, une couche d'image vide sans système de fichiers. Une image de base est une image de conteneur qui sert de base aux futures images en définissant le système d'exploitation de base et en fournissant des fonctionnalités de base. Les images sont composées d'une ou de plusieurs couches d'images construites les unes sur les autres pour former une image finale.

Aucun utilitaire ou système de fichiers standard n'est disponible lorsque vous travaillez directement à partir de scratch, 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 de scratch 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 qui configure un environnement de base dans lequel vos applications s'exécutent afin que vous n'ayez pas à créer un système complet pour chaque image.

Bien qu'il existe des images de base pour une variété de distributions Linux, il est préférable d'être délibéré sur les systèmes que vous choisissez. Chaque nouvelle machine devra télécharger l'image parente et toutes les couches supplémentaires que vous avez ajoutées. Pour les images volumineuses, cela peut consommer une quantité importante de bande passante et allonger sensiblement le temps de démarrage de vos conteneurs lors de leur première exécution. Il n'y a aucun moyen de réduire une image qui est utilisée comme parent en aval dans le processus de construction du conteneur, donc commencer avec un parent minimal est une bonne idée.

Les environnements riches en fonctionnalités comme Ubuntu permettent à votre application de s'exécuter dans un environnement que vous connaissez bien, mais il y a quelques compromis à prendre en compte. Les images Ubuntu (et les images de distribution conventionnelles similaires) ont tendance à être relativement volumineuses (plus de 100 Mo), ce qui signifie que toutes les images de conteneur construites à partir d'elles 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 comprend un gestionnaire de packages avec des référentiels importants et dispose de la plupart des utilitaires standard que vous attendez d'un environnement Linux minimal.

Lors de la conception de vos applications, c'est une bonne idée d'essayer 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échargent la couche parent qu'une seule fois. Ensuite, ils n'auront qu'à télécharger les calques qui diffèrent entre vos images. Cela signifie que si vous avez des caractéristiques ou des fonctionnalités communes que vous souhaitez intégrer dans chaque image, créer une image parent commune dont hériter peut être une bonne idée. Les images qui partagent une lignée aident à minimiser 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 parente, vous pouvez définir votre image de conteneur en ajoutant des logiciels supplémentaires, en copiant des fichiers, en exposant des ports et en choisissant les processus à exécuter. Certaines instructions dans le fichier de configuration de l'image (par exemple, un Dockerfile si vous utilisez Docker) ajouteront des couches supplémentaires à votre image.

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

Comprendre les calques d'image et construire le cache

Docker crée un nouveau calque d'image chaque fois qu'il exécute une instruction RUN, COPY ou ADD. Si vous construisez à nouveau l'image, le moteur de génération vérifiera chaque instruction pour voir si elle a une couche d'image 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 comme COPY et ADD, Docker compare les sommes de contrôle des fichiers pour voir si l'opération doit être effectuée à nouveau. Pour les instructions RUN, Docker vérifie s'il a une couche d'image existante mise en cache pour cette chaîne de commande particulière.

Bien qu'il ne soit pas immédiatement évident, ce comportement peut entraîner des résultats inattendus si vous ne faites pas attention. Un exemple courant de ceci est la mise à jour de l'index local des packages et l'installation des packages en deux étapes distinctes. Nous utiliserons Ubuntu pour cet exemple, mais le principe de base s'applique également aux images de base des autres distributions :

Exemple d'installation de package Dockerfile

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

Ici, l'index de package local est mis à jour dans une instruction RUN (apt -y update) et Nginx est installé dans une autre opération. Cela fonctionne sans problème lors de la première utilisation. Cependant, si le 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:20.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 significatif s'est écoulé depuis la création de l'image précédente, la nouvelle génération peut échouer. En effet, l'instruction de mise à jour de l'index du package (RUN apt -y update) a pas changé, donc Docker réutilise la couche d'image associée à cette instruction. Étant donné que nous utilisons un ancien index de package, la version du package php-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, veillez à regrouper toutes les étapes interdépendantes en une seule instruction RUN afin que Docker réexécute toutes les commandes nécessaires en cas de modification. Dans les scripts shell, l'utilisation de l'opérateur logique AND &&, qui exécutera plusieurs commandes sur la même ligne tant qu'elles réussissent chacune, est un bon moyen d'y parvenir :

Exemple d'installation de package Dockerfile

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

L'instruction met désormais à jour le cache de packages local chaque fois que la liste de packages change. Une alternative serait de RUN un script shell.sh entier qui contient plusieurs lignes d'instructions, mais qui devrait d'abord être mis à la disposition du conteneur.

Réduction de la taille de la couche d'image en ajustant les instructions RUN

L'exemple précédent montre comment le comportement de mise en cache de Docker peut subvertir les attentes, mais il y a d'autres choses à garder à l'esprit sur la façon dont les instructions RUN interagissent avec le système de couches de Docker. Comme mentionné précédemment, à la fin de chaque instruction RUN, Docker valide les modifications en tant que couche d'image supplémentaire. Afin d'exercer un contrôle sur l'étendue des calques d'image produits, vous pouvez nettoyer les fichiers inutiles 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 instruction RUN offre un grand contrôle 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 qu'il ne soit validé. Par exemple, de nombreux Dockerfiles enchaînent rm -rf /var/lib/apt/lists/* à la fin des commandes apt, en supprimant les index de package téléchargés, pour réduire la taille de la couche finale :

Exemple d'installation de package Dockerfile

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

Pour réduire davantage la taille des calques d'image que vous créez, il peut être utile d'essayer de limiter les autres effets secondaires imprévus 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 à vos commandes apt 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 des packages 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 construire les conditions préalables, d'exécuter la commande minimale viable, puis de nettoyer tous les artefacts inutiles dans une seule commande RUN pour réduire la surcharge de la couche que vous allez produire.

Utiliser des builds en plusieurs étapes

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

Les sections précédentes définissent les images qui peuvent être utilisées pour créer votre application et préparer les ressources. Ceux-ci contiennent souvent des outils de construction et des fichiers de développement qui sont nécessaires pour produire l'application, mais qui ne sont pas nécessaires pour l'exécuter. Chaque étape ultérieure définie dans le fichier aura accès aux artefacts produits par les étapes précédentes.

La dernière instruction FROM 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 simplifiée qui installe uniquement les exigences d'exécution nécessaires, puis copie les artefacts d'application produits par les étapes précédentes.

Ce système vous permet de vous soucier moins de l'optimisation des instructions RUN dans les étapes de construction puisque ces couches de conteneur ne seront pas présentes dans l'image d'exécution finale. Vous devez toujours faire attention à la façon dont les instructions interagissent avec la mise en cache des couches dans les étapes de construction, mais vos efforts peuvent être orientés vers la réduction du temps de construction plutôt que de la taille finale de l'image. Il est toujours important de prêter attention aux instructions dans l'étape finale 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 simplifiées sans autant de complexité Dockerfile.

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

Bien que les choix que vous faites concernant les instructions de construction de conteneurs soient importants, des décisions plus larges sur la façon de conteneuriser vos services ont souvent un impact plus direct sur votre réussite. Dans cette section, nous parlerons un peu plus de la meilleure façon de faire passer vos applications d'un environnement plus conventionnel à une exécution sur une plate-forme de conteneurs.

Conteneurisation par fonction

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

Cela diffère des stratégies courantes utilisées dans les environnements de machines virtuelles où les applications sont fréquemment regroupées dans la même image pour réduire la taille et minimiser les ressources nécessaires pour exécuter la machine virtuelle. Étant donné que les conteneurs sont des abstractions légères qui ne virtualisent pas l'ensemble de la pile du système d'exploitation, ce compromis est moins convaincant sur Kubernetes. Ainsi, alors qu'une machine virtuelle à pile Web peut regrouper un serveur Web Nginx avec un serveur d'applications Gunicorn sur une seule machine pour servir une application Django, dans Kubernetes, ceux-ci peuvent être divisés en conteneurs séparés.

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

Une fois déployée, chaque image de conteneur peut être mise à l'échelle indépendamment pour répondre aux différentes contraintes de ressources et de charge. En divisant vos applications en plusieurs images de conteneur, vous gagnez en flexibilité dans le développement, l'organisation et le déploiement.

Combiner des images de conteneurs dans des pods

Dans Kubernetes, les pods sont 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 noeud worker dans le cluster et le système redémarre automatiquement les conteneurs défaillants. L'abstraction de 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 conteneurs, les pods 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. Donc, pour continuer à utiliser notre exemple précédent, les conteneurs Nginx et Gunicorn séparés ne devraient probablement pas être regroupés dans un seul pod. De cette façon, ils peuvent être contrôlés et déployés séparément.

Cependant, il existe des scénarios dans lesquels il est logique de combiner des conteneurs fonctionnellement différents en une seule unité. En général, celles-ci peuvent être classées comme des situations dans lesquelles un conteneur supplémentaire prend en charge ou améliore la fonctionnalité de base 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 de base du conteneur principal en jouant un rôle d'utilitaire de support. Par exemple, le conteneur side-car peut transférer des journaux ou mettre à jour le système de fichiers lorsqu'un référentiel distant change. Le conteneur principal reste concentré sur sa responsabilité principale, mais est renforcé par les fonctionnalités fournies par le side-car.
  • Ambassadeur : un conteneur ambassadeur est chargé de découvrir et de se connecter à des ressources externes (souvent complexes). Le conteneur principal peut se connecter à un conteneur ambassadeur sur des interfaces bien connues à l'aide de l'environnement de pod interne. L'ambassadeur résume le trafic des ressources backend et des proxys 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 du conteneur principal afin de les aligner sur les propriétés attendues par les autres composants. Le conteneur principal peut fonctionner à l'aide de formats natifs et le conteneur adaptateur traduit 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 conteneurs primaires génériques standard qui peuvent ensuite être déployées dans divers contextes et configurations. Les conteneurs secondaires permettent de combler l'écart 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 primaires aux mêmes conditions environnementales. Ces modèles bénéficient du système de fichiers partagé et de l'espace de noms réseau fournis par l'abstraction de pod tout en permettant un développement indépendant et un déploiement flexible de conteneurs standardisés.

Conception pour la configuration d'exécution

Il existe une certaine tension entre le désir de créer des composants standardisés et 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 l'écart entre ces préoccupations. De cette façon, les composants sont conçus pour être à usage général et leur comportement requis est décrit au moment de l'exécution en fournissant des détails de configuration supplémentaires. Cette approche standard fonctionne aussi bien pour les conteneurs que pour les applications.

Construire avec la configuration d'exécution à l'esprit vous oblige à réfléchir à l'avance pendant les étapes de développement et de conteneurisation de l'application. Les applications doivent être conçues pour lire les valeurs des paramètres de ligne de commande, des fichiers de configuration ou des variables d'environnement lorsqu'elles sont lancées ou redémarrées. 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 Dockerfile, le conteneur doit également être conçu en tenant compte de la configuration d'exécution. Les conteneurs disposent d'un certain nombre de mécanismes pour fournir des données lors 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 transmises à l'environnement d'exécution interne du conteneur lors du démarrage du conteneur. Les instructions Dockerfile CMD et ENTRYPOINT peuvent également être définies de manière à permettre la transmission des informations de configuration d'exécution en tant que paramètres de ligne de commande.

Étant donné que Kubernetes manipule des objets de niveau supérieur comme les pods au lieu de gérer directement les conteneurs, il existe des mécanismes disponibles pour définir la configuration et l'injecter dans l'environnement du conteneur au moment de l'exécution. Kubernetes ConfigMaps et Secrets vous permettent de définir les données de configuration séparément, puis de projeter ces valeurs dans l'environnement de conteneur au moment de l'exécution. Les ConfigMaps sont des objets à usage général destinés à stocker des données de configuration qui peuvent varier en fonction de l'environnement, de l'étape de test, etc. Les secrets offrent une interface similaire mais sont spécifiquement conçus pour les données sensibles, comme les mots de passe de compte 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 s'inspirent des valeurs fournies par l'environnement. Cela permet de réutiliser les mêmes images de conteneurs dans des scénarios très différents, réduisant ainsi les frais généraux 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 déplacer les charges de travail existantes, avec peu ou pas de modifications, vers le nouveau système. Ils conditionnent les applications dans des conteneurs en enveloppant 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, l'abandon des implémentations précédentes dans les conteneurs peut parfois conduire à une conception inefficace.

Traiter les conteneurs comme des applications, pas comme des services

Des problèmes surviennent fréquemment lorsque les développeurs implémentent une fonctionnalité de gestion de service importante dans les 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 les meilleures pratiques dans un environnement informatique normal, mais elles entrent 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. PID 1 est le premier processus démarré, qui serait le système init dans les environnements informatiques traditionnels. Cependant, étant donné que l'hôte ne peut gérer que le PID 1, l'utilisation d'un système d'initialisation conventionnel pour gérer les processus au sein du conteneur signifie parfois qu'il n'y a aucun moyen de contrôler l'application principale. L'hôte peut démarrer, arrêter ou tuer le système d'initialisation interne, mais ne peut pas gérer directement l'application principale. Ces signaux peuvent parfois propager le comportement prévu à l'application en cours d'exécution, mais cela ajoute encore 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 suivants. Certaines applications, comme Apache, gèrent cela de manière native en engendrant et en gérant les travailleurs qui gèrent les connexions. Pour d'autres applications, un script wrapper ou un système d'initialisation très léger comme dumb-init ou le système d'initialisation tini inclus peut être utilisé. 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 signaux TERM envoyés par Kubernetes pour se comporter comme prévu.

Gérer la santé des conteneurs dans Kubernetes

Les déploiements et les services Kubernetes offrent une gestion du cycle de vie pour les 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 elles-mêmes changent. En extrayant la responsabilité de surveiller et de maintenir l'intégrité du service hors du conteneur, vous pouvez tirer parti des outils de la plate-forme pour gérer des charges de travail saines.

Pour que Kubernetes gère correctement les conteneurs, il doit comprendre si les applications exécutées dans les conteneurs sont saines et capables d'effectuer des tâches. Pour ce faire, les conteneurs peuvent implémenter des sondes de vivacité : c'est-à-dire des points de terminaison réseau ou des commandes pouvant être utilisées pour signaler l'état 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 redémarre le conteneur 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 dans un conteneur est saine, les sondes de préparation 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 se terminer avant d'être prête à recevoir des connexions. Kubernetes utilise des sondes de préparation 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 sonde peut aider Kubernetes à gérer efficacement vos conteneurs et peut empêcher les problèmes de cycle de vie des conteneurs d'affecter 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 doivent être exposés dans la configuration de l'image Docker.

Conclusion

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

  • Utilisez des images parentes minimales et partageables pour créer des images avec un minimum de surcharge et réduire le temps de démarrage
  • Utiliser des builds en plusieurs étapes pour séparer les environnements de build et d'exécution du conteneur
  • Combinez les instructions Dockerfile pour créer des calques d'image propres et éviter les erreurs de mise en cache des images
  • Conteneuriser en isolant les fonctionnalités discrètes pour permettre une mise à l'échelle et une gestion flexibles
  • Concevoir des pods pour avoir une responsabilité unique et ciblée
  • Regroupez les conteneurs auxiliaires pour améliorer les fonctionnalités du conteneur principal ou pour l'adapter à l'environnement de déploiement
  • Créez 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écutez 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 de santé et de vivacité dans l'application ou le conteneur afin que Kubernetes puisse surveiller la santé du conteneur

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