Workloads

Comprendre les Pods, le plus petit objet déployable sur Kubernetes, et les abstractions de haut niveaux vous permettant de les lancer.

Un workload (charge de travail) est une application fonctionnant sur Kubernetes. Que votre workload soit un composant unique ou un agrégat de composants, sur Kubernetes celui-ci fonctionnera dans une série de pods. Dans Kubernetes, un Pod represente un ensemble de conteneur (containers) en fonctionnement sur votre cluster.

Les pods Kubernetes ont un cycle de vie définit (defined lifecycle). Par exemple, quand un pod est en fonction sur votre cluster et qu’une panne critique survient sur le noeud (node) où se situe ce pod, tous les pods du noeud seront en échec. Kubernetes traite ce niveau d’échec comme un état final : Vous devez créer un nouveau Pod pour retrouver l’état initial même si le noeud redevient sain.

Cependant, pour vous simplifier la vie, vous n’avez pas a gérer chaque Pod directement. Vous pouvez utiliser une ressource workload qui gère votre groupe de pods à votre place. Ces ressources configurent des controleurs (controllers) qui s’assurent que le bon nombre et le bon type de pod soit en fonction pour égaler l’état que vous avez spécifié.

Kubernetes fournit plusieurs ressources workload pré-faites :

  • Deployment et ReplicaSet (qui remplacent l’ancienne ressource ReplicationController)). Le Deployment (déploiement) est une bonne approche pour manager une application stateless sur votre cluster, tous les Pods d’un Deployment sont interchangeables et peuvent être remplacés si besoin.
  • Le StatefulSet vous permet de lancer un ou plusieurs Pods en relation qui garde plus ou moins la trace de leurs état. Par exemple si votre workload enregistre des données de façon persistente, vous pouvez lancer un StatefulSet qui fera le lien entre les Pods et un volume persistent (PersistentVolume). Votre code, présent dans les Pods du StatefulSet, peut répliquer des données dans les autres Pods qui sont dans le même StatefulSet, pour améliorer la résilience global.
  • Le DaemonSet permet de définir les Pods qui effectuent des actions sur le noeud local. Ceux-ci peuvent être fondamental aux opérations de votre cluster, comme un outil d’aide réseau, ou peuvent faire part d’un module complémentaire (add-on). Pour chaque nouveau noeud ajouté au cluster, le controle plane organise l'ajout d'un Pod pour ce DaemonSet sur le nouveau noeud.
  • Les Job et CronJob sont des taches lancées jusqu’à accomplissement puis s’arrêtent. Les Jobs réprésentent une tâche ponctuelle, les CronJob sont des tâches récurrentes planifiés.

Dans l’écosystème étendu de Kubernetes, vous pouvez trouver des ressources workload de fournisseurs tiers qui offrent des fonctionnalités supplémentaires. L’utilisation d’un CustomResourceDefinition permet d’ajouter une ressource workload d’un fournisseur tiers si vous souhaitez rajouter une fonctionnalité ou un comportement spécifique qui ne fait pas partie du noyau de Kubernetes. Par exemple, si vous voulez lancer un groupe de Pods pour votre application mais que vous devez arrêter leurs fonctionnement tant qu’ils ne sont pas tous disponibles, alors vous pouvez implémenter ou installer une extension qui permet cette fonctionnalité.

A suivre

Vous pouvez continuer la lecture des ressources, vous pouvez aussi apprendre à connaitre les taches qui leurs sont liées :

Pour en apprendre plus sur les méchanismes de Kubernetes, de séparation du code et de la configuration, allez voir Configuration.

Il y a deux concepts supportés qui fournissent un contexte sur le sujet : comment Kubernetes gère les pods pour les applications :

Une fois que votre application est lancée, vous souhaitez peut etre la rendre disponible sur internet comme un Service ou comme une application web uniquement en utilsant un Ingress.

1 - Pods

1.1 - Aperçu du Pod

Pod Concept Kubernetes

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 :

example pod diagram

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

1.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.

