Version imprimable multipages. Cliquer ici pour imprimer.
Pods
- 1: Aperçu du Pod
- 2: Pods
- 3: Cycle de vie d'un Pod
- 4: Contraintes de propagation de topologie pour les Pods
- 5: Init Containers
1 - Aperçu du Pod
Cette page fournit un aperçu du Pod
, l'objet déployable le plus petit dans le modèle d'objets Kubernetes.
Comprendre les Pods
Un Pod est l'unité d'exécution de base d'une application Kubernetes--l'unité la plus petite et la plus simple dans le modèle d'objets de Kubernetes--que vous créez ou déployez. Un Pod représente des process en cours d'exécution dans votre cluster.
Un Pod encapsule un conteneur applicatif (ou, dans certains cas, plusieurs conteneurs), des ressources de stockage, une identité réseau (adresse IP) unique, ainsi que des options qui contrôlent comment le ou les conteneurs doivent s'exécuter. Un Pod représente une unité de déploiement : une instance unique d'une application dans Kubernetes, qui peut consister soit en un unique container soit en un petit nombre de conteneurs qui sont étroitement liés et qui partagent des ressources.
Docker est le runtime de conteneurs le plus courant utilisé dans un Pod Kubernetes, mais les Pods prennent également en charge d'autres runtimes de conteneurs.
Les Pods dans un cluster Kubernetes peuvent être utilisés de deux manières différentes :
- les Pods exécutant un conteneur unique. Le modèle "un-conteneur-par-Pod" est le cas d'utilisation Kubernetes le plus courant ; dans ce cas, vous pouvez voir un Pod comme un wrapper autour d'un conteneur unique, et Kubernetes gère les Pods plutôt que directement les conteneurs.
- les Pods exécutant plusieurs conteneurs devant travailler ensemble. Un Pod peut encapsuler une application composée de plusieurs conteneurs co-localisés qui sont étroitement liés et qui doivent partager des ressources. Ces conteneurs co-localisés pourraient former une unique unité de service cohésive--un conteneur servant des fichiers d'un volume partagé au public, alors qu'un conteneur "sidecar" séparé rafraîchit ou met à jour ces fichiers. Le Pod enveloppe ensemble ces conteneurs et ressources de stockage en une entité maniable de base.
Chaque Pod est destiné à exécuter une instance unique d'une application donnée. Si vous désirez mettre à l'échelle votre application horizontalement, (pour fournir plus de ressources au global en exécutant plus d'instances), vous devez utiliser plusieurs Pods, un pour chaque instance. Dans Kubernetes, on parle typiquement de réplication. Des Pods répliqués sont en général créés et gérés en tant que groupe par une ressource de charge de travail et son _contrôleur_. Voir Pods et contrôleurs pour plus d'informations.
Comment les Pods gèrent plusieurs conteneurs
Les Pods sont conçus pour supporter plusieurs process coopérants (sous forme de conteneurs) qui forment une unité de service cohésive. Les conteneurs d'un même Pod sont automatiquement co-localisés et co-programmés sur la même machine physique ou virtuelle dans le cluster. Ces conteneurs peuvent partager des ressources et dépendances, communiquer entre eux, et coordonner quand et comment ils sont arrêtés.
Notez que grouper plusieurs conteneurs co-localisés et co-gérés dans un unique Pod est un cas d'utilisation relativement avancé. Vous devez utiliser ce pattern seulement dans des instances spécifiques dans lesquelles vos conteneurs sont étroitement liés. Par exemple, vous pourriez avoir un conteneur qui agit comme un serveur web pour des fichiers contenus dans un volume partagé, et un conteneur "sidecar" séparé qui met à jour ces fichiers depuis une source externe, comme dans le diagramme suivant :
Certains Pods ont des init containers en plus d'app containers. Les Init containers s'exécutent et terminent avant que les conteneurs d'application soient démarrés.
Les Pods fournissent deux types de ressources partagées pour leurs conteneurs : réseau et stockage.
Réseau
Chaque Pod se voit assigner une adresse IP unique pour chaque famille d'adresses. Tous les conteneurs d'un Pod partagent le même namespace réseau, y compris l'adresse IP et les ports réseau. Les conteneurs à l'intérieur d'un Pod peuvent communiquer entre eux en utilisant localhost
. Lorsque les conteneurs dans un Pod communiquent avec des entités en dehors du Pod, ils doivent coordonner comment ils utilisent les ressources réseau partagées (comme les ports).
Stockage
Un Pod peut spécifier un jeu de volumes de stockage partagés. Tous les conteneurs dans le Pod peuvent accéder aux volumes partagés, permettant à ces conteneurs de partager des données. Les volumes permettent aussi les données persistantes d'un Pod de survivre au cas où un des conteneurs doit être redémarré. Voir Volumes pour plus d'informations sur la façon dont Kubernetes implémente le stockage partagé dans un Pod.
Travailler avec des Pods
Vous aurez rarement à créer directement des Pods individuels dans Kubernetes--même des Pods à un seul conteneur. Ceci est dû au fait que les Pods sont conçus comme des entités relativement éphémères et jetables. Lorsqu'un Pod est créé (directement par vous ou indirectement par un _contrôleur_), il est programmé pour s'exécuter sur un Node dans votre cluster. Le Pod reste sur ce nœud jusqu'à ce que le process se termine, l'objet pod soit supprimé, le pod soit expulsé par manque de ressources, ou le nœud soit en échec.
Les Pods ne se guérissent pas par eux-mêmes. Si un Pod est programmé sur un Nœud qui échoue, ou si l'opération de programmation elle-même échoue, le Pod est supprimé ; de plus, un Pod ne survivra pas à une expulsion due à un manque de ressources ou une mise en maintenance du Nœud. Kubernetes utilise une abstraction de plus haut niveau, appelée un contrôleur, qui s'occupe de gérer les instances de Pods relativement jetables. Ainsi, même s'il est possible d'utiliser des Pods directement, il est beaucoup plus courant dans Kubernetes de gérer vos Pods en utilisant un contrôleur.
Pods et contrôleurs
Vous pouvez utiliser des ressources de charges de travail pour créer et gérer plusieurs Pods pour vous. Un contrôleur pour la ressource gère la réplication, le plan de déploiement et la guérison automatique en cas de problèmes du Pod. Par exemple, si un noeud est en échec, un contrôleur note que les Pods de ce noeud ont arrêté de fonctionner et créent des Pods pour les remplacer. L'ordonnanceur place le Pod de remplacement sur un noeud en fonctionnement.
Voici quelques exemples de ressources de charges de travail qui gèrent un ou plusieurs Pods :
Templates de Pod
Les Templates de Pod sont des spécifications pour créer des Pods, et sont inclus dans les ressources de charges de travail comme les Deployments, les Jobs et les DaemonSets.
Chaque contrôleur pour une ressource de charges de travail utilise le template de pod à l'intérieur de l'objet pour créer les Pods. Le template de pod fait partie de l'état désiré de la ressource de charges de travail que vous avez utilisé pour exécuter votre application.
L'exemple ci-dessous est un manifest pour un Job simple avec un template
qui démarre un conteneur. Le conteneur dans ce Pod affiche un message puis se met en pause.
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
template:
# Ceci est un template de pod
spec:
containers:
- name: hello
image: busybox
command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
restartPolicy: OnFailure
# Le template de pod se termine ici
Modifier le template de pod ou changer pour un nouvau template de pod n'a pas d'effet sur les pods déjà existants. Les Pods ne reçoivent pas une mise à jour du template directement ; au lieu de cela, un nouveau Pod est créé pour correspondre au nouveau template de pod.
Par exemple, un contrôleur de Deployment s'assure que les Pods en cours d'exécution correspondent au template de pod en cours. Si le template est mis à jour, le contrôleur doit supprimer les pods existants et créer de nouveaux Pods avec le nouveau template. Chaque contrôleur de charges de travail implémente ses propres règles pour gérer les changements du template de Pod.
Sur les noeuds, le kubelet n'observe ou ne gère pas directement les détails concernant les templates de pods et leurs mises à jours ; ces détails sont abstraits. Cette abstraction et cette séparation des préoccupations simplifie la sémantique du système, et rend possible l'extension du comportement du cluster sans changer le code existant.
A suivre
- En savoir plus sur les Pods
- The Distributed System Toolkit: Patterns for Composite Containers explique les dispositions courantes pour des Pods avec plusieurs conteneurs
- En savoir plus sur le comportement des Pods :
2 - Pods
Les Pods sont les plus petites unités informatiques déployables qui peuvent être créées et gérées dans Kubernetes.
Qu'est-ce qu'un pod ?
Un pod (terme anglo-saxon décrivant un groupe de baleines ou une gousse de pois) est un groupe d'un ou plusieurs conteneurs (comme des conteneurs Docker), ayant du stockage/réseau partagé, et une spécification sur la manière d'exécuter ces conteneurs. Les éléments d'un pod sont toujours co-localisés et co-ordonnancés, et s'exécutent dans un contexte partagé. Un pod modélise un "hôte logique" spécifique à une application - il contient un ou plusieurs conteneurs applicatifs qui sont étroitement liés — dans un monde pré-conteneurs, être exécuté sur la même machine physique ou virtuelle signifierait être exécuté sur le même hôte logique.
Bien que Kubernetes prenne en charge d'autres runtimes de conteneurs que Docker, Docker est le runtime le plus connu, et cela aide à décrire des pods en termes Docker.
Le contexte partagé d'un pod est un ensemble de namespaces Linux, cgroups, et potentiellement d'autres facettes d'isolation - les mêmes choses qui isolent un conteneur Docker. Dans le contexte d'un pod, les applications individuelles peuvent se voir appliquer d'autres sous-isolations.
Les conteneurs d'un pod partagent une adresse IP et un espace de ports, et peuvent communiquer via localhost
.
Ils peuvent aussi communiquer entre eux en utilisant des communications inter-process standard comme
les sémaphores SystemV ou la mémoire partagée POSIX. Les conteneurs appartenant à des pods distincts ont des adresses IP
distinctes et ne peuvent pas communiquer par IPC sans configuration spécifique. Ces conteneurs communiquent en général entre eux via les adresses IP de leurs pods.
Les applications à l'intérieur d'un pod ont aussi accès à des volumes partagés, qui sont définis dans le cadre d'un pod et sont mis à disposition pour être montés dans le système de fichiers de chaque application.
En terme de concepts Docker, un pod est modélisé par un groupe de conteneurs Docker ayant des namespaces et des volumes partagés.
Tout comme des conteneurs applicatifs individuels, les pods sont considérés comme des entités relativement éphémères (plutôt que durables). Comme discuté dans Cycle de vie d'un pod, les pods sont créés, des ID uniques (UID) leurs sont assignés, et ils sont ordonnancés sur des nœuds où il restent jusqu'à leur arrêt (selon la politique de redémarrage) ou suppression. Si un nœud meurt, les pods ordonnancés sur ce nœud sont programmés pour être terminés, après un délai d'attente. Un pod donné (défini par un UID) n'est pas "re-ordonnancé" sur un nouveau nœud ; par contre, il peut être remplacé par un pod identique, ayant le même nom si désiré, mais avec un nouvel UID (voir replication controller pour plus de détails).
Lorsque quelque chose, comme un volume, a le même cycle de vie qu'un pod, il existe aussi longtemps que le pod (avec l'UID donné) existe. Si ce pod est supprimé pour une quelconque raison, même si un remplaçant identique est recréé, la chose liée (par ex. le volume) est aussi détruite et créée à nouveau.
Un pod multi-conteneurs contenant un extracteur de fichiers et un serveur web utilisant un volume persistant comme espace de stockage partagé entre les conteneurs.
Intérêts des pods
Gestion
Les pods fournissent une unité de service cohérente afin d'avoir un modèle coopératif entre plusieurs processus. Ils simplifient le déploiement et la gestion d'applications en fournissant une abstraction de plus haut niveau que l'ensemble des applications les constituant. Les pods servent d'unité de déploiement, de mise à l'échelle horizontale, et de réplication. La co-localisation (co-ordonnancement), la fin partagée (par ex. l'arrêt), la réplication coordonnée, le partage de ressources et la gestion des dépendances sont traités automatiquement pour les conteneurs dans un pod.
Partage de ressources et communication
Les pods permettent le partage de ressources et la communication entre ses constituants.
Les applications dans un pod utilisent toutes le même réseau (même adresse IP et espace de ports)
et peuvent donc "se trouver" entre elles et communiquer en utilisant localhost
.
À cause de cela, les applications dans un pod doivent coordonner leurs usages de ports.
Chaque pod a une adresse IP dans un réseau plat partagé ayant un accès complet
aux autres hôtes et pods à travers le réseau.
Le nom d'hôte est défini avec le nom du pod pour les conteneurs applicatifs à l'intérieur du pod. Plus de détails sur le réseau.
En plus de définir les conteneurs applicatifs s'exécutant dans le pod, le pod spécifie un ensemble de volumes de stockage partagés. Les volumes permettent aux données de survivre aux redémarrages de conteneurs et d'être partagés entre les applications d'un même pod.
Cas d'utilisation de pods
Des pods peuvent être utilisés pour héberger verticalement des piles applicatives intégrées (par ex. LAMP), mais leur principal intérêt est la mise en place de programmes auxiliaires co-localisés et co-gérés, comme :
- systèmes de gestion de contenu, chargeurs de fichiers et de données, gestionnaires de cache local, etc.
- sauvegarde de log et checkpoint, compression, rotation, prise d'instantanés, etc.
- data change watchers, log tailers, adaptateurs de logs et monitoring, éditeurs d'événements, etc.
- proxies, bridges et adaptateurs
- contrôleurs, gestionnaires, configurateurs et gestionnaires de mise à jour
Des pods individuels ne sont pas destinés à exécuter plusieurs instances de la même application, en général.
Pour une explication plus détaillée, voir The Distributed System ToolKit: Patterns for Composite Containers.
Alternatives envisagées
Pourquoi ne pas simplement exécuter plusieurs programmes dans un unique conteneur (Docker) ?
- Transparence. Rendre les conteneurs à l'intérieur du pod visibles par l'infrastucture permet à l'infrastucture de fournir des services à ces conteneurs, comme la gestion des processus et le monitoring des ressources. Ceci apporte un certain nombre de facilités aux utilisateurs.
- Découpler les dépendances logicielles. Les conteneurs individuels peuvent être versionnés, reconstruits et redéployés de manière indépendante. Kubernetes pourrait même un jour prendre en charge la mise à jour à chaud de conteneurs individuels.
- Facilité d'utilisation. Les utilisateurs n'ont pas besoin d'exécuter leur propre gestionnaire de processus, de se soucier de la propagation de signaux et de codes de sortie, etc.
- Efficacité. L'infrastructure prenant plus de responsabilités, les conteneurs peuvent être plus légers.
Pourquoi ne pas prendre en charge le co-ordonnancement de conteneurs basé sur les affinités ?
Cette approche pourrait fournir la co-localisation, mais ne fournirait pas la plupart des bénéfices des pods, comme le partage de ressources, IPC, la garantie d'une fin partagée et une gestion simplifiée.
Durabilité des pods (ou manque de)
Les pods ne doivent pas être considérés comme des entités durables. Ils ne survivent pas à des erreurs d'ordonnancement, à un nœud en échec ou à d'autres expulsions, suite à un manque de ressources ou une mise en maintenance d'un nœud.
En général, les utilisateurs n'ont pas à créer directement des pods. Ils doivent presque toujours utiliser des contrôleurs, même pour des singletons, comme par exemple des Deployments. Les contrôleurs fournissent l'auto-guérison à l'échelle du cluster, ainsi que la réplication et la gestion des déploiements (rollout). Les contrôleurs comme StatefulSet peuvent aussi prendre en charge des pods avec état (stateful).
L'utilisation d'APIs collectives comme principale primitive exposée à l'utilisateur est courante dans les systèmes d'ordonnancement de clusters, comme Borg, Marathon, Aurora, et Tupperware.
Un Pod est exposé en tant que primitive afin de faciliter :
- la connexion du scheduler et du contrôleur
- la possibilité d'opérations au niveau du pod sans besoin de passer par des APIs au niveau du contrôleur
- le découplage du cycle de fin d'un pod de celui d'un contrôleur, comme pour l'amorçage (bootstrapping)
- le découplage des contrôleurs et des services — le contrôleur d'endpoints examine uniquement des pods
- la composition claire des fonctionnalités niveau Kubelet et des fonctionnalités niveau cluster — concrètement, Kubelet est le "contrôleur de pods"
- les applications hautement disponibles, qui attendront que les pods soient remplacés avant leur arrêt et au moins avant leur suppression, comme dans les cas d'éviction programmée ou de pré-chargement d'image.
Arrêt de pods
Les pods représentant des processus s'exécutant sur des nœuds d'un cluster, il est important de permettre à ces processus de se terminer proprement lorsqu'ils ne sont plus nécessaires (plutôt que d'être violemment tués avec un signal KILL et n'avoir aucune chance de libérer ses ressources). Les utilisateurs doivent pouvoir demander une suppression et savoir quand les processus se terminent, mais aussi être capable de s'assurer que la suppression est réellement effective. Lorsqu'un utilisateur demande la suppression d'un pod, le système enregistre le délai de grâce prévu avant que le pod puisse être tué de force, et qu'un signal TERM soit envoyé au processus principal de chaque conteneur. Une fois la période de grâce expirée, le signal KILL est envoyé à ces processus, et le pod est alors supprimé de l'API server. Si Kubelet ou le gestionnaire de conteneurs est redémarré lors de l'attente de l'arrêt des processus, l'arrêt sera réessayé avec la période de grâce complète.
Un exemple de déroulement :
- Un utilisateur envoie une commande pour supprimer un Pod, avec une période de grâce par défaut (30s)
- Le Pod dans l'API server est mis à jour avec le temps au delà duquel le Pod est considéré "mort" ainsi que la période de grâce.
- Le Pod est affiché comme "Terminating" dans les listes des commandes client
- (en même temps que 3) Lorsque Kubelet voit qu'un Pod a été marqué "Terminating", le temps ayant été mis en 2, il commence le processus de suppression du pod.
- Si un des conteneurs du Pod a défini un preStop hook, il est exécuté à l'intérieur du conteneur. Si le
preStop
hook est toujours en cours d'exécution à la fin de la période de grâce, l'étape 2 est invoquée avec une courte (2 secondes) période de grâce supplémentaire une seule fois. Vous devez modifierterminationGracePeriodSeconds
si le hookpreStop
a besoin de plus de temps pour se terminer. - Le signal TERM est envoyé aux conteneurs. Notez que tous les conteneurs du Pod ne recevront pas le signal TERM en même temps et il peut être nécessaire de définir des
preStop
hook si l'ordre d'arrêt est important.
- Si un des conteneurs du Pod a défini un preStop hook, il est exécuté à l'intérieur du conteneur. Si le
- (en même temps que 3) Le Pod est supprimé des listes d'endpoints des services, et n'est plus considéré comme faisant partie des pods en cours d'exécution pour les contrôleurs de réplication. Les Pods s'arrêtant lentement ne peuvent pas continuer à servir du trafic, les load balancers (comme le service proxy) les supprimant de leurs rotations.
- Lorsque la période de grâce expire, les processus s'exécutant toujours dans le Pod sont tués avec SIGKILL.
- Kubelet va supprimer le Pod dans l'API server en indiquant une période de grâce de 0 (suppression immédiate). Le Pod disparaît de l'API et n'est plus visible par le client.
Par défaut, toutes les suppressions ont une période de grâce de 30 secondes. La commande kubectl delete
prend en charge l'option --grace-period=<secondes>
permettant à l'utilisateur de spécifier sa propre valeur. La valeur 0
force la suppression du pod. Avec kubectl version >= 1.5, vous devez spécifier un flag supplémentaire --force
avec --grace-period=0
pour pouvoir forcer la suppression.
Suppression forcée de pods
La suppression forcée d'un pod est définie comme la suppression immédiate d'un pod de l'état du cluster et d'etcd. Lorqu'une suppression forcée est effectuée, l'apiserver n'attend pas la confirmation de kubelet que le pod a été terminé sur le nœud sur lequel il s'exécutait. Il supprime le pod de l'API immédiatement pour qu'un nouveau pod puisse être créé avec le même nom. Sur le nœud, les pods devant se terminer immédiatement se verront donner une courte période de grâce avant d'être tués de force.
Les suppressions forcées peuvent être potentiellement dangereuses pour certains pods et doivent être effectuées avec précaution. Dans le cas de pods d'un StatefulSet, veuillez vous référer à la documentation pour supprimer des Pods d'un StatefulSet.
Mode privilégié pour les conteneurs d'un pod
Depuis Kubernetes v1.1, tout conteneur d'un pod peut activer le mode privilégié, en utilisant le flag privileged
du SecurityContext
de la spec du conteneur. Ceci est utile pour les conteneurs voulant utiliser les capacités de Linux comme manipuler la pile réseau ou accéder aux périphériques. Les processus dans un tel conteneur ont pratiquement les mêmes privilèges que les processus en dehors d'un conteneur. En mode privilégié, il doit être plus facile d'écrire des plugins réseau et volume en tant que pods séparés ne devant pas être compilés dans kubelet.
Si le master exécute Kubernetes v1.1 ou supérieur, et les nœuds exécutent une version antérieure à v1.1, les nouveaux pods privilégiés seront acceptés par l'api-server, mais ne seront pas lancés. Il resteront en état "pending".
Si l'utilisateur appelle kubectl describe pod FooPodName
, l'utilisateur peut voir la raison pour laquelle le pod est en état "pending". La table d'événements dans la sortie de la commande "describe" indiquera :
Error validating pod "FooPodName"."FooPodNamespace" from api, ignoring: spec.containers[0].securityContext.privileged: forbidden '<*>(0xc2089d3248)true'
Si le master exécute une version antérieure à v1.1, les pods privilégiés ne peuvent alors pas être créés. Si l'utilisateur tente de créer un pod ayant un conteneur privilégié, l'utilisateur obtiendra l'erreur suivante :
The Pod "FooPodName" is invalid. spec.containers[0].securityContext.privileged: forbidden '<*>(0xc20b222db0)true'
Objet de l'API
Le Pod est une ressource au plus haut niveau dans l'API REST Kubernetes. Plus de détails sur l'objet de l'API peuvent être trouvés à : Objet de l'API Pod.
Lorsque vous créez un manifest pour un objet Pod, soyez certain que le nom spécifié est un nom de sous-domaine DNS valide.
3 - Cycle de vie d'un Pod
Cette page décrit le cycle de vie d'un Pod.
Phase du Pod
Le champ status
d'un Pod est un objet
PodStatus,
contenant un champ phase
.
La phase d'un Pod est un résumé simple et de haut niveau de l'étape à laquelle le Pod se trouve dans son cycle de vie. La phase n'est pas faite pour être un cumul complet d'observations de l'état du conteneur ou du Pod, ni pour être une machine à état compréhensible.
Le nombre et la signification des valeurs de phase d'un pod sont soigneusement gardés.
Hormis ce qui est documenté ici, rien ne doit être supposé sur des Pods
ayant une valeur de phase
donnée.
Voici les valeurs possibles pour phase
:
Valeur | Description |
---|---|
Pending |
Le Pod a été accepté par Kubernetes, mais une ou plusieurs images de conteneurs n'ont pas encore été créées. Ceci inclut le temps avant d'être affecté ainsi que le temps à télécharger les images à travers le réseau, ce qui peut prendre un certain temps. |
Running |
Le pod a été affecté à un nœud et tous les conteneurs ont été créés. Au moins un conteneur est toujours en cours d'exécution, ou est en train de démarrer ou redémarrer. |
Succeeded |
Tous les conteneurs du pod ont terminé avec succès et ne seront pas redémarrés. |
Failed |
Tous les conteneurs d'un pod ont terminé, et au moins un conteneur a terminé en échec : soit le conteneur a terminé avec un status non zéro, soit il a été arrêté par le système. |
Unknown |
Pour quelque raison l'état du pod ne peut pas être obtenu, en général en cas d'erreur de communication avec l'hôte du Pod. |
Conditions du Pod
Un Pod a un PodStatus, qui contient un tableau de PodConditions à travers lesquelles le Pod est ou non passé. Chaque élément du tableau de PodCondition a six champs possibles :
-
Le champ
lastProbeTime
fournit un timestamp auquel la condition du Pod a été sondée pour la dernière fois. -
Le champ
lastTransitionTime
fournit un timestamp auquel le Pod a changé de statut pour la dernière fois. -
Le champ
message
est un message lisible indiquant les détails de la transition. -
Le champ
reason
est une raison unique, en un seul mot et en CamelCase de la transition vers la dernière condition. -
Le champ
status
est une chaîne de caractères avec les valeurs possibles "True
", "False
", et "Unknown
". -
Le champ
type
est une chaîne de caractères ayant une des valeurs suivantes :PodScheduled
: le Pod a été affecté à un nœud ;Ready
: le Pod est prêt à servir des requêtes et doit être rajouté aux équilibreurs de charge de tous les Services correspondants ;Initialized
: tous les init containers ont démarré correctement ;ContainersReady
: tous les conteneurs du Pod sont prêts.
Sondes du Conteneur
Une Sonde (Probe) est un diagnostic exécuté périodiquement par kubelet sur un Conteneur. Pour exécuter un diagnostic, kubelet appelle un Handler implémenté par le Conteneur. Il existe trois types de handlers :
-
ExecAction: Exécute la commande spécifiée à l'intérieur du Conteneur. Le diagnostic est considéré réussi si la commande se termine avec un code de retour de 0.
-
TCPSocketAction: Exécute un contrôle TCP sur l'adresse IP du Conteneur et sur un port spécifié. Le diagnostic est considéré réussi si le port est ouvert.
-
HTTPGetAction: Exécute une requête HTTP Get sur l'adresse IP du Conteneur et sur un port et un chemin spécifiés. Le diagnostic est considéré réussi si la réponse a un code de retour supérieur ou égal à 200 et inférieur à 400.
Chaque sonde a un résultat parmi ces trois :
- Success: Le Conteneur a réussi le diagnostic.
- Failure: Le Conteneur a échoué au diagnostic.
- Unknown: L'exécution du diagnostic a échoué, et donc aucune action ne peut être prise.
kubelet peut optionnellement exécuter et réagir à trois types de sondes sur des conteneurs en cours d'exécution :
-
livenessProbe
: Indique si le Conteneur est en cours d'exécution. Si la liveness probe échoue, kubelet tue le Conteneur et le Conteneur est soumis à sa politique de redémarrage (restart policy). Si un Conteneur ne fournit pas de liveness probe, l'état par défaut estSuccess
. -
readinessProbe
: Indique si le Conteneur est prêt à servir des requêtes. Si la readiness probe échoue, le contrôleur de points de terminaison (Endpoints) retire l'adresse IP du Pod des points de terminaison de tous les Services correspodant au Pod. L'état par défaut avant le délai initial estFailure
. Si le Conteneur ne fournit pas de readiness probe, l'état par défaut estSuccess
. -
startupProbe
: Indique si l'application à l'intérieur du conteneur a démarré. Toutes les autres probes sont désactivées si une starup probe est fournie, jusqu'à ce qu'elle réponde avec succès. Si la startup probe échoue, le kubelet tue le conteneur, et le conteneur est assujetti à sa politique de redémarrage. Si un conteneur ne fournit pas de startup probe, l'état par défaut estSuccess
.
Quand devez-vous utiliser une liveness probe ?
Si le process de votre Conteneur est capable de crasher de lui-même lorsqu'il
rencontre un problème ou devient inopérant, vous n'avez pas forcément besoin
d'une liveness probe ; kubelet va automatiquement exécuter l'action correcte
en accord avec la politique de redémarrage (restartPolicy
) du Pod.
Si vous désirez que votre Conteneur soit tué et redémarré si une sonde échoue, alors
spécifiez une liveness probe et indiquez une valeur pour restartPolicy
à Always
ou OnFailure.
Quand devez-vous utiliser une readiness probe ?
Kubernetes v1.0 [stable]
Si vous voulez commencer à envoyer du trafic à un Pod seulement lorsqu'une sonde réussit, spécifiez une readiness probe. Dans ce cas, la readiness probe peut être la même que la liveness probe, mais l'existence de la readiness probe dans la spec veut dire que le Pod va démarrer sans recevoir aucun trafic et va commencer à recevoir du trafic après que la sonde réussisse. Si votre Conteneur doit charger une grande quantité de données, des fichiers de configuration ou exécuter des migrations au démarrage, spécifiez une readiness probe.
Si vous désirez que le Conteneur soit capable de se mettre en maintenance tout seul, vous pouvez spécifier une readiness probe qui vérifie un point de terminaison spécifique au readiness et différent de la liveness probe.
Notez que si vous voulez uniquement être capable de dérouter les requêtes lorsque le Pod est supprimé, vous n'avez pas forcément besoin d'une readiness probe; lors de sa suppression, le Pod se met automatiquement dans un état non prêt, que la readiness probe existe ou non. Le Pod reste dans le statut non prêt le temps que les Conteneurs du Pod s'arrêtent.
Quand devez-vous utiliser une startup probe ?
Kubernetes v1.16 [alpha]
Si votre conteneur démarre habituellement en plus de initialDelaySeconds + failureThreshold × periodSeconds
,
vous devriez spécifier une startup probe qui vérifie le même point de terminaison que la liveness probe. La valeur par défaut pour periodSeconds
est 30s.
Vous devriez alors mettre sa valeur failureThreshold
suffisamment haute pour permettre au conteneur de démarrer, sans changer les valeurs par défaut de la liveness probe. Ceci aide à se protéger de deadlocks.
Pour plus d'informations sur la manière de mettre en place une liveness, readiness ou startup probe, voir Configurer des Liveness, Readiness et Startup Probes.
Statut d'un Pod et d'un Conteneur
Pour des informations détaillées sur le statut d'un Pod et d'un Conteneur, voir PodStatus et ContainerStatus. Notez que l'information rapportée comme statut d'un Pod dépend du ContainerState actuel.
États d'un Conteneur
Une fois que le Pod est assigné à un nœud par le scheduler, kubelet commence
à créer les conteneurs en utilisant le runtime de conteneurs. Il existe trois états possibles
pour les conteneurs : en attente (Waiting), en cours d'exécution (Running) et terminé (Terminated). Pour vérifier l'état d'un conteneur, vous pouvez utiliser kubectl describe pod [POD_NAME]
. L'état est affiché pour chaque conteneur du Pod.
-
Waiting
: état du conteneur par défaut. Si le conteneur n'est pas dans un état Running ou Terminated, il est dans l'état Waiting. Un conteneur dans l'état Waiting exécute les opérations nécessaires, comme télécharger les images, appliquer des Secrets, etc. À côté de cet état, un message et une raison sur l'état sont affichés pour vous fournir plus d'informations.... State: Waiting Reason: ErrImagePull ...
-
Running
: Indique que le conteneur s'exécute sans problème. Le hookpostStart
(s'il existe) est exécuté avant que le conteneur entre dans l'état Running. Cet état affiche aussi le moment auquel le conteneur est entré dans l'état Running.... State: Running Started: Wed, 30 Jan 2019 16:46:38 +0530 ...
-
Terminated
: Indique que le conteneur a terminé son exécution et s'est arrêté. Un conteneur entre dans cet état lorsqu'il s'est exécuté avec succès ou lorsqu'il a échoué pour une raison quelconque. De plus, une raison et un code de retour sont affichés, ainsi que les moments de démarrage et d'arrêt du conteneur. Avant qu'un conteneur entre dans l'état Terminated, le hookpreStop
est exécuté (s'il existe).... State: Terminated Reason: Completed Exit Code: 0 Started: Wed, 30 Jan 2019 11:45:26 +0530 Finished: Wed, 30 Jan 2019 11:45:26 +0530 ...
Pod readiness
Kubernetes v1.14 [stable]
Votre application peut injecter des données dans PodStatus
.
Pod readiness. Pour utiliser cette fonctionnalité, remplissez readinessGates
dans le PodSpec avec
une liste de conditions supplémentaires que le kubelet évalue pour la disponibilité du Pod.
Les Readiness gates sont déterminées par l'état courant des champs status.condition
du Pod.
Si Kubernetes ne peut pas trouver une telle condition dans le champs status.conditions
d'un Pod, the statut de la condition
est mise par défaut à "False
".
Voici un exemple :
kind: Pod
...
spec:
readinessGates:
- conditionType: "www.example.com/feature-1"
status:
conditions:
- type: Ready # une PodCondition intégrée
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
- type: "www.example.com/feature-1" # une PodCondition supplémentaire
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
containerStatuses:
- containerID: docker://abcd...
ready: true
...
Les conditions du Pod que vous ajoutez doivent avoir des noms qui sont conformes au format des étiquettes de Kubernetes.
Statut de la disponibilité d'un Pod
La commande kubectl patch
ne peut pas patcher le statut d'un objet.
Pour renseigner ces status.conditions
pour le pod, les applications et
operators doivent utiliser l'action PATCH
.
Vous pouvez utiliser une bibliothèque client Kubernetes pour
écrire du code qui renseigne les conditions particulières pour la disponibilité dun Pod.
Pour un Pod utilisant des conditions particulières, ce Pod est considéré prêt seulement lorsque les deux déclarations ci-dessous sont vraies :
- Tous les conteneurs du Pod sont prêts.
- Toutes les conditions spécifiées dans
ReadinessGates
sontTrue
.
Lorsque les conteneurs d'un Pod sont prêts mais qu'au moins une condition particulière
est manquante ou False
, le kubelet renseigne la condition du Pod à ContainersReady
.
Politique de redémarrage
La structure PodSpec a un champ restartPolicy
avec comme valeur possible
Always, OnFailure et Never. La valeur par défaut est Always.
restartPolicy
s'applique à tous les Conteneurs du Pod. restartPolicy
s'applique
seulement aux redémarrages des Conteneurs par kubelet sur le même nœud. Des conteneurs
terminés qui sont redémarrés par kubelet sont redémarrés avec un délai exponentiel
(10s, 20s, 40s ...) plafonné à cinq minutes, qui est réinitialisé après dix minutes
d'exécution normale. Comme discuté dans le
document sur les Pods,
une fois attaché à un nœud, un Pod ne sera jamais rattaché à un autre nœud.
Durée de vie d'un Pod
En général, les Pods restent jusqu'à ce qu'un humain ou un process de contrôleur les supprime explicitement.
Le plan de contrôle nettoie les Pods terminés (avec une phase à Succeeded
ou
Failed
), lorsque le nombre de Pods excède le seuil configuré
(determiné par terminated-pod-gc-threshold
dans le kube-controller-manager).
Ceci empêche une fuite de ressources lorsque les Pods sont créés et supprimés au fil du temps.
Il y a différents types de ressources pour créer des Pods :
-
Utilisez un Déploiement, ReplicaSet ou StatefulSet pour les Pods qui ne sont pas censés terminer, par exemple des serveurs web.
-
Utilisez un Job pour les Pods qui sont censés se terminer une fois leur tâche accomplie. Les Jobs sont appropriés seulement pour des Pods ayant
restartPolicy
égal à OnFailure ou Never. -
Utilisez un DaemonSet pour les Pods qui doivent s'exécuter sur chaque noeud éligible.
Toutes les ressources de charges de travail contiennent une PodSpec. Il est recommandé de créer la ressource de charges de travail appropriée et laisser le contrôleur de la ressource créer les Pods pour vous, plutôt que de créer directement les Pods vous-même.
Si un nœud meurt ou est déconnecté du reste du cluster, Kubernetes applique
une politique pour mettre la phase
de tous les Pods du nœud perdu à Failed.
Exemples
Exemple avancé de liveness probe
Les Liveness probes sont exécutées par kubelet, toutes les requêtes sont donc faites dans l'espace réseau de kubelet.
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- args:
- /server
image: k8s.gcr.io/liveness
livenessProbe:
httpGet:
# lorsque "host" n'est pas défini, "PodIP" sera utilisé
# host: my-host
# lorsque "scheme" n'est pas défini, "HTTP" sera utilisé. "HTTP" et "HTTPS" sont les seules valeurs possibles
# scheme: HTTPS
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
name: liveness
Exemples d'états
-
Un Pod est en cours d'exécution et a un Conteneur. Le conteneur se termine avec succès.
- Écriture d'un événement de complétion.
- Si
restartPolicy
est :- Always : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - OnFailure : la
phase
du Pod passe à Succeeded. - Never : la
phase
du Pod passe à Succeeded.
- Always : Redémarrage du Conteneur ; la
-
Un Pod est en cours d'exécution et a un Conteneur. Le conteneur se termine en erreur.
- Écriture d'un événement d'échec.
- Si
restartPolicy
est :- Always : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - OnFailure : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - Never : la
phase
du Pod passe à Failed.
- Always : Redémarrage du Conteneur ; la
-
Un Pod est en cours d'exécution et a deux Conteneurs. Le conteneur 1 termine en erreur.
- Écriture d'un événement d'échec.
- Si
restartPolicy
est :- Always : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - OnFailure : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - Never : Le Conteneur n'est pas redémarré ; la
phase
du Pod reste à Running.
- Always : Redémarrage du Conteneur ; la
- Si Container 1 est arrêté, et Conteneur 2 se termine :
- Écriture d'un événement d'échec.
- Si
restartPolicy
est :- Always : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - OnFailure : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - Never : la
phase
du Pod passe à Failed.
- Always : Redémarrage du Conteneur ; la
-
Un Pod est en cours d'exécution et a un Conteneur. Le Conteneur n'a plus assez de mémoire.
- Le Conteneur se termine en erreur.
- Écriture d'un événement OOM.
- Si
restartPolicy
est :- Always : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - OnFailure : Redémarrage du Conteneur ; la
phase
du Pod reste à Running. - Never : Écriture d'un événement d'erreur ; la
phase
du Pod passe à Failed.
- Always : Redémarrage du Conteneur ; la
-
Le Pod est en cours d'exécution, et un disque meurt.
- Tous les conteneurs sont tués.
- Écriture d'un événement approprié.
- La
phase
du Pod devient Failed. - Si le Pod s'exécute sous un contrôleur, le Pod est recréé ailleurs.
-
Le Pod est en cours d'exécution et son nœud est segmenté.
- Le contrôleur de Nœud attend un certain temps.
- Le contrôleur de Nœud passe la
phase
du Pod à Failed. - Si le Pod s'exécute sous un contrôleur, le Pod est recréé ailleurs.
A suivre
-
Apprenez par la pratique attacher des handlers à des événements de cycle de vie d'un conteneur.
-
Apprenez par la pratique configurer des liveness, readiness et startup probes.
-
En apprendre plus sur les hooks de cycle de vie d'un Conteneur.
4 - Contraintes de propagation de topologie pour les Pods
Kubernetes v1.18 [beta]
Vous pouvez utiliser des contraintes de propagation de topologie pour contrôler comment les Pods sont propagés à travers votre cluster parmi les domaines de défaillance comme les régions, zones, noeuds et autres domaines de topologie définis par l'utilisateur. Ceci peut aider à mettre en place de la haute disponibilité et à utiliser efficacement les ressources.
Conditions préalables
Autoriser la Feature Gate
La feature gate EvenPodsSpread
doit être autorisée pour
l'API Server et le
scheduler.
Labels de noeuds
Les contraintes de propagation de topologie reposent sur les labels de noeuds pour identifier le ou les domaines de topologie dans lesquels se trouve chacun des noeuds. Par exemple, un noeud pourrait avoir les labels: node=node1,zone=us-east-1a,region=us-east-1
Supposons que vous ayez un cluster de 4 noeuds ayant les labels suivants:
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready <none> 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready <none> 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready <none> 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready <none> 2m43s v1.16.0 node=node4,zone=zoneB
Une vue logique du cluster est celle-ci :
+---------------+---------------+
| zoneA | zoneB |
+-------+-------+-------+-------+
| node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+
Plutôt que d'appliquer des labels manuellement, vous pouvez aussi réutiliser les labels réputés qui sont créés et renseignés automatiquement dans la plupart des clusters.
Contraintes de propagation pour les Pods
API
Le champ pod.spec.topologySpreadConstraints
est introduit dans 1.16 comme suit :
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer>
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
Vous pouvez définir une ou plusieurs topologySpreadConstraint
pour indiquer au kube-scheduler comment placer chaque nouveau Pod par rapport aux Pods déjà existants dans votre cluster. Les champs sont :
- maxSkew décrit le degré avec lequel les Pods peuvent être inégalement distribués. C'est la différence maximale permise entre le nombre de Pods correspondants entre deux quelconques domaines de topologie d'un type donné. Il doit être supérieur à zéro.
- topologyKey est la clé des labels de noeuds. Si deux noeuds sont étiquettés avec cette clé et ont des valeurs égales pour ce label, le scheduler considère les deux noeuds dans la même topologie. Le scheduler essaie de placer un nombre équilibré de Pods dans chaque domaine de topologie.
- whenUnsatisfiable indique comment traiter un Pod qui ne satisfait pas les contraintes de propagation :
DoNotSchedule
(défaut) indique au scheduler de ne pas le programmer.ScheduleAnyway
indique au scheduler de le programmer, tout en priorisant les noeuds minimisant le biais (skew).
- labelSelector est utilisé pour touver les Pods correspondants. Les Pods correspondants à ce sélecteur de labels sont comptés pour déterminer le nombre de Pods dans leurs domaines de topologie correspodants. Voir Sélecteurs de labels pour plus de détails.
Vous pouvez en savoir plus sur ces champ en exécutant kubectl explain Pod.spec.topologySpreadConstraints
.
Exemple : Une TopologySpreadConstraint
Supposons que vous ayez un cluster de 4 noeuds où 3 Pods étiquettés foo:bar
sont placés sur node1, node2 et node3 respectivement (P
représente un Pod) :
+---------------+---------------+
| zoneA | zoneB |
+-------+-------+-------+-------+
| node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+
| P | P | P | |
+-------+-------+-------+-------+
Si nous voulons qu'un nouveau Pod soit uniformément réparti avec les Pods existants à travers les zones, la spec peut être :
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: k8s.gcr.io/pause:3.1
topologyKey: zone
implique que la distribution uniforme sera uniquement appliquée pour les noeuds ayant le label "zone:<any value>" présent. whenUnsatisfiable: DoNotSchedule
indique au scheduler de laisser le Pod dans l'état Pending si le Pod entrant ne peut pas satisfaire la contrainte.
Si le scheduler plaçait ce Pod entrant dans "zoneA", la distribution des Pods deviendrait [3, 1], et le biais serait de 2 (3 - 1) - ce qui va à l'encontre de maxSkew: 1
. Dans cet exemple, le Pod entrant peut uniquement être placé dans "zoneB":
+---------------+---------------+ +---------------+---------------+
| zoneA | zoneB | | zoneA | zoneB |
+-------+-------+-------+-------+ +-------+-------+-------+-------+
| node1 | node2 | node3 | node4 | OR | node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+ +-------+-------+-------+-------+
| P | P | P | P | | P | P | P P | |
+-------+-------+-------+-------+ +-------+-------+-------+-------+
Vous pouvez ajuster la spec du Pod pour pour répondre à divers types d'exigences :
- Changez
maxSkew
pour une valeur plus grande comme "2" pour que le Pod entrant puisse aussi être placé dans la "zoneA". - Changez
topologyKey
pour "node" pour distribuer les Pods uniformément à travers les noeuds et non plus les zones. Dans l'exemple ci-dessus, simaxSkew
reste à "1", le Pod entrant peut être uniquement placé dans "node4". - Changez
whenUnsatisfiable: DoNotSchedule
enwhenUnsatisfiable: ScheduleAnyway
pour s'assurer que le Pod est toujours programmable (en supposant que les autres APIs de scheduling soient satisfaites). Cependant, il sera de préférence placé dans la topologie de domaine ayant le moins de Pods correspondants. (Prenez note que cette préférence est normalisée conjointement avec d'autres priorités de scheduling interne comme le ratio d'usage de ressources, etc.)
Example: Plusieurs TopologySpreadConstraints
Cela s'appuie sur l'exemple précédent. Supposons que vous ayez un cluster de 4 noeuds où 3 Pods étiquetés foo:bar
sont placés sur node1, node2 et node3 respectivement (P
représente un Pod):
+---------------+---------------+
| zoneA | zoneB |
+-------+-------+-------+-------+
| node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+
| P | P | P | |
+-------+-------+-------+-------+
Vous pouvez utiliser 2 TopologySpreadConstraints pour contrôler la répartition des Pods aussi bien dans les zones que dans les noeuds :
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: k8s.gcr.io/pause:3.1
Dans ce cas, pour satisfaire la première contrainte, le Pod entrant peut uniquement être placé dans "zoneB" ; alors que pour satisfaire la seconde contrainte, le Pod entrant peut uniquement être placé dans "node4". Le résultat étant l'intersection des résultats des 2 contraintes, l'unique option possible est de placer le Pod entrant dans "node4".
Plusieurs contraintes peuvent entraîner des conflits. Supposons que vous ayez un cluster de 3 noeuds couvrant 2 zones :
+---------------+-------+
| zoneA | zoneB |
+-------+-------+-------+
| node1 | node2 | node3 |
+-------+-------+-------+
| P P | P | P P |
+-------+-------+-------+
Si vous appliquez "two-constraints.yaml" à ce cluster, vous noterez que "mypod" reste dans l'état Pending
. Cela parce que : pour satisfaire la première contrainte, "mypod" peut uniquement être placé dans "zoneB"; alors que pour satisfaire la seconde contrainte, "mypod" peut uniquement être placé sur "node2". Ainsi, le résultat de l'intersection entre "zoneB" et "node2" ne retourne rien.
Pour surmonter cette situation, vous pouvez soit augmenter maxSkew
, soit modifier une des contraintes pour qu'elle utilise whenUnsatisfiable: ScheduleAnyway
.
Conventions
Il existe quelques conventions implicites qu'il est intéressant de noter ici :
-
Seuls le Pods du même espace de noms que le Pod entrant peuvent être des candidats pour la correspondance.
-
Les noeuds sans label
topologySpreadConstraints[*].topologyKey
seront ignorés. Cela induit que :- les Pods localisés sur ces noeuds n'impactent pas le calcul de
maxSkew
- dans l'exemple ci-dessus, supposons que "node1" n'a pas de label "zone", alors les 2 Pods ne seront pas comptés, et le Pod entrant sera placé dans "zoneA". - le Pod entrant n'a aucune chance d'être programmé sur ce type de noeuds - dans l'exemple ci-dessus, supposons qu'un "node5" portant un label
{zone-typo: zoneC}
joigne le cluster ; il sera ignoré, en raison de l'absence de label "zone".
- les Pods localisés sur ces noeuds n'impactent pas le calcul de
-
Faites attention à ce qui arrive lorsque le
topologySpreadConstraints[*].labelSelector
du Pod entrant ne correspond pas à ses propres labels. Dans l'exemple ci-dessus, si nous supprimons les labels du Pod entrant, il sera toujours placé dans "zoneB" car les contraintes sont toujours satisfaites. Cependant, après le placement, le degré de déséquilibre du cluster reste inchangé - zoneA contient toujours 2 Pods ayant le label {foo:bar}, et zoneB contient 1 Pod cayant le label {foo:bar}. Si ce n'est pas ce que vous attendez, nous recommandons quetopologySpreadConstraints[*].labelSelector
du workload corresponde à ses propres labels. -
Si le Pod entrant a défini
spec.nodeSelector
ouspec.affinity.nodeAffinity
, les noeuds non correspondants seront ignorés.Supposons que vous ayez un cluster de 5 noeuds allant de zoneA à zoneC :
+---------------+---------------+-------+ | zoneA | zoneB | zoneC | +-------+-------+-------+-------+-------+ | node1 | node2 | node3 | node4 | node5 | +-------+-------+-------+-------+-------+ | P | P | P | | | +-------+-------+-------+-------+-------+
et vous savez que "zoneC" doit être exclue. Dans ce cas, vous pouvez écrire le yaml ci-dessous, pour que "mypod" soit placé dans "zoneB" plutôt que dans "zoneC".
spec.nodeSelector
est pris en compte de la même manière.kind: Pod apiVersion: v1 metadata: name: mypod labels: foo: bar spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: foo: bar affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: zone operator: NotIn values: - zoneC containers: - name: pause image: k8s.gcr.io/pause:3.1
Contraintes par défaut au niveau du cluster
Kubernetes v1.18 [alpha]
Il est possible de définir des contraintes de propagation de topologie par défaut pour un cluster. Les contraintes de propagation de topologie sont appliquées à un Pod si et seulement si :
- Il ne définit aucune contrainte dans son
.spec.topologySpreadConstraints
. - Il appartient à un service, replication controller, replica set ou stateful set.
Les contraintes par défaut peuvent être définies comme arguments du plugin PodTopologySpread
dans un profil de scheduling.
Les contraintes sont spécifiées avec la même API ci-dessus, à l'exception que
labelSelector
doit être vide. Les sélecteurs sont calculés à partir des services,
replication controllers, replica sets ou stateful sets auxquels le Pod appartient.
Un exemple de configuration pourrait ressembler à :
apiVersion: kubescheduler.config.k8s.io/v1alpha2
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
- pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
DefaultPodTopologySpread
.
Il est recommandé de désactiver ce plugin dans le profil de scheduling lorsque vous utilisez des contraintes
par défaut pour PodTopologySpread
.
Comparaison avec PodAffinity/PodAntiAffinity
Dans Kubernetes, les directives relatives aux "Affinités" contrôlent comment les Pods sont programmés - plus regroupés ou plus dispersés.
- Pour
PodAffinity
, vous pouvez essayer de regrouper un certain nombre de Pods dans des domaines de topologie qualifiés, - Pour
PodAntiAffinity
, seulement un Pod peut être programmé dans un domaine de topologie unique.
La fonctionnalité "EvenPodsSpread" fournit des options flexibles pour distribuer des Pods uniformément sur différents domaines de topologie - pour mettre en place de la haute disponibilité ou réduire les coûts. Cela peut aussi aider au rolling update des charges de travail et à la mise à l'échelle de réplicas. Voir Motivations pour plus de détails.
Limitations connues
En version 1.18, pour laquelle cette fonctionnalité est en Beta, il y a quelques limitations connues :
- Réduire un Déploiement peut résulter en une distrubution désiquilibrée des Pods.
- Les Pods correspondants sur des noeuds taintés sont respectés. Voir Issue 80921
5 - Init Containers
Cette page fournit une vue d'ensemble des conteneurs d'initialisation (init containers) : des conteneurs spécialisés qui s'exécutent avant les conteneurs d'application dans un Pod. Les init containers peuvent contenir des utilitaires ou des scripts d'installation qui ne sont pas présents dans une image d'application.
Vous pouvez spécifier des init containers dans la spécification du Pod à côté du tableau containers
(qui décrit les conteneurs d'application)
Comprendre les init containers
Un Pod peut avoir plusieurs conteneurs exécutant des applications mais peut aussi avoir un ou plusieurs init containers, qui sont exécutés avant que les conteneurs d'application ne démarrent.
Les init containers se comportent comme les conteneurs réguliers, avec quelques différences :
- Les init containers s'exécutent toujours jusqu'à la complétion.
- Chaque init container doit se terminer avec succès avant que le prochain ne démarre.
Si le init container d'un Pod échoue, Kubernetes redémarre le Pod à répétition jusqu'à ce que le init container se termine avec succès.
Cependant, si le Pod a une restartPolicy
à "Never", Kubernetes ne redémarre pas le Pod.
Afin de spécifier un init container pour un Pod, il faut ajouter le champ initContainers
dans la spécification du Pod, comme un
tableau d'objets de type Container, au même niveau que le tableau d'applications containers
.
Le statut des init containers est retourné dans le champ .status.initContainerStatuses
comme un tableau des statuts du conteneur (comparable au champ .status.containerStatuses
).
Différences avec les conteneurs réguliers
Les init containers supportent tous les champs et fonctionnalités des conteneurs d'application incluant les limites de ressources, les volumes et les paramètres de sécurité. Cependant, les demandes de ressources pour un init container sont gérées différemment des limites de ressources, tel que documenté dans Ressources.
De plus, les init containers ne supportent pas les readiness probes parce que ces conteneurs s'exécutent jusqu'au bout avant que le Pod soit prêt.
Si l'on spécifie plusieurs init containers pour un Pod, Kubelet exécute chaque init container de manière séquentielle. Chaque init container doit se terminer avec succès avant que le prochain ne puisse s'exécuter. Lorsque tous les init containers se sont exécutés jusqu'au bout, Kubelet initialise les conteneurs d'application pour le Pod et les exécute comme d'habitude.
Utiliser les init containers
Puisque les init containers ont des images séparées des conteneurs d'application, ils apportent certains avantages pour du code de mise en route :
- Les init containers peuvent contenir des utilitaires ou du code de configuration personnalisé
qui ne sont pas présents dans une image d'application.
Par exemple, il n'y a pas besoin de faire hériter une image d'une autre (
FROM
) seulement pour utiliser un outil commesed
,awk
,python
, oudig
pendant l'installation. - Les init containers peuvent exécuter en toute sécurité des utilitaires qui rendraient moins sécurisée une image de conteneur d'application.
- Les rôles "builder" et "deployer" d'une image d'application peuvent travailler indépendamment sans qu'il n'y ait besoin de créer conjointement une seule image d'application.
- Les init containers peuvent s'exécuter avec une vue du système de fichiers différente de celle des conteneurs d'application dans le même Pod. Par conséquent, on peut leur donner accès aux Secrets, auxquels les conteneurs d'application n'ont pas accès.
- Puisque les init containers s'exécutent jusqu'à la complétion avant qu'un conteneur d'application ne démarre, les init containers offrent un mécanisme pour bloquer ou retarder le démarrage d'un conteneur d'application tant qu'un ensemble de préconditions n'est pas respecté. Une fois que les préconditions sont respectées, tous les conteneurs d'application dans un Pod peuvent démarrer en parallèle.
Exemples
Voici plusieurs idées pour utiliser les init containers :
-
Attendre qu'un Service soit créé, en utilisant une commande shell d'une ligne telle que :
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
-
Enregistrer ce Pod à un serveur distant depuis l'API downward avec une commande telle que :
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
-
Attendre un certain temps avant de démarrer le conteneur d'application avec une commande telle que :
sleep 60
-
Cloner un dépôt Git dans un Volume
-
Placer des valeurs dans un fichier de configuration et exécuter un outil de templating pour générer dynamiquement un fichier de configuration pour le conteneur d'application principal. Par exemple, placer la valeur
POD_IP
dans une configuration et générer le fichier de configuration de l'application principale en utilisant Jinja.
Les init containers en utilisation
Cet exemple définit un simple Pod possédant deux init containers.
Le premier attend myservice
et le second attend mydb
. Une fois que les deux
init containers terminent leur exécution, le Pod exécute le conteneur d'application décrit dans sa section spec
.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo "L''app s''exécute!" && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo en attente de myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo en attente de mydb; sleep 2; done"]
Les fichiers YAML suivants résument les services mydb
et myservice
:
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
Vous pouvez démarrer ce Pod en exécutant :
kubectl apply -f myapp.yaml
pod/myapp-pod created
Et vérifier son statut avec :
kubectl get -f myapp.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
ou pour plus de détails :
kubectl describe -f myapp.yaml
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634
Pour voir les logs des init containers dans ce Pod, exécuter :
kubectl logs myapp-pod -c init-myservice # Inspecter le premier init container
kubectl logs myapp-pod -c init-mydb # Inspecter le second init container
À ce stade, ces init containers attendent de découvrir les services nommés
mydb
et myservice
.
Voici une configuration que vous pouvez utiliser pour faire apparaître ces Services :
---
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
Pour créer les services mydb
et myservice
:
kubectl apply -f services.yaml
service/myservice created
service/mydb created
Vous verrez ensuite que ces init containers se terminent et que le Pod myapp-pod
évolue vers l'état "Running" (en exécution) :
kubectl get -f myapp.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
Cet exemple simple devrait suffire à vous inspirer pour créer vos propres init containers. A suivre contient un lien vers un exemple plus détaillé.
Comportement détaillé
Pendant le démarrage d'un Pod, chaque init container démarre en ordre, après que le réseau
et les volumes ont été initialisés. Chaque conteneur doit se terminer avec succès avant que le prochain
ne démarre. Si un conteneur n'arrive pas à démarrer à cause d'un problème d'exécution ou
se termine avec un échec, il est redémarré selon la restartPolicy
du Pod.
Toutefois, si la restartPolicy
du Pod est configurée à "Always", les init containers utilisent la restartPolicy
"OnFailure".
Un Pod ne peut pas être Ready
tant que tous les init containers ne se sont pas exécutés avec succès.
Les ports d'un init container ne sont pas agrégés sous un Service. Un Pod qui s'initialise
est dans l'état Pending
mais devrait avoir une condition Initialized
configurée à "true".
Si le Pod redémarre ou est redémarré, tous les init containers doivent s'exécuter à nouveau.
Les changements aux spec d'un init containers sont limités au champ image du conteneur. Changer le champ image d'un init container équivaut à redémarrer le Pod.
Puisque les init containers peuvent être redémarrés, réessayés ou ré-exécutés,
leur code doit être idempotent. En particulier, le code qui écrit dans des fichiers sur EmptyDirs
devrait être préparé à la possibilité qu'un fichier de sortie existe déjà.
Les init containers ont tous les champs d'un conteneur d'application.
Cependant, Kubernetes interdit l'utilisation de readinessProbe
parce que les init containers
ne peuvent pas définir une "readiness" distincte de la complétion. Ceci est appliqué lors de la validation.
L'utilisation de activeDeadlineSeconds
sur le Pod et livenessProbe
sur le conteneur
permet d'empêcher les init containers d'échouer tout le temps.
La deadline active inclut les init containers.
Le nom de chaque application et init container dans un Pod doit être unique; une erreur de validation est générée pour tout conteneur partageant un nom avec un autre.
Ressources
Étant donné l'ordonnancement et l'exécution des init containers, les règles suivantes s'appliquent pour l'utilisation des ressources :
- La plus haute requête ou limite particulière de ressource définie pour tous les init containers est la limite/requête d'initialisation effective
- La limite/requête effective d'un Pod pour une ressource est la plus haute parmis :
- la somme de toutes les requêtes/limites des conteneurs d'application pour une ressource
- la limite/requête d'initialisation effective pour une ressource
- Le Scheduling est effectué sur la base des requêtes/limites effectives, ce qui signifie que les init containers peuvent réserver des ressources pour l'initialisation qui ne sont pas utilisées durant le cycle de vie du Pod.
- La QoS (qualité de service) tierce de la QoS tierce effective d'un Pod est la QoS tierce aussi bien pour les init containers que pour les conteneurs d'application.
Les quotas et limites sont appliqués sur la base de la requête/limite effective d'un Pod.
Les groupes de contrôle au niveau du Pod (cgroups) sont basés sur la requête/limite effective de Pod, la même que celle du scheduler.
Raisons du redémarrage d'un Pod
Un Pod peut redémarrer, ce qui cause la ré-exécution des init containers, pour les raisons suivantes :
- Un utilisateur met à jour les spécifications du Pod, ce qui cause le changement de l'image de l'init container. Tout changement à l'image du init container redémarre le Pod. Les changements au conteneur d'application entraînent seulement le redémarrage du conteneur d'application.
- Le conteneur d'infrastructure Pod est redémarré. Ceci est peu commun et serait effectué par une personne ayant un accès root aux nœuds.
- Tous les conteneurs dans un Pod sont terminés tandis que
restartPolicy
est configurée à "Always", ce qui force le redémarrage, et l'enregistrement de complétion du init container a été perdu à cause d'une opération de garbage collection (récupération de mémoire).
A suivre
- Lire à propos de la création d'un Pod ayant un init container
- Apprendre à debugger les init containers