pod diagram

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) ?

  1. 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.
  2. 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.
  3. 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.
  4. 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 :

  1. Un utilisateur envoie une commande pour supprimer un Pod, avec une période de grâce par défaut (30s)
  2. 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.
  3. Le Pod est affiché comme "Terminating" dans les listes des commandes client
  4. (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.
    1. 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 modifier terminationGracePeriodSeconds si le hook preStop a besoin de plus de temps pour se terminer.
    2. 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.
  5. (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.
  6. Lorsque la période de grâce expire, les processus s'exécutant toujours dans le Pod sont tués avec SIGKILL.
  7. 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.

1.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 est Success.

  • 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 est Failure. Si le Conteneur ne fournit pas de readiness probe, l'état par défaut est Success.

  • 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 est Success.

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 ?

FEATURE STATE: 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 ?

FEATURE STATE: 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 hook postStart (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 hook preStop 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

FEATURE STATE: 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 sont True.

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.
  • 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.
  • 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.
    • 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.
  • 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.
  • 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

1.4 - Contraintes de propagation de topologie pour les Pods

FEATURE STATE: 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>
      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, si maxSkew reste à "1", le Pod entrant peut être uniquement placé dans "node4".
  • Changez whenUnsatisfiable: DoNotSchedule en whenUnsatisfiable: 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 :

    1. 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".
    2. 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".
  • 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 que topologySpreadConstraints[*].labelSelector du workload corresponde à ses propres labels.

  • Si le Pod entrant a défini spec.nodeSelector ou spec.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

FEATURE STATE: 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

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

1.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 comme sed, awk, python, ou dig 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: 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=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

2 - Contrôleurs

2.1 - ReplicaSet

Un ReplicaSet (ensemble de réplicas en français) a pour but de maintenir un ensemble stable de Pods à un moment donné. Cet objet est souvent utilisé pour garantir la disponibilité d'un certain nombre identique de Pods.

Comment un ReplicaSet fonctionne

Un ReplicaSet est défini avec des champs, incluant un selecteur qui spécifie comment identifier les Pods qu'il peut posséder, un nombre de replicas indiquant le nombre de Pods qu'il doit maintenir et un modèle de Pod spécifiant les données que les nouveaux Pods que le replicatSet va créer jusqu'au nombre de replicas demandé.

Un ReplicaSet va atteindre son objectif en créant et supprimant des Pods pour atteindre le nombre de réplicas désirés. Quand un ReplicaSet a besoin de créer de nouveaux Pods, il utilise alors son Pod template.

Le lien d'un ReplicaSet à ses Pods est fait par le champ metadata.ownerReferences, qui spécifie la ressource de l'objet par lequel il est détenu. Tous les Pods acquis par un ReplicaSet ont leurs propres informations d'identification de leur Replicaset, avec leur propre champ ownerReferences. C'est par ce lien que le ReplicaSet connait l'état des Pods qu'il maintient et agit en fonction de ces derniers.

Un ReplicaSet identifie des nouveaux Pods à acquérir en utilisant son selecteur. Si il y a un Pod qui n'a pas de OwnerReference ou que OwnerReference n'est pas un controller et qu'il correspond à un sélecteur de ReplicaSet, il va immédiatement être acquis par ce ReplicaSet.

Quand utiliser un ReplicaSet ?

Un ReplicaSet garantit qu’un nombre spécifié de réplicas de Pod soient exécutés à un moment donné. Cependant, un Deployment est un concept de plus haut niveau qui gère les ReplicaSets et fournit des mises à jour déclaratives aux Pods ainsi que de nombreuses autres fonctionnalités utiles. Par conséquent, nous vous recommandons d’utiliser des Deployments au lieu d’utiliser directement des ReplicaSets, sauf si vous avez besoin d'une orchestration personnalisée des mises à jour ou si vous n'avez pas besoin de mises à jour.

Cela signifie qu'il est possible que vous n'ayez jamais besoin de manipuler des objets ReplicaSet : utilisez plutôt un déploiement et définissez votre application dans la section spec.

Exemple

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

Enregistrer ce manifeste dans frontend.yaml et le soumettre à un cluster Kubernetes va créer le ReplicaSet défini et les pods qu’il gère.

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

Vous pouvez ensuite récupérer les ReplicaSets actuellement déployés :

kubectl get rs

Et voir le frontend que vous avez créé :

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

Vous pouvez également vérifier l'état du ReplicaSet :

kubectl describe rs/frontend

Et vous verrez une sortie similaire à :

Name:		frontend
Namespace:	default
Selector:	tier=frontend,tier in (frontend)
Labels:		app=guestbook
		tier=frontend
Annotations:	<none>
Replicas:	3 current / 3 desired
Pods Status:	3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:       app=guestbook
                tier=frontend
  Containers:
   php-redis:
    Image:      gcr.io/google_samples/gb-frontend:v3
    Port:       80/TCP
    Requests:
      cpu:      100m
      memory:   100Mi
    Environment:
      GET_HOSTS_FROM:   dns
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----                -------------    --------    ------            -------
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-qhloh
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-dnjpy
  1m           1m          1        {replicaset-controller }             Normal      SuccessfulCreate  Created pod: frontend-9si5l

Et enfin, vous pourrez afficher les Pods déployés :

kubectl get Pods

Vous devriez voir des informations sur les Pods avec une sortie similaire à :

NAME             READY     STATUS    RESTARTS   AGE
frontend-9si5l   1/1       Running   0          1m
frontend-dnjpy   1/1       Running   0          1m
frontend-qhloh   1/1       Running   0          1m

Vous pouvez également vérifier que la OwnerReference de ces pods est définie sur le frontend ReplicaSet. Pour ce faire, récupérez le yaml de l’un des pods :

kubectl get pods frontend-9si5l -o yaml

La sortie sera similaire à celle-ci, avec les informations de l'interface ReplicaSet frontend définies dans le champ ownerReferences des métadonnées:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: 2019-01-31T17:20:41Z
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-9si5l
  namespace: default
  ownerReferences:
  - apiVersion: extensions/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: 892a2330-257c-11e9-aecd-025000000001
...

Acquisitions de Pod en dehors du template

Bien que vous puissiez créer des pods manuellement sans problème, il est fortement recommandé de s’assurer que ces pods n'ont pas de labels correspondant au sélecteur de l’un de vos ReplicaSets. Car un ReplicaSet n’est pas limité à posséder les pods spécifiés par son modèle - il peut acquérir d’autres pods de la manière spécifiée dans les sections précédentes.

Prenez l'exemple précédent de ReplicaSet, ainsi que les pods spécifiés dans le manifeste suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

Ces pods n’ayant pas de contrôleur (ni d’objet) en tant que référence propriétaire, ils correspondent au sélecteur de du ReplicaSet frontend, ils seront donc immédiatement acquis par ce ReplicaSet.

Supposons que vous créiez les pods une fois le ReplicaSet frontend déployé et qui a déjà déployé ses replicas de Pods initiaux afin de remplir son exigence de nombre de replicas :

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

Les nouveaux pods seront acquis par le ReplicaSet, puis immédiatement terminés car le ReplicaSet dépasserait alors le compte désiré.

En récupérant les pods :

kubectl get Pods

La sortie montre que les nouveaux pods sont soit déjà terminés, soit en voie de l'être :

NAME             READY   STATUS        RESTARTS   AGE
frontend-9si5l   1/1     Running       0          1m
frontend-dnjpy   1/1     Running       0          1m
frontend-qhloh   1/1     Running       0          1m
pod2             0/1     Terminating   0          4s

Cependant, si vous créez d'abord les pods :

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

Et puis créez le ReplicaSet :

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

Vous verrez que le ReplicaSet a acquis les pods et n'a créé que les nouveaux Pods manquants, conformément à ses spécifications, jusqu'au nombre souhaité de Pods. En récupérant les Pods :

kubectl get Pods

La sortie va donner :

NAME             READY   STATUS    RESTARTS   AGE
frontend-pxj4r   1/1     Running   0          5s
pod1             1/1     Running   0          13s
pod2             1/1     Running   0          13s

De cette manière, un ReplicaSet peut avoir un ensemble de Pods hétérogène.

Écrire un manifest de ReplicaSet

Comme avec tous les autres objets API Kubernetes, un ReplicaSet a besoin des champs apiVersion, kind et metadata. Pour ReplicaSets, l'attribut kind est toujours ReplicaSet.

Dans Kubernetes 1.9, la version de l'API apps/v1 pour le type ReplicaSet est la version actuelle et activée par défaut. La version de l'API apps/v1beta2 est obsolète.

Reportez-vous aux premières lignes de l'exemple frontend.yaml pour obtenir des conseils.

Un ReplicaSet a également besoin de .spec section.

Pod Template

L'attribut .spec.template est un modèle de pod qui requiert d'avoir des labels. Dans notre exemple frontend.yaml, nous avons un label : tier: frontend. Il faut faire attention à ne pas avoir des selecteurs que d'autres controllers utilisent, afin d'éviter que le ReplicaSet n'adopte ce pod.

Pour le champ restart policy, .spec.template.spec.restartPolicy, la seule valeur autorisée est Always, qui est la valeur par défaut.

Sélecteur de Pod

Le champ .spec.selector est un label selector. Tel que discuté précédemment, ce sont les labels utilisés pour identifier les Pods potentiels à acquérir. Dans notre exemple avec frontend.yaml, le sélecteur était :

matchLabels:
	tier: frontend

Dans le ReplicaSet, .spec.template.metadata.labels doit correspondre à spec.selector, ou sinon il sera rejeté par l'API.

Replicas

Vous pouvez spécifier le nombre de pods à exécuter simultanément en définissant .spec.replicas. Le ReplicaSet va créer/supprimer ses pods pour correspondre à ce nombre.

Si vous ne spécifiez pas .spec.replicas, la valeur par défaut est 1.

Travailler avec des ReplicaSets

Suppression d'un ReplicaSet et de ses pods

Pour supprimer un ReplicaSet et tous ses pods, utilisez kubectl delete. The Garbage collector supprime automatiquement tous les pods associés par défaut.

Lors de l’utilisation de l’API REST ou de la bibliothèque client-go, vous devez définir propagationPolicy sur Background ou Foreground dans l'option -d. Par exemple :

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
> -H "Content-Type: application/json"

Supprimer juste un ReplicaSet

Vous pouvez supprimer un ReplicaSet sans affecter ses pods à l’aide de kubectl delete avec l'option --cascade=false. Lorsque vous utilisez l'API REST ou la bibliothèque client-go, vous devez définir propagationPolicy sur Orphan. Par exemple :

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/extensions/v1beta1/namespaces/default/replicasets/frontend' \
> -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
> -H "Content-Type: application/json"

Une fois l’original supprimé, vous pouvez créer un nouveau ReplicaSet pour le remplacer. Tant que l'ancien et le nouveau .spec.selector sont identiques, le nouveau adoptera les anciens Pods. Cependant, le ReplicaSet ne fera aucun effort pour que les pods existants correspondent à un nouveau Pod template. Pour mettre à jour les Pods à une nouvelle spec de manière contrôlée, utilisez un Deployment, car les ReplicaSets ne supportent pas de rolling update directement.

Isoler les pods d'un ReplicaSet

Vous pouvez supprimer les pods d'un ReplicaSet en modifiant leurs labels. Cette technique peut être utilisée pour enlever les pods pour le débogage, récupération de données, etc. Les pods ainsi supprimés seront automatiquement remplacés (en supposant que le nombre de réplicas n’est pas également modifié).

Scaling d'un ReplicaSet

Un ReplicaSet peut facilement être scalé en mettant simplement à jour le champ .spec.replicas. Le contrôleur ReplicaSet garantit que le nombre souhaité de pods avec un sélecteur de label correspondant soient disponibles et opérationnels.

ReplicaSet en tant que Horizontal Pod Autoscaler Target

Un ReplicaSet peut également être une cible pour Horizontal Pod Autoscalers (HPA). Un ReplicaSet peut être mis à l'échelle automatiquement par un HPA. Voici un exemple HPA qui cible le ReplicaSet que nous avons créé dans l'exemple précédent.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

Enregistrer ce manifeste dans hpa-rs.yaml et le soumettre à un cluster Kubernetes devrait créer le HPA défini qui scale automatiquement le ReplicaSet cible en fonction de l'utilisation du processeur des pods répliqués.

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

Vous pouvez aussi utiliser la commande kubectl autoscale pour accomplir la même chose. (et c'est plus facile !)

kubectl autoscale rs frontend --max=10

Alternatives au ReplicaSet

Deployment (recommandé)

Le Deployment est un object qui peut posséder les ReplicaSets et les mettres à jour ainsi que leurs Pods de façon déclarative, côté serveur et avec des rolling updates.

Alors que les ReplicaSets peuvent être utilisés indépendamment, ils sont principalement utilisés aujourd'hui par Deployments comme mécanisme pour orchestrer la création, suppresion et mises à jour des Pods. Lorsque vous utilisez des Deployments, vous n’aurez plus à vous soucier de la gestion des ReplicaSets ainsi créés. Les déploiements possèdent et gèrent leurs ReplicaSets. C'est pourquoi il est recommandé d’utiliser les déploiements lorsque vous voulez des ReplicaSets.

Pods nus

Contrairement au cas où un utilisateur a créé directement des pods, un ReplicaSet remplace les pods supprimés ou terminés pour quelque raison que ce soit, par exemple en cas de défaillance d'un nœud ou de maintenance de nœud perturbateur, telle qu'une mise à jour kernel. Pour cette raison, nous vous recommandons d'utiliser un ReplicaSet même si votre application ne nécessite qu'un seul pod. Pensez-y de la même manière qu’un superviseur de processus, mais il supervise plusieurs pods sur plusieurs nœuds au lieu de processus individuels sur un seul nœud. Un ReplicaSet délègue les redémarrages de conteneurs locaux à un agent du nœud (par exemple, Kubelet ou Docker).

Job

Utilisez un Job au lieu d'un ReplicaSet pour les pods qui doivent se terminer seuls (c'est à dire des batch jobs).

DaemonSet

Utilisez un DaemonSet au lieu d’un ReplicaSet pour les pods qui fournissent une fonction au niveau du noeud, comme le monitoring ou la gestion des logs de ce noeud. Ces pods ont une durée de vie qui est liée durée de vie d’une machine : le pod doit être en cours d’exécution sur la machine avant le démarrage des autres Pods et sont sûrs de se terminer lorsque la machine est prête à être redémarrée/arrêtée.

ReplicationController

Les ReplicaSets sont les successeurs de ReplicationControllers. Les deux servent le même objectif et se comportent de la même manière, à la différence près que ReplicationController ne prend pas en charge les les exigences de sélecteur décrites dans le labels user guide. En tant que tels, les ReplicaSets sont préférés aux ReplicationControllers.

2.2 - Déploiements

Un Deployment (déploiement en français) fournit des mises à jour déclaratives pour Pods et ReplicaSets.

Vous décrivez un état désiré dans un déploiement et le controlleur déploiement change l'état réel à l'état souhaité à un rythme contrôlé. Vous pouvez définir des Deployments pour créer de nouveaux ReplicaSets, ou pour supprimer des déploiements existants et adopter toutes leurs ressources avec de nouveaux déploiements.

Cas d'utilisation

Voici des cas d'utilisation typiques pour les déploiements:

Création d'un déploiement

Voici un exemple de déploiement. Il crée un ReplicaSet pour faire apparaître trois pods nginx:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

Dans cet exemple:

  • Un déploiement nommé nginx-deployment est créé, indiqué par le champ .metadata.name.

  • Le déploiement crée trois pods répliqués, indiqués par le champ replicas.

  • Le champ selector définit comment le déploiement trouve les pods à gérer. Dans ce cas, vous sélectionnez simplement un label définie dans le template de pod (app:nginx). Cependant, des règles de sélection plus sophistiquées sont possibles, tant que le modèle de pod satisfait lui-même la règle.

  • Le champ template contient les sous-champs suivants:

    • Les Pods reçoivent le label app:nginx dans le champ labels.
    • La spécification du template de pod dans le champ .template.spec, indique que les pods exécutent un conteneur, nginx, qui utilise l'image nginx Docker Hub à la version 1.7.9.
    • Créez un conteneur et nommez-le nginx en utilisant le champ name.

Suivez les étapes ci-dessous pour créer le déploiement ci-dessus:

Avant de commencer, assurez-vous que votre cluster Kubernetes est opérationnel.

  1. Créez le déploiement en exécutant la commande suivante:

    kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
    
  2. Exécutez kubectl get deployments pour vérifier si le déploiement a été créé. Si le déploiement est toujours en cours de création, la sortie est similaire à:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   0/3     0            0           1s
    

    Lorsque vous inspectez les déploiements de votre cluster, les champs suivants s'affichent:

    • NAME répertorie les noms des déploiements dans le cluster.
    • DESIRED affiche le nombre souhaité de répliques de l'application, que vous définissez lorsque vous créez le déploiement. C'est l'état désiré.
    • CURRENT affiche le nombre de réplicas en cours d'exécution.
    • UP-TO-DATE affiche le nombre de réplicas qui ont été mises à jour pour atteindre l'état souhaité.
    • AVAILABLE affiche le nombre de réplicas de l'application disponibles pour vos utilisateurs.
    • AGE affiche la durée d'exécution de l'application.

    Notez que le nombre de réplicas souhaitées est de 3 selon le champ .spec.replicas.

  3. Pour voir l'état du déploiement, exécutez:

    kubectl rollout status deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    deployment "nginx-deployment" successfully rolled out
    
  4. Exécutez à nouveau kubectl get deployments quelques secondes plus tard. La sortie est similaire à ceci:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           18s
    

    Notez que le déploiement a créé les trois répliques et que toutes les répliques sont à jour (elles contiennent le dernier modèle de pod) et disponibles.

  5. Pour voir le ReplicaSet (rs) créé par le déploiement, exécutez kubectl get rs. La sortie est similaire à ceci:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-75675f5897   3         3         3       18s
    

    Notez que le nom du ReplicaSet est toujours formaté comme: [DEPLOYMENT-NAME]-[RANDOM-STRING]. La chaîne aléatoire est générée aléatoirement et utilise le pod-template-hash comme graine.

  6. Pour voir les labels générées automatiquement pour chaque Pod, exécutez kubectl get pods --show-labels. La sortie est similaire à ceci:

    NAME                                READY     STATUS    RESTARTS   AGE       LABELS
    nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    

Le ReplicaSet créé garantit qu'il y a trois pods nginx.

Étiquette pod-template-hash

Le label pod-template-hash est ajoutée par le contrôleur de déploiement à chaque ReplicaSet créé ou adopté par un déploiement.

Ce label garantit que les ReplicaSets enfants d'un déploiement ne se chevauchent pas. Il est généré en hachant le PodTemplate du ReplicaSet et en utilisant le hachage résultant comme valeur de label qui est ajoutée au sélecteur ReplicaSet, aux labels de template de pod et dans tous les pods existants que le ReplicaSet peut avoir.

Mise à jour d'un déploiement

Suivez les étapes ci-dessous pour mettre à jour votre déploiement:

  1. Mettons à jour les pods nginx pour utiliser l'image nginx: 1.9.1 au lieu de l'image nginx: 1.7.9.

    kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
    

    ou utilisez la commande suivante:

    kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1 --record
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment image updated
    

    Alternativement, vous pouvez éditer le déploiement et changer .spec.template.spec.containers[0].image de nginx: 1.7.9 à nginx: 1.9.1:

    kubectl edit deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment edited
    
  2. Pour voir l'état du déploiement, exécutez:

    kubectl rollout status deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    

    ou

    deployment "nginx-deployment" successfully rolled out
    

Obtenez plus de détails sur votre déploiement mis à jour:

  • Une fois le déploiement réussi, vous pouvez afficher le déploiement en exécutant kubectl get deployments. La sortie est similaire à ceci:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           36s
    
  • Exécutez kubectl get rs pour voir que le déploiement a mis à jour les pods en créant un nouveau ReplicaSet et en le redimensionnant jusqu'à 3 replicas, ainsi qu'en réduisant l'ancien ReplicaSet à 0 réplicas.

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       6s
    nginx-deployment-2035384211   0         0         0       36s
    
  • L'exécution de kubectl get pods ne devrait désormais afficher que les nouveaux pods:

    kubectl get pods
    

    La sortie est similaire à ceci:

    NAME                                READY     STATUS    RESTARTS   AGE
    nginx-deployment-1564180365-khku8   1/1       Running   0          14s
    nginx-deployment-1564180365-nacti   1/1       Running   0          14s
    nginx-deployment-1564180365-z9gth   1/1       Running   0          14s
    

    La prochaine fois que vous souhaitez mettre à jour ces pods, il vous suffit de mettre à jour le modèle de pod de déploiement à nouveau.

    Le déploiement garantit que seul un certain nombre de pods sont en panne pendant leur mise à jour. Par défaut, il garantit qu'au moins 75% du nombre souhaité de pods sont en place (25% max indisponible).

    Le déploiement garantit également que seul un certain nombre de pods sont créés au-dessus du nombre souhaité de pods. Par défaut, il garantit qu'au plus 125% du nombre de pods souhaité sont en hausse (surtension maximale de 25%).

    Par exemple, si vous regardez attentivement le déploiement ci-dessus, vous verrez qu'il a d'abord créé un nouveau pod, puis supprimé certains anciens pods et en a créé de nouveaux. Il ne tue pas les anciens Pods tant qu'un nombre suffisant de nouveaux Pods n'est pas apparu, et ne crée pas de nouveaux Pods tant qu'un nombre suffisant de Pods anciens n'a pas été tué. Il s'assure qu'au moins 2 pods sont disponibles et qu'au maximum 4 pods au total sont disponibles.

  • Obtenez les détails de votre déploiement:

    kubectl describe deployments
    

    La sortie est similaire à ceci:

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=2
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
        Labels:  app=nginx
        Containers:
        nginx:
            Image:        nginx:1.9.1
            Port:         80/TCP
            Environment:  <none>
            Mounts:       <none>
        Volumes:        <none>
        Conditions:
        Type           Status  Reason
        ----           ------  ------
        Available      True    MinimumReplicasAvailable
        Progressing    True    NewReplicaSetAvailable
        OldReplicaSets:  <none>
        NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
        Events:
        Type    Reason             Age   From                   Message
        ----    ------             ----  ----                   -------
        Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
        Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
        Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0
    

    Ici, vous voyez que lorsque vous avez créé le déploiement pour la première fois, il a créé un ReplicaSet (nginx-deployment-2035384211) et l'a mis à l'échelle directement jusqu'à 3 réplicas. Lorsque vous avez mis à jour le déploiement, il a créé un nouveau ReplicaSet (nginx-deployment-1564180365) et l'a mis à l'échelle jusqu'à 1, puis a réduit l'ancien ReplicaSet à 2, de sorte qu'au moins 2 pods étaient disponibles et au plus 4 pods ont été créés à chaque fois. Il a ensuite poursuivi la montée en puissance du nouveau et de l'ancien ReplicaSet, avec la même stratégie de mise à jour continue. Enfin, vous aurez 3 réplicas disponibles dans le nouveau ReplicaSet, et l'ancien ReplicaSet est réduit à 0.

Rollover (alias plusieurs mises à jour en vol)

Chaque fois qu'un nouveau déploiement est observé par le contrôleur de déploiement, un ReplicaSet est créé pour afficher les pods souhaités. Si le déploiement est mis à jour, le ReplicaSet existant qui contrôle les pods dont les étiquettes correspondent à .spec.selector mais dont le modèle ne correspond pas à .spec.template est réduit. Finalement, le nouveau ReplicaSet est mis à l'échelle à .spec.replicas et tous les anciens ReplicaSets sont mis à l'échelle à 0.

Si vous mettez à jour un déploiement alors qu'un déploiement existant est en cours, le déploiement crée un nouveau ReplicaSet conformément à la mise à jour et commence à le mettre à l'échelle, et arrête de mettre à jour le ReplicaSet qu'il augmentait précédemment - il l'ajoutera à sa liste de anciens ReplicaSets et commencera à le réduire.

Par exemple, supposons que vous créez un déploiement pour créer 5 répliques de nginx: 1.7.9, puis mettez à jour le déploiement pour créer 5 répliques de nginx: 1.9.1, alors que seulement 3 répliques de nginx:1.7.9 avait été créés. Dans ce cas, le déploiement commence immédiatement à tuer les 3 pods nginx: 1.7.9 qu'il avait créés et commence à créer des pods nginx: 1.9.1. Il n'attend pas que les 5 répliques de nginx: 1.7.9 soient créées avant de changer de cap.

Mises à jour du sélecteur de labels

Il est généralement déconseillé de mettre à jour le sélecteur de labels et il est suggéré de planifier vos sélecteurs à l'avance. Dans tous les cas, si vous devez effectuer une mise à jour du sélecteur de labels, soyez très prudent et assurez-vous d'avoir saisi toutes les implications.

  • Les ajouts de sélecteur nécessitent que les labels de template de pod dans la spécification de déploiement soient également mises à jour avec les nouveaux labels, sinon une erreur de validation est renvoyée. Cette modification ne se chevauche pas, ce qui signifie que le nouveau sélecteur ne sélectionne pas les ReplicaSets et les pods créés avec l'ancien sélecteur, ce qui entraîne la perte de tous les anciens ReplicaSets et la création d'un nouveau ReplicaSet.
  • Les mises à jour du sélecteur modifient la valeur existante dans une clé de sélection - entraînent le même comportement que les ajouts.
  • La suppression de sélecteur supprime une clé existante du sélecteur de déploiement - ne nécessite aucune modification dans les labels du template de pod. Les ReplicaSets existants ne sont pas orphelins et aucun nouveau ReplicaSet n'est créé, mais notez que le label supprimé existe toujours dans tous les Pods et ReplicaSets existants.

Annulation d'un déploiement

Parfois, vous souhaiterez peut-être annuler un déploiement; par exemple, lorsque le déploiement n'est pas stable, comme en cas d'échecs à répétition (CrashLoopBackOff). Par défaut, tout l'historique des déploiements d'un déploiement est conservé dans le système afin que vous puissiez le restaurer à tout moment (vous pouvez le modifier en modifiant la limite de l'historique des révisions).

  • Supposons que vous ayez fait une faute de frappe lors de la mise à jour du déploiement, en mettant le nom de l'image sous la forme nginx:1.91 au lieu de nginx: 1.9.1:

    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment image updated
    
  • Le déploiement est bloqué. Vous pouvez le vérifier en vérifiant l'état du déploiement:

    kubectl rollout status deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
    
  • Appuyez sur Ctrl-C pour arrêter la surveillance d'état de déploiement ci-dessus. Pour plus d'informations sur les déploiements bloqués, en savoir plus ici.

  • Vous voyez que le nombre d'anciens réplicas (nginx-deployment-1564180365 et nginx-deployment-2035384211) est 2, et les nouveaux réplicas (nginx-deployment-3066724191) est 1.

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       25s
    nginx-deployment-2035384211   0         0         0       36s
    nginx-deployment-3066724191   1         1         0       6s
    
  • En regardant les pods créés, vous voyez que 1 pod créé par le nouveau ReplicaSet est coincé dans une boucle pour récupérer son image:

    kubectl get pods
    

    La sortie est similaire à ceci:

    NAME                                READY     STATUS             RESTARTS   AGE
    nginx-deployment-1564180365-70iae   1/1       Running            0          25s
    nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
    nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
    nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
    
  • Obtenez la description du déploiement:

    kubectl describe deployment
    

    La sortie est similaire à ceci:

    Name:           nginx-deployment
    Namespace:      default
    CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
    Labels:         app=nginx
    Selector:       app=nginx
    Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
    StrategyType:       RollingUpdate
    MinReadySeconds:    0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.91
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    ReplicaSetUpdated
    OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
    NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
    Events:
      FirstSeen LastSeen    Count   From                    SubObjectPath   Type        Reason              Message
      --------- --------    -----   ----                    -------------   --------    ------              -------
      1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1
    

    Pour résoudre ce problème, vous devez revenir à une version précédente de Deployment qui est stable.

Vérification de l'historique de déploiement d'un déploiement

Suivez les étapes ci-dessous pour vérifier l'historique de déploiement:

  1. Tout d'abord, vérifiez les révisions de ce déploiement:

    kubectl rollout history deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployments "nginx-deployment"
    REVISION    CHANGE-CAUSE
    1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml --record=true
    2           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
    3           kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true
    

    CHANGE-CAUSE est copié de l'annotation de déploiement kubernetes.io/change-cause dans ses révisions lors de la création. Vous pouvez spécifier le messageCHANGE-CAUSE en:

    • Annoter le déploiement avec kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image mis à jour en 1.9.1"
    • Ajoutez le drapeau --record pour enregistrer la commande kubectl qui apporte des modifications à la ressource.
    • Modification manuelle du manifeste de la ressource.
  2. Pour voir les détails de chaque révision, exécutez:

    kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
    

    La sortie est similaire à ceci:

    deployments "nginx-deployment" revision 2
      Labels:       app=nginx
              pod-template-hash=1159050644
      Annotations:  kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
      Containers:
       nginx:
        Image:      nginx:1.9.1
        Port:       80/TCP
         QoS Tier:
            cpu:      BestEffort
            memory:   BestEffort
        Environment Variables:      <none>
      No volumes.
    

Revenir à une révision précédente

Suivez les étapes ci-dessous pour restaurer le déploiement de la version actuelle à la version précédente, qui est la version 2.

  1. Vous avez maintenant décidé d'annuler le déploiement actuel et le retour à la révision précédente:

    kubectl rollout undo deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment
    

    Alternativement, vous pouvez revenir à une révision spécifique en la spécifiant avec --to-revision:

    kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment
    

    Pour plus de détails sur les commandes liées au déploiement, lisez kubectl rollout.

    Le déploiement est maintenant rétabli à une précédente révision stable. Comme vous pouvez le voir, un événement DeploymentRollback pour revenir à la révision 2 est généré à partir du contrôleur de déploiement.

  2. Vérifiez si la restauration a réussi et que le déploiement s'exécute comme prévu, exécutez:

    kubectl get deployment nginx-deployment
    

    La sortie est similaire à ceci:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           30m
    
  3. Obtenez la description du déploiement:

    kubectl describe deployment nginx-deployment
    

    La sortie est similaire à ceci:

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=4
                            kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.9.1
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    NewReplicaSetAvailable
    OldReplicaSets:  <none>
    NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
    Events:
      Type    Reason              Age   From                   Message
      ----    ------              ----  ----                   -------
      Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
      Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
      Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0
    

Mise à l'échelle d'un déploiement

Vous pouvez mettre à l'échelle un déploiement à l'aide de la commande suivante:

kubectl scale deployment.v1.apps/nginx-deployment --replicas=10

La sortie est similaire à ceci:

deployment.apps/nginx-deployment scaled

En supposant que l'horizontal Pod autoscaling est activé dans votre cluster, vous pouvez configurer une mise à l'échelle automatique pour votre déploiement et choisir le nombre minimum et maximum de pods que vous souhaitez exécuter en fonction de l'utilisation du processeur de vos pods existants.

kubectl autoscale deployment.v1.apps/nginx-deployment --min=10 --max=15 --cpu-percent=80

La sortie est similaire à ceci:

deployment.apps/nginx-deployment scaled

Mise à l'échelle proportionnelle

Les déploiements RollingUpdate prennent en charge l'exécution simultanée de plusieurs versions d'une application. Lorsque vous ou un autoscaler mettez à l'échelle un déploiement RollingUpdate qui se trouve au milieu d'un déploiement (en cours ou en pause), le contrôleur de déploiement équilibre les réplicas supplémentaires dans les ReplicaSets actifs existants (ReplicaSets avec pods) afin d'atténuer le risque. Ceci est appelé mise à l'échelle proportionnelle.

Par exemple, vous exécutez un déploiement avec 10 réplicas, maxSurge=3, et maxUnavailable=2.

  • Assurez-vous que les 10 réplicas de votre déploiement sont en cours d'exécution.

    kubectl get deploy
    

    La sortie est similaire à ceci:

    NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment     10        10        10           10          50s
    
  • Vous effectuez une mise à jour vers une nouvelle image qui s'avère impossible à résoudre depuis l'intérieur du cluster.

    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:sometag
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment image updated
    
  • La mise à jour de l'image démarre un nouveau déploiement avec ReplicaSet nginx-deployment-1989198191, mais elle est bloquée en raison de l'exigence maxUnavailable que vous avez mentionnée ci-dessus. Découvrez l'état du déploiement:

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME                          DESIRED   CURRENT   READY     AGE
    nginx-deployment-1989198191   5         5         0         9s
    nginx-deployment-618515232    8         8         8         1m
    
  • Ensuite, une nouvelle demande de mise à l'échelle pour le déploiement arrive. La mise à l'échelle automatique incrémente les réplicas de déploiement à 15. Le contrôleur de déploiement doit décider où ajouter ces 5 nouvelles répliques. Si vous n'utilisiez pas la mise à l'échelle proportionnelle, les 5 seraient ajoutés dans le nouveau ReplicaSet. Avec une mise à l'échelle proportionnelle, vous répartissez les répliques supplémentaires sur tous les ReplicaSets. Des proportions plus importantes vont aux ReplicaSets avec le plus de répliques et des proportions plus faibles vont aux ReplicaSets avec moins de replicas. Tous les restes sont ajoutés au ReplicaSet avec le plus de répliques. Les ReplicaSets avec zéro réplicas ne sont pas mis à l'échelle.

Dans notre exemple ci-dessus, 3 répliques sont ajoutées à l'ancien ReplicaSet et 2 répliques sont ajoutées au nouveau ReplicaSet. Le processus de déploiement devrait éventuellement déplacer toutes les répliques vers le nouveau ReplicaSet, en supposant que les nouvelles répliques deviennent saines. Pour confirmer cela, exécutez:

kubectl get deploy

La sortie est similaire à ceci:

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m

Le statut de déploiement confirme la façon dont les réplicas ont été ajoutés à chaque ReplicaSet.

kubectl get rs

La sortie est similaire à ceci:

NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

Pause et reprise d'un déploiement

Vous pouvez suspendre un déploiement avant de déclencher une ou plusieurs mises à jour, puis le reprendre. Cela vous permet d'appliquer plusieurs correctifs entre la pause et la reprise sans déclencher de déploiements inutiles.

  • Par exemple, avec un déploiement qui vient d'être créé: Obtenez les détails du déploiement:

    kubectl get deploy
    

    La sortie est similaire à ceci:

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx     3         3         3            3           1m
    

    Obtenez le statut de déploiement:

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         1m
    
  • Mettez le déploiement en pause en exécutant la commande suivante:

    kubectl rollout pause deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment paused
    
  • Mettez ensuite à jour l'image du déploiement:

    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment image updated
    
  • Notez qu'aucun nouveau déploiement n'a commencé:

    kubectl rollout history deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployments "nginx"
    REVISION  CHANGE-CAUSE
    1   <none>
    
  • Obtenez l'état de déploiement pour vous assurer que le déploiement est correctement mis à jour:

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         2m
    
  • Vous pouvez effectuer autant de mises à jour que vous le souhaitez, par exemple, mettre à jour les ressources qui seront utilisées:

    kubectl set resources deployment.v1.apps/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment resource requirements updated
    

    L'état initial du déploiement avant de le suspendre continuera de fonctionner, mais les nouvelles mises à jour du déploiement n'auront aucun effet tant que le déploiement sera suspendu.

  • Finalement, reprenez le déploiement et observez un nouveau ReplicaSet à venir avec toutes les nouvelles mises à jour:

    kubectl rollout resume deployment.v1.apps/nginx-deployment
    

    La sortie est similaire à ceci:

    deployment.apps/nginx-deployment resumed
    
  • Regardez l'état du déploiement jusqu'à ce qu'il soit terminé.

    kubectl get rs -w
    

    La sortie est similaire à ceci:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   2         2         2         2m
    nginx-3926361531   2         2         0         6s
    nginx-3926361531   2         2         1         18s
    nginx-2142116321   1         2         2         2m
    nginx-2142116321   1         2         2         2m
    nginx-3926361531   3         2         1         18s
    nginx-3926361531   3         2         1         18s
    nginx-2142116321   1         1         1         2m
    nginx-3926361531   3         3         1         18s
    nginx-3926361531   3         3         2         19s
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         20s
    
  • Obtenez le statut du dernier déploiement:

    kubectl get rs
    

    La sortie est similaire à ceci:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         28s
    

Statut de déploiement

Un déploiement entre dans différents états au cours de son cycle de vie. Il peut être progressant lors du déploiement d'un nouveau ReplicaSet, il peut être effectué, ou il peut ne pas progresser.

Progression du déploiement

Kubernetes marque un déploiement comme progressing lorsqu'une des tâches suivantes est effectuée:

  • Le déploiement crée un nouveau ReplicaSet.
  • Le déploiement augmente son nouveau ReplicaSet.
  • Le déploiement réduit ses anciens ReplicaSet.
  • De nouveaux pods deviennent prêts ou disponibles (prêt pour au moins MinReadySeconds).

Vous pouvez surveiller la progression d'un déploiement à l'aide de kubectl rollout status.

Déploiement effectué

Kubernetes marque un déploiement comme effectué lorsqu'il présente les caractéristiques suivantes:

  • Toutes les répliques associées au déploiement ont été mises à jour vers la dernière version que vous avez spécifiée, ce qui signifie que toutes les mises à jour que vous avez demandées ont été effectuées.
  • Toutes les répliques associées au déploiement sont disponibles.
  • Aucune ancienne réplique pour le déploiement n'est en cours d'exécution.

Vous pouvez vérifier si un déploiement est terminé en utilisant kubectl rollout status. Si le déploiement s'est terminé avec succès, kubectl rollout status renvoie un code de sortie de 0.

kubectl rollout status deployment.v1.apps/nginx-deployment

La sortie est similaire à ceci:

Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
$ echo $?
0

Déploiement échoué

Votre déploiement peut rester bloqué en essayant de déployer son nouveau ReplicaSet sans jamais terminer. Cela peut se produire en raison de certains des facteurs suivants:

  • Quota insuffisant
  • Échecs de la sonde de préparation
  • Erreurs d'extraction d'image
  • Permissions insuffisantes
  • Plages limites
  • Mauvaise configuration de l'exécution de l'application

Vous pouvez détecter cette condition en spécifiant un paramètre d'échéance dans votre spécification de déploiement: (.spec.progressDeadlineSeconds). .spec.progressDeadlineSeconds indique le nombre de secondes pendant lesquelles le contrôleur de déploiement attend avant d'indiquer (dans l'état de déploiement) que la progression du déploiement est au point mort.

La commande kubectl suivante définit la spécification avec progressDeadlineSeconds pour que le contrôleur signale l'absence de progression pour un déploiement après 10 minutes:

kubectl patch deployment.v1.apps/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'

La sortie est similaire à ceci:

deployment.apps/nginx-deployment patched

Une fois le délai dépassé, le contrôleur de déploiement ajoute un DeploymentCondition avec les attributs suivants aux .status.conditions du déploiement:

  • Type=Progressing
  • Status=False
  • Reason=ProgressDeadlineExceeded

Voir les conventions Kubernetes API pour plus d'informations sur les conditions d'état.

Vous pouvez rencontrer des erreurs transitoires avec vos déploiements, soit en raison d'un délai d'attente bas que vous avez défini, soit en raison de tout autre type d'erreur pouvant être traité comme transitoire. Par exemple, supposons que votre quota soit insuffisant. Si vous décrivez le déploiement, vous remarquerez la section suivante:

kubectl describe deployment nginx-deployment

La sortie est similaire à ceci:

<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

Si vous exécutez kubectl get deployment nginx-deployment -o yaml, l'état de déploiement est similaire à ceci:

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

Finalement, une fois la date limite de progression du déploiement dépassée, Kubernetes met à jour le statut et la raison de la condition de progression:

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

Vous pouvez résoudre un problème de quota insuffisant en réduisant votre déploiement, en réduisant d'autres contrôleurs que vous exécutez ou en augmentant le quota de votre namespace. Si vous remplissez les conditions de quota et que le contrôleur de déploiement termine ensuite le déploiement de déploiement, vous verrez la mise à jour de l'état du déploiement avec une condition réussie (Status=True et Reason=NewReplicaSetAvailable).

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

Type=Available avec Status=True signifie que votre déploiement a une disponibilité minimale. La disponibilité minimale est dictée par les paramètres spécifiés dans la stratégie de déploiement. Type=Progressing avec Status=True signifie que votre déploiement est soit au milieu d'un déploiement et qu'il progresse ou qu'il a terminé avec succès sa progression et que les nouvelles répliques minimales requises sont disponibles (voir la raison de la condition pour les détails - dans notre cas, Reason=NewReplicaSetAvailable signifie que le déploiement est terminé).

Vous pouvez vérifier si un déploiement n'a pas pu progresser en utilisant kubectl rollout status. kubectl rollout status renvoie un code de sortie différent de zéro si le déploiement a dépassé le délai de progression.

kubectl rollout status deployment.v1.apps/nginx-deployment

La sortie est similaire à ceci:

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
$ echo $?
1

Agir sur un déploiement échoué

Toutes les actions qui s'appliquent à un déploiement complet s'appliquent également à un déploiement ayant échoué. Vous pouvez le mettre à l'échelle à la hausse/baisse, revenir à une révision précédente ou même la suspendre si vous devez appliquer plusieurs réglages dans le modèle de pod de déploiement.

Politique de nettoyage

Vous pouvez définir le champ .spec.revisionHistoryLimit dans un déploiement pour spécifier le nombre d'anciens ReplicaSets pour ce déploiement que vous souhaitez conserver. Le reste sera effacé en arrière-plan. Par défaut, c'est 10.

Déploiement des Canaries

Si vous souhaitez déployer des versions sur un sous-ensemble d'utilisateurs ou de serveurs à l'aide du déploiement, vous pouvez créer plusieurs déploiements, un pour chaque version, en suivant le modèle canari décrit dans gestion des ressources.

Écriture d'une spécification de déploiement

Comme pour toutes les autres configurations Kubernetes, un déploiement a besoin des champs apiVersion, kind et metadata. Pour des informations générales sur l'utilisation des fichiers de configuration, voir déploiement d'applications, configuration des conteneurs, et Utilisation de kubectl pour gérer les ressources.

Un déploiement nécessite également un .spec section.

Pod Template

Les .spec.template et .spec.selector sont les seuls champs obligatoires du .spec.

Le .spec.template est un Pod template. Il a exactement le même schéma qu'un Pod, sauf qu'il est imbriqué et n'a pas de apiVersion ou de kind.

En plus des champs obligatoires pour un pod, un Pod Template dans un déploiement doit spécifier des labels appropriées et une stratégie de redémarrage appropriée. Pour les labels, assurez-vous de ne pas chevaucher l'action d'autres contrôleurs. Voir sélecteur).

Seulement un .spec.template.spec.restartPolicy égal à Always est autorisé, ce qui est la valeur par défaut s'il n'est pas spécifié.

Répliques

.spec.replicas est un champ facultatif qui spécifie le nombre de pods souhaités. Il vaut par défaut 1.

Sélecteur

.spec.selector est un champ obligatoire qui spécifie un sélecteur de labels pour les pods ciblés par ce déploiement.

.spec.selector doit correspondre .spec.template.metadata.labels, ou il sera rejeté par l'API.

Dans la version d'API apps/v1, .spec.selector et .metadata.labels ne sont pas définis par défaut sur .spec.template.metadata.labels s'ils ne sont pas définis. Ils doivent donc être définis explicitement. Notez également que .spec.selector est immuable après la création du déploiement dans apps/v1.

Un déploiement peut mettre fin aux pods dont les étiquettes correspondent au sélecteur si leur modèle est différent de .spec.template ou si le nombre total de ces pods dépasse .spec.replicas. Il fait apparaître de nouveaux pods avec .spec.template si le nombre de pods est inférieur au nombre souhaité.

Si vous avez plusieurs contrôleurs qui ont des sélecteurs qui se chevauchent, les contrôleurs se battront entre eux et ne se comporteront pas correctement.

Stratégie

.spec.strategy spécifie la stratégie utilisée pour remplacer les anciens pods par de nouveaux. .spec.strategy.type peut être "Recreate" ou "RollingUpdate". "RollingUpdate" est la valeur par défaut.

Déploiment Recreate

Tous les pods existants sont tués avant que de nouveaux ne soient créés lorsque .spec.strategy.type==Recreate.

Déploiement de mise à jour continue

Le déploiement met à jour les pods dans une mise à jour continue quand .spec.strategy.type==RollingUpdate. Vous pouvez spécifier maxUnavailable et maxSurge pour contrôler le processus de mise à jour continue.

Max non disponible

.spec.strategy.rollingUpdate.maxUnavailable est un champ facultatif qui spécifie le nombre maximal de pods qui peuvent être indisponibles pendant le processus de mise à jour. La valeur peut être un nombre absolu (par exemple, 5) ou un pourcentage des pods souhaités (par exemple, 10%). Le nombre absolu est calculé à partir du pourcentage en arrondissant vers le bas. La valeur ne peut pas être 0 si .spec.strategy.rollingUpdate.maxSurge est 0. La valeur par défaut est 25%.

Par exemple, lorsque cette valeur est définie sur 30%, l'ancien ReplicaSet peut être réduit à 70% des pods souhaités immédiatement au démarrage de la mise à jour continue. Une fois que les nouveaux pods sont prêts, l'ancien ReplicaSet peut être réduit davantage, suivi d'une augmentation du nouveau ReplicaSet, garantissant que le nombre total de pods disponibles à tout moment pendant la mise à jour est d'au moins 70% des pods souhaités.

Max Surge

.spec.strategy.rollingUpdate.maxSurge est un champ facultatif qui spécifie le nombre maximal de pods pouvant être créés sur le nombre de pods souhaité. La valeur peut être un nombre absolu (par exemple, 5) ou un pourcentage des pods souhaités (par exemple, 10%). La valeur ne peut pas être 0 si MaxUnavailable est 0. Le nombre absolu est calculé à partir du pourcentage en arrondissant. La valeur par défaut est 25%.

Par exemple, lorsque cette valeur est définie sur 30%, le nouveau ReplicaSet peut être mis à l'échelle immédiatement au démarrage de la mise à jour continue, de sorte que le nombre total d'anciens et de nouveaux pods ne dépasse pas 130% des pods souhaités. Une fois que les anciens pods ont été détruits, le nouveau ReplicaSet peut être augmenté davantage, garantissant que le nombre total de pods en cours d'exécution à tout moment pendant la mise à jour est au maximum de 130% des pods souhaités.

Progress Deadline Seconds

.spec.progressDeadlineSeconds est un champ facultatif qui spécifie le nombre de secondes pendant lesquelles vous souhaitez attendre que votre déploiement progresse avant que le système ne signale que le déploiement a échoué - refait surface comme une condition avec Type=Progressing, Status=False et Reason=ProgressDeadlineExceeded dans l'état de la ressource. Le contrôleur de déploiement continuera de réessayer le déploiement. À l'avenir, une fois la restauration automatique implémentée, le contrôleur de déploiement annulera un déploiement dès qu'il observera une telle condition.

S'il est spécifié, ce champ doit être supérieur à .spec.minReadySeconds.

Min Ready Seconds

.spec.minReadySeconds est un champ facultatif qui spécifie le nombre minimum de secondes pendant lequel un pod nouvellement créé doit être prêt sans qu'aucun de ses conteneurs ne plante, pour qu'il soit considéré comme disponible. Cette valeur par défaut est 0 (le pod sera considéré comme disponible dès qu'il sera prêt). Pour en savoir plus sur le moment où un pod est considéré comme prêt, consultez Sondes de conteneur.

Rollback To

Le champ .spec.rollbackTo est obsolète dans les versions d'API extensions/v1beta1 et apps/v1beta1 et n'est plus pris en charge dans les versions d'API commençant par apps/v1beta2. Utilisez, kubectl rollout undo pour Revenir à une révision précédente.

Limite de l'historique des révisions

L'historique de révision d'un déploiement est stocké dans les ReplicaSets qu'il contrôle.

.spec.revisionHistoryLimit est un champ facultatif qui spécifie le nombre d'anciens ReplicaSets à conserver pour permettre la restauration. Ces anciens ReplicaSets consomment des ressources dans etcd et encombrent la sortie de kubectl get rs. La configuration de chaque révision de déploiement est stockée dans ses ReplicaSets; par conséquent, une fois un ancien ReplicaSet supprimé, vous perdez la possibilité de revenir à cette révision du déploiement. Par défaut, 10 anciens ReplicaSets seront conservés, mais sa valeur idéale dépend de la fréquence et de la stabilité des nouveaux déploiements.

Plus précisément, la définition de ce champ à zéro signifie que tous les anciens ReplicaSets avec 0 réplicas seront nettoyés. Dans ce cas, un nouveau panneau déroulant Déploiement ne peut pas être annulé, car son historique de révision est nettoyé.

Paused

.spec.paused est un champ booléen facultatif pour suspendre et reprendre un déploiement. La seule différence entre un déploiement suspendu et un autre qui n'est pas suspendu, c'est que toute modification apportée au PodTemplateSpec du déploiement suspendu ne déclenchera pas de nouveaux déploiements tant qu'il sera suspendu. Un déploiement n'est pas suspendu par défaut lors de sa création.

Alternative aux déploiements

kubectl rolling-update

kubectl rolling-update met à jour les pods et les ReplicationControllers de la même manière. Mais les déploiements sont recommandés, car ils sont déclaratifs, côté serveur et ont des fonctionnalités supplémentaires, telles que la restauration de toute révision précédente même après la mise à jour progressive..

2.3 - StatefulSets

StatefulSet est l'objet de l'API de charge de travail utilisé pour gérer des applications avec état (stateful).

Gère le déploiement et la mise à l'échelle d'un ensemble de Pods, et fournit des garanties sur l'ordre et l'unicité de ces Pods.

Comme un Déploiement, un StatefulSet gère des Pods qui sont basés sur une même spécification de conteneur. Contrairement à un Deployment, un StatefulSet maintient une identité pour chacun de ces Pods. Ces Pods sont créés à partir de la même spec, mais ne sont pas interchangeables : chacun a un identifiant persistant qu'il garde à travers tous ses re-scheduling.

Si vous voulez utiliser des volumes de stockage pour fournir de la persistance à votre charge de travail, vous pouvez utiliser un StatefulSet comme partie de la solution. Même si des Pods individuels d'un StatefulSet sont susceptibles d'échouer, les identifiants persistants des Pods rendent plus facile de faire correspondre les volumes existants aux nouveaux Pods remplaçant ceux ayant échoué.

Utiliser des StatefulSets

Les StatefulSets sont utiles pour des applications qui nécessitent une ou plusieurs des choses suivantes :

  • Des identifiants réseau stables et uniques.
  • Un stockage persistant stable.
  • Un déploiement et une mise à l'échelle ordonnés et contrôlés.
  • Des mises à jour continues (rolling update) ordonnées et automatisées.

Ci-dessus, stable est synonyme de persistance suite au (re)scheduling de Pods. Si une application ne nécessite aucun identifiant stable ou de déploiement, suppression ou mise à l'échelle stables, vous devriez déployer votre application en utilisant un objet de charge de travail fournissant un ensemble de réplicas sans état (stateless).

Un Deployment ou ReplicaSet peut être mieux adapté pour vos applications sans état.

Limitations

  • Le stockage pour un Pod donné doit être provisionné soit par un approvisionneur de PersistentVolume basé sur un storage class donné, soit pré-provisionné par un admin.
  • Supprimer et/ou réduire l'échelle d'un StatefulSet à zéro ne supprimera pas les volumes associés avec le StatefulSet. Ceci est fait pour garantir la sécurité des données, ce qui a généralement plus de valeur qu'une purge automatique de toutes les ressources relatives à un StatefulSet.
  • Les StatefulSets nécessitent actuellement un Service Headless qui est responsable de l'identité réseau des Pods. Vous êtes responsable de la création de ce Service.
  • Les StatefulSets ne fournissent aucune garantie de la terminaison des pods lorsqu'un StatefulSet est supprimé. Pour avoir une terminaison ordonnée et maîtrisée des pods du StatefulSet, il est possible de réduire l'échelle du StatefulSet à 0 avant de le supprimer.
  • Lors de l'utilisation de Rolling Updates avec la Politique de gestion des Pods par défaut (OrderedReady), il est possible de tomber dans un état indéfini nécessitant une intervention manuelle pour réparer.

Composants

L'exemple ci-dessous décrit les composants d'un StatefulSet.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # doit correspondre à .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # est 1 par défaut
  template:
    metadata:
      labels:
        app: nginx # doit correspondre à .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

Dans l'exemple ci-dessus :

  • Un Service Headless, appelé nginx, est utilisé pour contrôler le domaine réseau.
  • Le StatefulSet, appelé web, a une Spec indiquant que 3 réplicas du container nginx seront démarrés dans des Pods.
  • Le volumeClaimTemplates fournira un stockage stable utilisant des PersistentVolumes provisionnés par un approvisionneur de PersistentVolume.

Le nom d'un objet StatefulSet doit être un nom de sous-domaine DNS valide.

Sélecteur de Pod

Vous devez renseigner le champ .spec.selector d'un StatefulSet pour qu'il corresponde aux labels de son .spec.template.metadata.labels. Avant Kubernetes 1.8, le champ .spec.selector était mis par défaut s'il était omis. Pour les versions 1.8 et ultérieures, ne pas spécifier de sélecteur de Pod résulte en une erreur de validation lors de la création du StatefulSet.

Identité du Pod

Les Pods d'un StatefulSet ont une identité unique comprenant un ordinal, une identité réseau stable et un stockage stable. L'identité est accrochée au Pod, indépendamment du noeud sur lequel il est (re)programmé.

Index Ordinal

Pour un StatefulSet avec N réplicas, chaque Pod du StatefulSet se verra assigné un ordinal entier, de 0 à N-1, unique sur l'ensemble des pods.

ID réseau stable

Chaque Pod dans un StatefulSet dérive son nom d'hôte du nom du StatefulSet et de l'ordinal du Pod. Le modèle pour le nom d'hôte généré est $(nom statefulset)-$(ordinal). L'exemple ci-dessus créera trois Pods nommés web-0,web-1,web-2. Un StatefulSet peut utiliser un Service Headless pour contrôler le domaine de ses Pods. Le domaine pris en charge par ce Service prend la forme : $(nom service).$(namespace).svc.cluster.local, où "cluster.local" est le domaine du cluster. Chaque fois qu'un Pod est créé, il obtient un sous-domaine DNS correspondant, prenant la forme : $(nom pod).$(domaine du service gouvernant), où le service gouvernant est défini par le champ serviceName du StatefulSet.

En fonction de la façon dont est configuré le DNS dans votre cluster, vous ne pourrez peut-être pas rechercher immédiatement le nom DNS d'un pod nouvellement exécuté. Ce problème peut se produire lorsque d'autres clients dans le cluster ont déjà envoyé des requêtes pour le nom d'hôte du Pod avant sa création. La mise en cache négative (normale pour le DNS) signifie que les résultats des recherches précédentes ayant échoué sont mémorisés et réutilisés, même après que le Pod ait démarré, pendant au moins quelques secondes.

Si vous avez besoin de découvrir les Pods rapidement après leur création, vous avez plusieurs options :

  • Interrogez directement l'API Kubernetes (par exemple, à l'aide d'un watch) plutôt que de vous fier aux recherches DNS.
  • Réduisez le temps de mise en cache dans votre fournisseur de DNS Kubernetes (cela signifie généralement modifier le ConfigMap de CoreDNS, qui met actuellement en cache pendant 30 secondes).

Comme mentionné dans la section limitations, vous êtes responsable de créer le Service Headless responsable de l'identité réseau des Pods.

Voici quelques exemples de choix pour le domaine du cluster, le nom du service, le nom du StatefulSet et comment cela affecte les noms DNS des pods du StatefulSet.

Domaine Cluster Service (ns/nom) StatefulSet (ns/nom) Domaine StatefulSet DNS Pod Nom d'hôte
cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1}
cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1}
kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{0..N-1}

Stockage stable

Kubernetes crée un PersistentVolume pour chaque VolumeClaimTemplate. Dans l'exemple nginx ci-dessus, chaque Pod se verra affecter un unique PersistentVolume avec un StorageClass de my-storage-class et 1 Gib de stockage provisionné. Si aucun StorageClass n'est spécifié, alors le StorageClass par défaut sera utilisé. Lorsqu'un Pod est (re)schedulé sur un noeud, ses volumeMounts montent les PersistentVolumes associés aux
PersistentVolumeClaims. Notez que les PersistentVolumes associés avec les PersistentVolumeClaims des Pods ne sont pas supprimés lorsque les Pods, ou le StatefulSet, sont supprimés. Ceci doit être fait manuellement.

Étiquette du nom de Pod

Lorsque le StatefulSet Contrôleur crée un Pod, il ajoute une étiquette, statefulset.kubernetes.io/pod-name, renseignée avec le nom du Pod. Cette étiquette vous permet d'attacher un Service à un Pod spécifique du StatefulSet.

Garanties de déploiement et de mise à l'échelle

  • Pour un StatefulSet avec N réplicas, lorsque les Pods sont déployés, ils sont créés de manière séquentielle, dans l'ordre {0..N-1}.
  • Lorsque les Pods sont supprimés, ils sont terminés dans l'ordre inverse, {N-1..0}.
  • Avant qu'une opération de mise à l'échelle soit appliquée à un Pod, tous ses prédécesseurs doivent être Running et Ready.
  • Avant qu'un Pod soit terminé, tous ses successeurs doivent être complètement arrêtés.

Le StatefulSet ne devrait pas spécifier un pod.Spec.TerminationGracePeriodSeconds à 0. Cette pratique est dangereuse et fortement déconseillée. Pour plus d'explications, veuillez vous référer à forcer la suppression de Pods de StatefulSet.

Lorsque l'exemple nginx ci-dessus est créé, trois Pods seront déployés dans l'ordre web-0, web-1, web-2. web-1 ne sera pas déployé avant que web-0 soit Running et Ready, et web-2 ne sera pas déployé avant que web-1 soit Running et Ready. Si web-0 venait à échouer, après que web-1 soit Running et Ready, mais avant que web-2 soit lancé, web-2 ne serait pas lancé avant que web-0 soit correctement relancé et redevienne Running et Ready.

Si un utilisateur venait à mettre à l'échelle l'exemple déployé en patchant le StatefulSet pour que replicas=1, web-2 serait terminé en premier. web-1 ne serait pas terminé avant que web-2 ne soit complètement arrêté et supprimé. Si web-0 venait à échouer après que web-2 soit terminé et complètement arrêté, mais avant que web-1 soit terminé, web-1 ne serait pas terminé avant que web-0 soit Running et Ready.

Politiques de gestion d'un Pod

Dans Kubernetes 1.7 et ultérieurs, le StatefulSet vous permet d'assouplir ses garanties d'ordre, tout en préservant ses garanties d'unicité et d'identité via son champ .spec.podManagementPolicy.

Gestion de Pod OrderedReady

La gestion de Pod OrderedReady est la valeur par défaut pour les StatefulSets. Il implémente le comportement décrit ci-dessus.

Gestion de Pod Parallel

La gestion de Pod Parallel indique au contrôleur de StatefulSet de lancer ou terminer tous les Pods en parallèle, et de ne pas attendre que les Pods deviennent Running et Ready ou complètement terminés avant de lancer ou terminer un autre Pod. Cette option affecte seulement le comportement pour les opérations de mise à l'échelle. Les mises à jour ne sont pas affectées.

Stratégies de mise à jour

Dans Kubernetes 1.7 et ultérieurs, le champ .spec.updateStrategy d'un StatefulSet vous permet de configurer et désactiver les rolling updates automatisés pour les conteneurs, étiquettes, requête/limites de ressources, et annotations pour les Pods d'un StatefulSet.

On Delete

La stratégie de mise à jour OnDelete implémente l'ancien comportement (1.6 et précédents). Lorsque .spec.updateStrategy.type d'un StatefulSet est mis à OnDelete, le contrôleur de StatefulSet ne mettra pas à jour automatiquement les Pods dans un StatefulSet. Les utilisateurs doivent supprimer manuellement les Pods pour forcer le contrôleur à créer de nouveaux Pods qui réflètent les modifications faites à un .spec.template d'un StatefulSet.

Rolling Updates

La stratégie de mise à jour RollingUpdate implémente le rolling update automatisé pour les Pods d'un StatefulSet. C'est la stratégie par défaut lorsque .spec.updateStrategy n'est pas spécifié. Lorsqu'un .spec.updateStrategy.type d'un StatefulSet est mis à RollingUpdate, le contrôleur de StatefulSet va supprimer et recréer chaque Pod d'un StatefulSet. Il va procéder dans le même ordre que pour la terminaison d'un Pod (de l'ordinal le plus grand au plus petit), mettant à jour chaque Pod, un seul à la fois. Il va attendre qu'un Pod mis à jour soit Running et Ready avant de mettre à jour son prédécesseur.

Partitions

La stratégie de mise à jour RollingUpdate peut être partitionnée, en spécifiant une .spec.updateStrategy.rollingUpdate.partition. Si une partition est spécifiée, tous les Pods ayant un ordinal plus grand ou égal à la partition seront mis à jour lorsque le .spec.template du StatefulSet sera mis à jour. Tous les Pods ayant un ordinal inférieur à la partition ne sera pas mis à jour, et, même s'ils sont supprimés, ils seront recréés avec l'ancienne version. Si une .spec.updateStrategy.rollingUpdate.partition d'un StatefulSet est plus grand que son .spec.replicas, les mises à jour de son .spec.template ne seront pas propagés à ses Pods. Dans la plupart des cas vous n'aurez pas à utiliser de partition, mais elles sont utiles si vous désirez organiser une mise à jour, déployer une version canari, ou effectuer un déploiement par étapes.

Rollback forcé

En utilisant des Rolling Updates avec la politique de gestion d'un Pod par défaut (OrderedReady), il est possible de se retrouver dans un état inconsistant nécessitant une intervention manuelle pour réparation.

Si vous mettez à jour le template de Pod dans une configuration qui ne devient jamais Running et Ready (par exemple, du fait d'un mauvais binaire ou d'une erreur de configuration au niveau de l'application), le StatefulSet va arrêter le rollout et attendre.

Dans cet état, il n'est pas suffisant de revenir à une bonne configuration du template de Pod. En raison d'une erreur connue, le StatefulSet va continuer à attendre que le Pod en échec Pod devienne Ready (ce qui n'arrive jamais) avant qu'il tente de revenir à la bonne configuration.

Après être revenu au bon template, vous devez aussi supprimer tous les Pods que le StatefulSet avait déjà essayé de démarrer avec la mauvaise configuration. Le StatefulSet va alors commencer à recréer les Pods en utilisant le bon template.

A suivre