Concepts

Concepts Kubernetes

La section Concepts vous aide à mieux comprendre les composants du système Kubernetes et les abstractions que Kubernetes utilise pour représenter votre cluster. Elle vous aide également à mieux comprendre le fonctionnement de Kubernetes en général.

Vue d'ensemble

Pour utiliser Kubernetes, vous utilisez les objets de l'API Kubernetes pour décrire l'état souhaité de votre cluster: quelles applications ou autres processus que vous souhaitez exécuter, quelles images de conteneur elles utilisent, le nombre de réplicas, les ressources réseau et disque que vous mettez à disposition, et plus encore. Vous définissez l'état souhaité en créant des objets à l'aide de l'API Kubernetes, généralement via l'interface en ligne de commande, kubectl. Vous pouvez également utiliser l'API Kubernetes directement pour interagir avec le cluster et définir ou modifier l'état souhaité.

Une fois que vous avez défini l'état souhaité, le plan de contrôle Kubernetes (control plane en anglais) permet de faire en sorte que l'état actuel du cluster corresponde à l'état souhaité. Pour ce faire, Kubernetes effectue automatiquement diverses tâches, telles que le démarrage ou le redémarrage de conteneurs, la mise à jour du nombre de réplicas d'une application donnée, etc. Le control plane Kubernetes comprend un ensemble de processus en cours d'exécution sur votre cluster:

  • Le maître Kubernetes (Kubernetes master en anglais) qui est un ensemble de trois processus qui s'exécutent sur un seul nœud de votre cluster, désigné comme nœud maître (master node en anglais). Ces processus sont: kube-apiserver, kube-controller-manager et kube-scheduler.
  • Chaque nœud non maître de votre cluster exécute deux processus:
    • kubelet, qui communique avec le Kubernetes master.
    • kube-proxy, un proxy réseau reflétant les services réseau Kubernetes sur chaque nœud.

Objets Kubernetes

Kubernetes contient un certain nombre d'abstractions représentant l'état de votre système: applications et processus conteneurisés déployés, leurs ressources réseau et disque associées, ainsi que d'autres informations sur les activités de votre cluster. Ces abstractions sont représentées par des objets de l'API Kubernetes; consultez Vue d'ensemble des objets Kubernetes pour plus d'informations.

Les objets de base de Kubernetes incluent:

En outre, Kubernetes contient un certain nombre d'abstractions de niveau supérieur appelées Contrôleurs. Les contrôleurs s'appuient sur les objets de base et fournissent des fonctionnalités supplémentaires.

Voici quelques exemples:

Kubernetes control plane

Les différentes parties du control plane Kubernetes, telles que les processus Kubernetes master et kubelet, déterminent la manière dont Kubernetes communique avec votre cluster. Le control plane conserve un enregistrement de tous les objets Kubernetes du système et exécute des boucles de contrôle continues pour gérer l'état de ces objets. À tout moment, les boucles de contrôle du control plane répondent aux modifications du cluster et permettent de faire en sorte que l'état réel de tous les objets du système corresponde à l'état souhaité que vous avez fourni.

Par exemple, lorsque vous utilisez l'API Kubernetes pour créer un objet Deployment, vous fournissez un nouvel état souhaité pour le système. Le control plane Kubernetes enregistre la création de cet objet et exécute vos instructions en lançant les applications requises et en les planifiant vers des nœuds de cluster, afin que l'état actuel du cluster corresponde à l'état souhaité.

Kubernetes master

Le Kubernetes master est responsable du maintien de l'état souhaité pour votre cluster. Lorsque vous interagissez avec Kubernetes, par exemple en utilisant l'interface en ligne de commande kubectl, vous communiquez avec le master Kubernetes de votre cluster.

Le "master" fait référence à un ensemble de processus gérant l'état du cluster. En règle générale, tous les processus sont exécutés sur un seul nœud du cluster. Ce nœud est également appelé master. Le master peut également être répliqué pour la disponibilité et la redondance.

Noeuds Kubernetes

Les nœuds d’un cluster sont les machines (serveurs physiques, machines virtuelles, etc.) qui exécutent vos applications et vos workflows. Le master node Kubernetes contrôle chaque noeud; vous interagirez rarement directement avec les nœuds.

Metadonnées des objets Kubernetes

A suivre

Si vous souhaitez écrire une page de concept, consultez Utilisation de modèles de page pour plus d'informations sur le type de page pour la documentation d'un concept.

1 - Vue d'ensemble

Vue d'ensemble Kubernetes

1.1 - Qu'est-ce-que Kubernetes ?

Description de Kubernetes

Cette page est une vue d'ensemble de Kubernetes.

Kubernetes est une plate-forme open-source extensible et portable pour la gestion de charges de travail (workloads) et de services conteneurisés. Elle favorise à la fois l'écriture de configuration déclarative (declarative configuration) et l'automatisation. C'est un large écosystème en rapide expansion. Les services, le support et les outils Kubernetes sont largement disponibles.

Google a rendu open-source le projet Kubernetes en 2014. Le développement de Kubernetes est basé sur une décennie et demie d’expérience de Google avec la gestion de la charge et de la mise à l'échelle (scale) en production, associée aux meilleures idées et pratiques de la communauté.

Pourquoi ai-je besoin de Kubernetes et que peut-il faire ?

Kubernetes a un certain nombre de fonctionnalités. Il peut être considéré comme:

  • une plate-forme de conteneurs
  • une plate-forme de microservices
  • une plate-forme cloud portable et beaucoup plus.

Kubernetes fournit un environnement de gestion focalisé sur le conteneur (container-centric). Il orchestre les ressources machines (computing), la mise en réseau et l’infrastructure de stockage sur les workloads des utilisateurs. Cela permet de se rapprocher de la simplicité des Platform as a Service (PaaS) avec la flexibilité des solutions d'Infrastructure as a Service (IaaS), tout en gardant de la portabilité entre les différents fournisseurs d'infrastructures (providers).

Comment Kubernetes est-il une plate-forme ?

Même si Kubernetes fournit de nombreuses fonctionnalités, il existe toujours de nouveaux scénarios qui bénéficieraient de fonctionnalités complémentaires. Ces workflows spécifiques à une application permettent d'accélérer la vitesse de développement. Si l'orchestration fournie de base est acceptable pour commencer, il est souvent nécessaire d'avoir une automatisation robuste lorsque l'on doit la faire évoluer. C'est pourquoi Kubernetes a également été conçu pour servir de plate-forme et favoriser la construction d’un écosystème de composants et d’outils facilitant le déploiement, la mise à l’échelle et la gestion des applications.

Les Labels permettent aux utilisateurs d'organiser leurs ressources comme ils/elles le souhaitent. Les Annotations autorisent les utilisateurs à définir des informations personnalisées sur les ressources pour faciliter leurs workflows et fournissent un moyen simple aux outils de gérer la vérification d'un état (checkpoint state).

De plus, le plan de contrôle Kubernetes (control plane) est construit sur les mêmes APIs que celles accessibles aux développeurs et utilisateurs. Les utilisateurs peuvent écrire leurs propres contrôleurs (controllers), tels que les ordonnanceurs (schedulers), avec leurs propres APIs qui peuvent être utilisés par un outil en ligne de commande.

Ce choix de conception a permis de construire un ensemble d'autres systèmes par dessus Kubernetes.

Ce que Kubernetes n'est pas

Kubernetes n’est pas une solution PaaS (Platform as a Service). Kubernetes opérant au niveau des conteneurs plutôt qu'au niveau du matériel, il fournit une partie des fonctionnalités des offres PaaS, telles que le déploiement, la mise à l'échelle, l'équilibrage de charge (load balancing), la journalisation (logging) et la surveillance (monitoring). Cependant, Kubernetes n'est pas monolithique. Ces implémentations par défaut sont optionnelles et interchangeables. Kubernetes fournit les bases permettant de construire des plates-formes orientées développeurs, en laissant la possibilité à l'utilisateur de faire ses propres choix.

Kubernetes:

  • Ne limite pas les types d'applications supportées. Kubernetes prend en charge des workloads extrêmement diverses, dont des applications stateless, stateful ou orientées traitement de données (data-processing). Si l'application peut fonctionner dans un conteneur, elle devrait fonctionner correctement sur Kubernetes.
  • Ne déploie pas de code source et ne build pas d'application non plus. Les workflows d'Intégration Continue, de Livraison Continue et de Déploiement Continu (CI/CD) sont réalisés en fonction de la culture d'entreprise, des préférences ou des pré-requis techniques.
  • Ne fournit pas nativement de services au niveau applicatif tels que des middlewares (e.g., message buses), des frameworks de traitement de données (par exemple, Spark), des bases de données (e.g., mysql), caches, ou systèmes de stockage clusterisés (e.g., Ceph). Ces composants peuvent être lancés dans Kubernetes et/ou être accessibles à des applications tournant dans Kubernetes via des mécaniques d'intermédiation tel que Open Service Broker.
  • N'impose pas de solutions de logging, monitoring, ou alerting. Kubernetes fournit quelques intégrations primaires et des mécanismes de collecte et export de métriques.
  • Ne fournit ou n'impose un langague/système de configuration (e.g., jsonnet). Il fournit une API déclarative qui peut être ciblée par n'importe quelle forme de spécifications déclaratives.
  • Ne fournit ou n'adopte aucune mécanique de configuration des machines, de maintenance, de gestion ou de contrôle de la santé des systèmes.

De plus, Kubernetes n'est pas vraiment un système d'orchestration. En réalité, il élimine le besoin d'orchestration. Techniquement, l'orchestration se définit par l'exécution d'un workflow spécifié : premièrement faire A, puis B, puis C. Kubernetes quant à lui est composé d'un ensemble de processus de contrôle qui pilotent l'état courant vers l'état désiré. Peu importe comment on arrive du point A au point C. Un contrôle centralisé n'est pas non plus requis. Cela aboutit à un système plus simple à utiliser et plus puissant, robuste, résiliant et extensible.

Pourquoi les conteneurs ?

Vous cherchez des raisons d'utiliser des conteneurs ?

Pourquoi les conteneurs ?

L'ancienne façon (old way) de déployer des applications consistait à installer les applications sur un hôte en utilisant les systèmes de gestions de paquets natifs. Cela avait pour principale inconvénient de lier fortement les exécutables, la configuration, les librairies et le cycle de vie de chacun avec l'OS. Il est bien entendu possible de construire une image de machine virtuelle (VM) immuable pour arriver à produire des publications (rollouts) ou retours arrières (rollbacks), mais les VMs sont lourdes et non-portables.

La nouvelle façon (new way) consiste à déployer des conteneurs basés sur une virtualisation au niveau du système d'opération (operation-system-level) plutôt que de la virtualisation hardware. Ces conteneurs sont isolés les uns des autres et de l'hôte : ils ont leurs propres systèmes de fichiers, ne peuvent voir que leurs propres processus et leur usage des ressources peut être contraint. Ils sont aussi plus faciles à construire que des VMs, et vu qu'ils sont décorrélés de l'infrastructure sous-jacente et du système de fichiers de l'hôte, ils sont aussi portables entre les différents fournisseurs de Cloud et les OS.

Étant donné que les conteneurs sont petits et rapides, une application peut être packagée dans chaque image de conteneurs. Cette relation application-image tout-en-un permet de bénéficier de tous les bénéfices des conteneurs. Avec les conteneurs, des images immuables de conteneurs peuvent être créées au moment du build/release plutôt qu'au déploiement, vu que chaque application ne dépend pas du reste de la stack applicative et n'est pas liée à l'environnement de production. La génération d'images de conteneurs au moment du build permet d'obtenir un environnement constant qui peut être déployé tant en développement qu'en production. De la même manière, les conteneurs sont bien plus transparents que les VMs, ce qui facilite le monitoring et le management. Cela est particulièrement vrai lorsque le cycle de vie des conteneurs est géré par l'infrastructure plutôt que caché par un gestionnaire de processus à l'intérieur du conteneur. Avec une application par conteneur, gérer ces conteneurs équivaut à gérer le déploiement de son application.

Résumé des bénéfices des conteneurs :

  • Création et déploiement agile d'applications : Augmente la simplicité et l'efficacité de la création d'images par rapport à l'utilisation d'images de VM.
  • Développement, intégration et déploiement Continus: Fournit un processus pour constuire et déployer fréquemment et de façon fiable avec la capacité de faire des rollbacks rapides et simples (grâce à l'immuabilité de l'image).
  • Séparation des besoins entre Dev et Ops: Création d'images applicatives au moment du build plutôt qu'au déploiement, tout en séparant l'application de l'infrastructure.
  • Observabilité Informations venant non seulement du système d'exploitation sous-jacent mais aussi des signaux propres de l'application.
  • Consistance entre les environnements de développement, tests et production: Fonctionne de la même manière que ce soit sur un poste local que chez un fournisseur d'hébergement / dans le Cloud.
  • Portabilité entre Cloud et distribution système: Fonctionne sur Ubuntu, RHEL, CoreOS, on-prem, Google Kubernetes Engine, et n'importe où.
  • Gestion centrée Application: Bascule le niveau d'abstraction d'une virtualisation hardware liée à l'OS à une logique de ressources orientée application.
  • Micro-services faiblement couplés, distribués, élastiques: Les applications sont séparées en petits morceaux indépendants et peuvent être déployées et gérées dynamiquement -- pas une stack monolithique dans une seule machine à tout faire.
  • Isolation des ressources: Performances de l'application prédictibles.
  • Utilisation des ressources: Haute efficacité et densité.

Qu'est-ce-que Kubernetes signifie ? K8s ?

Le nom Kubernetes tire son origine du grec ancien, signifiant capitaine ou pilote et est la racine de gouverneur et cybernetic. K8s est l'abbréviation dérivée par le remplacement des 8 lettres "ubernete" par "8".

A suivre

1.2 - Composants de Kubernetes

Ce document résume les divers composants binaires requis pour livrer un cluster Kubernetes fonctionnel.

Composants Master

Les composants Master fournissent le plan de contrôle (control plane) du cluster. Les composants Master prennent des décisions globales à propos du cluster (par exemple, la planification (scheduling)). Ils détectent et répondent aux événements du cluster (par exemple, démarrer un nouveau Pod lorsque le champ replicas d'un déploiement n'est pas satisfait).

Les composants Master peuvent être exécutés sur n'importe quelle machine du cluster. Toutefois, par soucis de simplicité, les scripts de mise en route démarrent typiquement tous les composants master sur la même machine et n'exécutent pas de conteneurs utilisateur sur cette machine. Voir Construire des Clusters en Haute Disponibilité pour une configuration d'exemple en multi-master-VM.

kube-apiserver

Composant sur le master qui expose l'API Kubernetes. Il s'agit du front-end pour le plan de contrôle Kubernetes.

Il est conçu pour une mise à l'échelle horizontale, ce qui veut dire qu'il met à l'échelle en déployant des instances supplémentaires. Voir Construire des Clusters en Haute Disponibilité.

etcd

Base de données clé-valeur consistante et hautement disponible utilisée comme mémoire de sauvegarde pour toutes les données du cluster.

Si votre cluster Kubernetes utilise etcd comme mémoire de sauvegarde, assurez-vous d'avoir un plan de back up pour ces données.

Vous pouvez trouver plus d'informations à propos d'etcd dans la documentation officielle.

kube-scheduler

Composant sur le master qui surveille les pods nouvellement créés qui ne sont pas assignés à un nœud et sélectionne un nœud sur lequel ils vont s'exécuter.

Les facteurs pris en compte pour les décisions de planification (scheduling) comprennent les exigences individuelles et collectives en ressources, les contraintes matérielles/logicielles/politiques, les spécifications d'affinité et d'anti-affinité, la localité des données, les interférences entre charges de travail et les dates limites.

kube-controller-manager

Composant du master qui exécute les contrôleurs.

Logiquement, chaque contrôleur est un processus à part mais, pour réduire la complexité, les contrôleurs sont tous compilés dans un seul binaire et s'exécutent dans un seul processus.

Ces contrôleurs incluent :

  • Node Controller : Responsable de détecter et apporter une réponse lorsqu'un nœud tombe en panne.
  • Replication Controller : Responsable de maintenir le bon nombre de pods pour chaque objet ReplicationController dans le système.
  • Endpoints Controller : Remplit les objets Endpoints (c'est-à-dire joint les Services et Pods).
  • Service Account & Token Controllers : Créent des comptes par défaut et des jetons d'accès à l'API pour les nouveaux namespaces.

cloud-controller-manager

Le cloud-controller-manager exécute les contrôleurs qui interagissent avec les fournisseurs cloud sous-jacents. Le binaire du cloud-controller-manager est une fonctionnalité alpha introduite dans la version 1.6 de Kubernetes.

Le cloud-controller-manager exécute seulement les boucles spécifiques des fournisseurs cloud. Vous devez désactiver ces boucles de contrôleurs dans le kube-controller-manager. Vous pouvez désactiver les boucles de contrôleurs en définissant la valeur du flag --cloud-provider à external lors du démarrage du kube-controller-manager.

Le cloud-controller-manager permet au code du fournisseur cloud et au code de Kubernetes d'évoluer indépendamment l'un de l'autre. Dans des versions antérieures, le code de base de Kubernetes dépendait du code spécifique du fournisseur cloud pour la fonctionnalité. Dans des versions ultérieures, le code spécifique des fournisseurs cloud devrait être maintenu par les fournisseurs cloud eux-mêmes et lié au cloud-controller-manager lors de l'exécution de Kubernetes.

Les contrôleurs suivants ont des dépendances vers des fournisseurs cloud :

  • Node Controller : Pour vérifier le fournisseur de cloud afin de déterminer si un nœud a été supprimé dans le cloud après avoir cessé de répondre
  • Route Controller : Pour mettre en place des routes dans l'infrastructure cloud sous-jacente
  • Service Controller : Pour créer, mettre à jour et supprimer les load balancers des fournisseurs cloud
  • Volume Controller : Pour créer, attacher et monter des Volumes, et interagir avec le fournisseur cloud pour orchestrer les volumes.

Composants de nœud

Les composants de nœud (Node components) s'exécutent sur chaque nœud, en maintenant les pods en exécution et en fournissant l'environnement d'exécution Kubernetes.

kubelet

Un agent qui s'exécute sur chaque nœud du cluster. Il s'assure que les conteneurs fonctionnent dans un pod.

Le kubelet prend un ensemble de PodSpecs fournis par divers mécanismes et s'assure du fonctionnement et de la santé des conteneurs décrits dans ces PodSpecs. Le kubelet ne gère que les conteneurs créés par Kubernetes.

kube-proxy

kube-proxy est un proxy réseau qui s'exécute sur chaque nœud du cluster et implémente une partie du concept Kubernetes de Service.

kube-proxy maintient les règles réseau sur les nœuds. Ces règles réseau permettent une communication réseau vers les Pods depuis des sessions réseau à l'intérieur ou à l'extérieur du cluster.

kube-proxy utilise la couche de filtrage de paquets du système d'exploitation s'il y en a une et qu'elle est disponible. Sinon, kube-proxy transmet le trafic lui-même.

Container Runtime

L'environnement d'exécution de conteneurs est le logiciel responsable de l'exécution des conteneurs.

Kubernetes est compatible avec plusieurs environnements d'exécution de conteneur: Docker, containerd, cri-o, rktlet ainsi que toute implémentation de Kubernetes CRI (Container Runtime Interface).

Addons

Les addons utilisent les ressources Kubernetes (DaemonSet, Déploiement, etc) pour implémenter des fonctionnalités cluster. Comme ces derniers fournissent des fonctionnalités au niveau du cluster, les ressources dans des namespaces pour les addons appartiennent au namespace kube-system.

Les addons sélectionnés sont décrits ci-dessous. Pour une liste étendue des addons disponibles, voir la page Addons.

DNS

Tandis que les autres addons ne sont pas strictement requis, tous les clusters Kubernetes devraient avoir un DNS cluster car de nombreux exemples en dépendent.

Le DNS Cluster est un serveur DNS, en plus des autres serveurs DNS dans votre environnement, qui sert les enregistrements DNS pour les services Kubernetes.

Les conteneurs démarrés par Kubernetes incluent automatiquement ce serveur DNS dans leurs recherches DNS.

Interface utilisateur Web (Dashboard)

Le Dashboard est une interface utilisateur web à but général pour les clusters Kubernetes. Il permet aux utilisateurs de gérer et de dépanner aussi bien des applications s'exécutant dans le cluster que le cluster lui-même.

La surveillance des ressources de conteneur

La surveillance des ressources de conteneur enregistre des métriques chronologiques génériques à propos des conteneurs dans une base de données centrale et fournit une interface utilisateur pour parcourir ces données.

Le logging au niveau cluster

Un mécanisme de logging au niveau cluster est chargé de sauvegarder les logs des conteneurs dans un magasin de logs central avec une interface de recherche/navigation.

A suivre

1.3 - Utilisation des objets Kubernetes

1.3.1 - Namespaces

Kubernetes prend en charge plusieurs clusters virtuels présents sur le même cluster physique. Ces clusters virtuels sont appelés namespaces (espaces de nommage en français).

Quand utiliser plusieurs namespaces

Les namespaces sont destinés à être utilisés dans les environnements ayant de nombreux utilisateurs répartis en plusieurs équipes ou projets. Pour les clusters de quelques dizaines d'utilisateurs, vous n'avez pas besoin d'utiliser de namespaces. Commencez à utiliser des namespaces lorsque vous avez besoin des fonctionnalités qu'ils fournissent.

Les namespaces sont des groupes de noms. Ils fournissent un modèle d'isolation de nommage des ressources. Les noms des ressources doivent être uniques dans un namespace, mais pas dans l'ensemble des namespaces. Les namespaces ne peuvent pas être imbriqués les uns dans les autres et chaque ressource Kubernetes ne peut se trouver que dans un seul namespace.

Les namespaces sont un moyen de répartir les ressources d'un cluster entre plusieurs utilisateurs (via quota de ressources).

Dans les futures versions de Kubernetes, les objets du même namespace auront les mêmes stratégies de contrôle d'accès par défaut.

Il n'est pas nécessaire d'utiliser plusieurs namespaces juste pour séparer des ressources légèrement différentes, telles que les versions du même logiciel: utiliser les labels pour distinguer les ressources dans le même namespace.

Utilisation des namespaces

La création et la suppression des namespaces sont décrites dans la Documentation du guide d'administration pour les namespaces.

Affichage des namespaces

Dans un cluster vous pouvez lister les namespaces actuels à l'aide de :

kubectl get namespace
NAME              STATUS   AGE
default           Active   1d
kube-node-lease   Active   1d
kube-public       Active   1d
kube-system       Active   1d

Kubernetes démarre avec quatre namespaces initiaux:

  • default Le namespace par défaut pour les objets sans mention d'autre namespace
  • kube-system Le namespace pour les objets créés par Kubernetes lui-même
  • kube-public Ce namespace est créé automatiquement et est visible par tous les utilisateurs (y compris ceux qui ne sont pas authentifiés). Ce namespace est principalement réservé à l'utilisation du cluster, au cas où certaines ressources devraient être disponibles publiquement dans l'ensemble du cluster. L'aspect public de ce namespace n'est qu'une convention, pas une exigence.
  • kube-node-lease Ce namespace contient les objets de bail associés à chaque nœud, ce qui améliore les performances des pulsations du nœud à mesure que le cluster évolue.

Définition du namespaces pour une requête

Pour définir le namespace pour une requête en cours, utilisez l'indicateur --namespace.

Par exemple:

kubectl run nginx --image=nginx --namespace=<insert-namespace-name-here>
kubectl get pods --namespace=<insert-namespace-name-here>

Spécifier un namespace

Vous pouvez enregistrer de manière permanente le namespace à utiliser pour toutes les commandes kubectl à suivre.

kubectl config set-context --current --namespace=<insert-namespace-name-here>
# Validez-le
kubectl config view --minify | grep namespace:

Namespaces et DNS

Lorsque vous créez un Service, il crée une entrée DNS correspondante. Cette entrée est de la forme <nom-service>.<nom-namespace>.svc.cluster.local, ce qui signifie que si un conteneur utilise simplement <nom-service>, il résoudra le service qui est local à son namespace. Ceci est utile pour utiliser la même configuration pour plusieurs namespaces tels que le Développement, la Qualification et la Production. Si vous voulez naviguer entre plusieurs namespaces, vous devez utiliser le nom de domaine complet (FQDN ou nom de domaine complètement qualifié en français).

Tous les objets ne se trouvent pas dans un namespace

La plupart des ressources Kubernetes (par exemple, pods, services, contrôleurs de réplication et autres) sont dans des namespaces. Cependant, les ressources de type namespace ne sont pas elles-mêmes dans un namespace. Et les ressources de bas niveau, telles que les nœuds et les volumes persistants, ne se trouvent dans aucun namespace.

Pour voir quelles ressources Kubernetes sont et ne sont pas dans un namespace:

# Dans un namespace
kubectl api-resources --namespaced=true

# Pas dans un namespace
kubectl api-resources --namespaced=false

A suivre

2 - Architecture de Kubernetes

Architecture Kubernetes

2.1 - Noeuds

Concept Noeud Kubernetes

Un nœud est une machine de travail dans Kubernetes, connue auparavant sous le nom de minion. Un nœud peut être une machine virtuelle ou une machine physique, selon le cluster. Chaque nœud contient les services nécessaires à l'exécution de pods et est géré par les composants du master. Les services sur un nœud incluent le container runtime, kubelet et kube-proxy. Consultez la section Le Nœud Kubernetes dans le document de conception de l'architecture pour plus de détails.

Statut du nœud

Le statut d'un nœud contient les informations suivantes:

Chaque section est décrite en détail ci-dessous.

Adresses

L'utilisation de ces champs varie en fonction de votre fournisseur de cloud ou de votre configuration physique.

  • HostName: Le nom d'hôte tel que rapporté par le noyau du nœud. Peut être remplacé via le paramètre kubelet --hostname-override.
  • ExternalIP: En règle générale, l'adresse IP du nœud pouvant être routé en externe (disponible de l'extérieur du cluster).
  • InternalIP: En règle générale, l'adresse IP du nœud pouvant être routé uniquement dans le cluster.

Condition

Le champ conditions décrit le statut de tous les nœuds Running.

Node Condition Description
OutOfDisk True si l'espace disponible sur le nœud est insuffisant pour l'ajout de nouveaux pods, sinon False
Ready True si le noeud est sain et prêt à accepter des pods, False si le noeud n'est pas sain et n'accepte pas de pods, et Unknown si le contrôleur de noeud n'a pas reçu d'information du noeud depuis node-monitor-grace-period (la valeur par défaut est de 40 secondes)
MemoryPressure True s'il existe une pression sur la mémoire du noeud, c'est-à-dire si la mémoire du noeud est faible; autrement False
PIDPressure True s'il existe une pression sur le nombre de processus, c'est-à-dire s'il y a trop de processus sur le nœud; autrement False
DiskPressure True s'il existe une pression sur la taille du disque, c'est-à-dire si la capacité du disque est faible; autrement False
NetworkUnavailable True si le réseau pour le noeud n'est pas correctement configuré, sinon False

La condition de noeud est représentée sous la forme d'un objet JSON. Par exemple, la réponse suivante décrit un nœud sain.

"conditions": [
  {
    "type": "Ready",
    "status": "True"
  }
]

Si le statut de l'état Ready reste Unknown ou False plus longtemps que pod-eviction-timeout, un argument est passé au kube-controller-manager et les pods sur le nœud sont programmés pour être supprimés par le contrôleur du nœud. Le délai d’expulsion par défaut est de cinq minutes.. Dans certains cas, lorsque le nœud est inaccessible, l'apiserver est incapable de communiquer avec le kubelet sur le nœud. La décision de supprimer les pods ne peut pas être communiquée au kublet tant que la communication avec l'apiserver n'est pas rétablie. Entre-temps, les pods dont la suppression est planifiée peuvent continuer à s'exécuter sur le nœud inaccessible.

Dans les versions de Kubernetes antérieures à 1.5, le contrôleur de noeud forcait la suppression de ces pods inaccessibles de l'apiserver. Toutefois, dans la version 1.5 et ultérieure, le contrôleur de noeud ne force pas la suppression des pods tant qu'il n'est pas confirmé qu'ils ont cessé de fonctionner dans le cluster. Vous pouvez voir que les pods en cours d'exécution sur un nœud inaccessible sont dans l'état Terminating ou Unknown. Dans les cas où Kubernetes ne peut pas déduire de l'infrastructure sous-jacente si un nœud a définitivement quitté un cluster, l'administrateur du cluster peut avoir besoin de supprimer l'objet nœud à la main. La suppression de l'objet nœud de Kubernetes entraîne la suppression de tous les objets Pod exécutés sur le nœud de l'apiserver et libère leurs noms.

Dans la version 1.12, la fonctionnalité TaintNodesByCondition est promue en version bêta, ce qui permet au contrôleur de cycle de vie du nœud de créer automatiquement des marquages (taints en anglais) qui représentent des conditions. De même, l'ordonnanceur ignore les conditions lors de la prise en compte d'un nœud; au lieu de cela, il regarde les taints du nœud et les tolérances d'un pod.

Les utilisateurs peuvent désormais choisir entre l'ancien modèle de planification et un nouveau modèle de planification plus flexible. Un pod qui n’a aucune tolérance est programmé selon l’ancien modèle. Mais un pod qui tolère les taints d'un nœud particulier peut être programmé sur ce nœud.

Capacité

Décrit les ressources disponibles sur le nœud: CPU, mémoire et nombre maximal de pods pouvant être planifiés sur le nœud.

Info

Informations générales sur le noeud, telles que la version du noyau, la version de Kubernetes (versions de kubelet et kube-proxy), la version de Docker (si utilisée), le nom du système d'exploitation. Les informations sont collectées par Kubelet à partir du noeud.

Gestion

Contrairement aux pods et aux [services] (/docs/concepts/services-networking/service/), un nœud n'est pas créé de manière inhérente par Kubernetes: il est créé de manière externe par un cloud tel que Google Compute Engine, ou bien il existe dans votre pool de machines physiques ou virtuelles. Ainsi, lorsque Kubernetes crée un nœud, il crée un objet qui représente le nœud. Après la création, Kubernetes vérifie si le nœud est valide ou non. Par exemple, si vous essayez de créer un nœud à partir du contenu suivant:

{
  "kind": "Node",
  "apiVersion": "v1",
  "metadata": {
    "name": "10.240.79.157",
    "labels": {
      "name": "my-first-k8s-node"
    }
  }
}

Kubernetes crée un objet noeud en interne (la représentation) et valide le noeud en vérifiant son intégrité en fonction du champ metadata.name. Si le nœud est valide, c'est-à-dire si tous les services nécessaires sont en cours d'exécution, il est éligible pour exécuter un pod. Sinon, il est ignoré pour toute activité de cluster jusqu'à ce qu'il devienne valide.

Actuellement, trois composants interagissent avec l'interface de noeud Kubernetes: le contrôleur de noeud, kubelet et kubectl.

Contrôleur de nœud

Le contrôleur de noeud (node controller en anglais) est un composant du master Kubernetes qui gère divers aspects des noeuds.

Le contrôleur de nœud a plusieurs rôles dans la vie d'un nœud. La première consiste à affecter un bloc CIDR au nœud lorsqu’il est enregistré (si l’affectation CIDR est activée).

La seconde consiste à tenir à jour la liste interne des nœuds du contrôleur de nœud avec la liste des machines disponibles du fournisseur de cloud. Lorsqu'il s'exécute dans un environnement de cloud, chaque fois qu'un nœud est en mauvaise santé, le contrôleur de nœud demande au fournisseur de cloud si la machine virtuelle de ce nœud est toujours disponible. Sinon, le contrôleur de nœud supprime le nœud de sa liste de nœuds.

La troisième est la surveillance de la santé des nœuds. Le contrôleur de noeud est responsable de la mise à jour de la condition NodeReady de NodeStatus vers ConditionUnknown lorsqu'un noeud devient inaccessible (le contrôleur de noeud cesse de recevoir des heartbeats pour une raison quelconque, par exemple en raison d'une panne du noeud), puis de l'éviction ultérieure de tous les pods du noeud. (en utilisant une terminaison propre) si le nœud continue d’être inaccessible. (Les délais d'attente par défaut sont de 40 secondes pour commencer à signaler ConditionUnknown et de 5 minutes après cela pour commencer à expulser les pods.)

Le contrôleur de nœud vérifie l'état de chaque nœud toutes les --node-monitor-period secondes.

Dans les versions de Kubernetes antérieures à 1.13, NodeStatus correspond au heartbeat du nœud. À partir de Kubernetes 1.13, la fonctionnalité de bail de nœud (node lease en anglais) est introduite en tant que fonctionnalité alpha (feature gate NodeLease, KEP-0009). Lorsque la fonction de node lease est activée, chaque noeud a un objet Lease associé dans le namespace kube-node-lease qui est renouvelé périodiquement par le noeud, et NodeStatus et le node lease sont traités comme des heartbeat du noeud. Les node leases sont renouvelés fréquemment lorsque NodeStatus est signalé de nœud à master uniquement lorsque des modifications ont été apportées ou que suffisamment de temps s'est écoulé (la valeur par défaut est 1 minute, ce qui est plus long que le délai par défaut de 40 secondes pour les nœuds inaccessibles). Étant donné qu'un node lease est beaucoup plus léger qu'un NodeStatus, cette fonctionnalité rends le heartbeat d'un nœud nettement moins coûteux, tant du point de vue de l'évolutivité que des performances.

Dans Kubernetes 1.4, nous avons mis à jour la logique du contrôleur de noeud afin de mieux gérer les cas où un grand nombre de noeuds rencontrent des difficultés pour atteindre le master (par exemple parce que le master a un problème de réseau). À partir de la version 1.4, le contrôleur de noeud examine l’état de tous les noeuds du cluster lorsqu’il prend une décision concernant l’éviction des pods.

Dans la plupart des cas, le contrôleur de noeud limite le taux d’expulsion à --node-eviction-rate (0,1 par défaut) par seconde, ce qui signifie qu’il n’expulsera pas les pods de plus d’un nœud toutes les 10 secondes.

Le comportement d'éviction de noeud change lorsqu'un noeud d'une zone de disponibilité donnée devient défaillant. Le contrôleur de nœud vérifie quel pourcentage de nœuds de la zone est défaillant (la condition NodeReady est ConditionUnknown ou ConditionFalse) en même temps. Si la fraction de nœuds défaillant est au moins --unhealthy-zone-threshold (valeur par défaut de 0,55), le taux d'expulsion est réduit: si le cluster est petit (c'est-à-dire inférieur ou égal à --large-cluster-size-threshold noeuds - valeur par défaut 50) puis les expulsions sont arrêtées, sinon le taux d'expulsion est réduit à --secondary-node-eviction-rate (valeur par défaut de 0,01) par seconde.

Ces stratégies sont implémentées par zone de disponibilité car une zone de disponibilité peut être partitionnée à partir du master, tandis que les autres restent connectées. Si votre cluster ne s'étend pas sur plusieurs zones de disponibilité de fournisseur de cloud, il n'existe qu'une seule zone de disponibilité (la totalité du cluster).

L'une des principales raisons de la répartition de vos nœuds entre les zones de disponibilité est de pouvoir déplacer la charge de travail vers des zones saines lorsqu'une zone entière tombe en panne. Par conséquent, si tous les nœuds d’une zone sont défaillants, le contrôleur de nœud expulse à la vitesse normale --node-eviction-rate. Le cas pathologique se produit lorsque toutes les zones sont complètement défaillantes (c'est-à-dire qu'il n'y a pas de nœuds sains dans le cluster). Dans ce cas, le contrôleur de noeud suppose qu'il existe un problème de connectivité au master et arrête toutes les expulsions jusqu'à ce que la connectivité soit restaurée.

À partir de Kubernetes 1.6, NodeController est également responsable de l'expulsion des pods s'exécutant sur des noeuds avec des marques NoExecute, lorsque les pods ne tolèrent pas ces marques. De plus, en tant que fonctionnalité alpha désactivée par défaut, NodeController est responsable de l'ajout de marques correspondant aux problèmes de noeud tels que les noeuds inaccessibles ou non prêts. Voir cette documentation pour plus de détails sur les marques NoExecute et cette fonctionnalité alpha.

À partir de la version 1.8, le contrôleur de noeud peut être chargé de créer des tâches représentant les conditions de noeud. Ceci est une fonctionnalité alpha de la version 1.8.

Auto-enregistrement des nœuds

Lorsque l'indicateur de kubelet --register-node est à true (valeur par défaut), le kubelet tente de s'enregistrer auprès du serveur d'API. C'est le modèle préféré, utilisé par la plupart des distributions Linux.

Pour l'auto-enregistrement (self-registration en anglais), le kubelet est lancé avec les options suivantes:

  • --kubeconfig - Chemin d'accès aux informations d'identification pour s'authentifier auprès de l'apiserver.
  • --cloud-provider - Comment lire les métadonnées d'un fournisseur de cloud sur lui-même.
  • --register-node - Enregistrement automatique avec le serveur API.
  • --register-with-taints - Enregistrez le noeud avec la liste donnée de marques (séparés par des virgules <key>=<value>:<effect>). Sans effet si register-node est à false.
  • --node-ip - Adresse IP du noeud.
  • --node-labels - Labels à ajouter lors de l’enregistrement du noeud dans le cluster (voir Restrictions des labels appliquées par le plugin NodeRestriction admission dans les versions 1.13+).
  • --node-status-update-frequency - Spécifie la fréquence à laquelle kubelet publie le statut de nœud sur master.

Quand le mode autorisation de nœud et plugin NodeRestriction admission sont activés, les kubelets sont uniquement autorisés à créer / modifier leur propre ressource de noeud.

Administration manuelle de noeuds

Un administrateur de cluster peut créer et modifier des objets de nœud.

Si l'administrateur souhaite créer des objets de noeud manuellement, définissez l'argument de kubelet: --register-node=false.

L'administrateur peut modifier les ressources du nœud (quel que soit le réglage de --register-node). Les modifications comprennent la définition de labels sur le nœud et son marquage comme non programmable.

Les étiquettes sur les nœuds peuvent être utilisées avec les sélecteurs de nœuds sur les pods pour contrôler la planification. Par exemple, pour contraindre un pod à ne pouvoir s'exécuter que sur un sous-ensemble de nœuds.

Marquer un nœud comme non planifiable empêche la planification de nouveaux pods sur ce nœud, mais n'affecte pas les pods existants sur le nœud. Ceci est utile comme étape préparatoire avant le redémarrage d'un nœud, etc. Par exemple, pour marquer un nœud comme non programmable, exécutez la commande suivante:

kubectl cordon $NODENAME

Capacité de nœud

La capacité du nœud (nombre de CPU et quantité de mémoire) fait partie de l’objet Node. Normalement, les nœuds s'enregistrent et indiquent leur capacité lors de la création de l'objet Node. Si vous faites une administration manuelle de nœud, alors vous devez définir la capacité du nœud lors de l'ajout d'un nœud.

Le scheduler Kubernetes veille à ce qu'il y ait suffisamment de ressources pour tous les pods d'un noeud. Il vérifie que la somme des demandes des conteneurs sur le nœud n'est pas supérieure à la capacité du nœud. Cela inclut tous les conteneurs lancés par le kubelet, mais pas les conteneurs lancés directement par le conteneur runtime, ni aucun processus exécuté en dehors des conteneurs.

Si vous souhaitez réserver explicitement des ressources pour des processus autres que Pod, suivez ce tutoriel pour: réserver des ressources pour les démons système.

API Object

L'objet Node est une ressource de niveau supérieur dans l'API REST de Kubernetes. Plus de détails sur l'objet API peuvent être trouvés à l'adresse suivante: Node API object.

2.2 - Communication Master-Node

Communication Master-Node Kubernetes

Ce document répertorie les canaux de communication entre l'API du noeud maître (apiserver of master node en anglais) et le reste du cluster Kubernetes. L'objectif est de permettre aux utilisateurs de personnaliser leur installation afin de sécuriser la configuration réseau, de sorte que le cluster puisse être exécuté sur un réseau non approuvé (ou sur des adresses IP entièrement publiques d'un fournisseur de cloud).

Communication du Cluster vers le Master

Tous les canaux de communication du cluster au master se terminent à l'apiserver (aucun des autres composants principaux n'est conçu pour exposer des services distants). Dans un déploiement typique, l'apiserver est configuré pour écouter les connexions distantes sur un port HTTPS sécurisé (443) avec un ou plusieurs types d'authentification client. Une ou plusieurs formes d'autorisation devraient être activées, notamment si les requêtes anonymes ou jeton de compte de service sont autorisés.

Le certificat racine public du cluster doit être configuré pour que les nœuds puissent se connecter en toute sécurité à l'apiserver avec des informations d'identification client valides. Par exemple, dans un déploiement GKE par défaut, les informations d'identification client fournies au kubelet sont sous la forme d'un certificat client. Consultez amorçage TLS de kubelet pour le provisioning automatisé des certificats de client Kubelet.

Les pods qui souhaitent se connecter à l'apiserver peuvent le faire de manière sécurisée en utilisant un compte de service afin que Kubernetes injecte automatiquement le certificat racine public et un jeton de support valide dans le pod lorsqu'il est instancié. Le service kubernetes (dans tous les namespaces) est configuré avec une adresse IP virtuelle redirigée (via kube-proxy) vers le point de terminaison HTTPS sur l'apiserver.

Les composants du master communiquent également avec l'apiserver du cluster via le port sécurisé.

Par conséquent, le mode de fonctionnement par défaut pour les connexions du cluster (nœuds et pods s'exécutant sur les nœuds) au master est sécurisé par défaut et peut s'exécuter sur des réseaux non sécurisés et/ou publics.

Communication du Master vers le Cluster

Il existe deux voies de communication principales du master (apiserver) au cluster. La première est du processus apiserver au processus kubelet qui s'exécute sur chaque nœud du cluster. La seconde part de l'apiserver vers n'importe quel nœud, pod ou service via la fonctionnalité proxy de l'apiserver.

Communication de l'apiserver vers le kubelet

Les connexions de l'apiserver au kubelet sont utilisées pour:

  • Récupérer les logs des pods.
  • S'attacher (via kubectl) à des pods en cours d'exécution.
  • Fournir la fonctionnalité de transfert de port du kubelet.

Ces connexions se terminent au point de terminaison HTTPS du kubelet. Par défaut, l'apiserver ne vérifie pas le certificat du kubelet, ce qui rend la connexion sujette aux attaques de type "man-in-the-middle", et non sûre sur des réseaux non approuvés et/ou publics.

Pour vérifier cette connexion, utilisez l'argument --kubelet-certificate-authority pour fournir à l'apiserver un ensemble de certificats racine à utiliser pour vérifier le certificat du kubelet.

Si ce n'est pas possible, utilisez SSH tunneling entre l'apiserver et le kubelet si nécessaire pour éviter la connexion sur un réseau non sécurisé ou public.

Finalement, l'authentification et/ou autorisation du Kubelet devrait être activée pour sécuriser l'API kubelet.

apiserver vers nodes, pods et services

Les connexions de l'apiserver à un nœud, à un pod ou à un service sont définies par défaut en connexions HTTP. Elles ne sont donc ni authentifiées ni chiffrées. Elles peuvent être exécutées sur une connexion HTTPS sécurisée en préfixant https: au nom du nœud, du pod ou du service dans l'URL de l'API. Cependant ils ne valideront pas le certificat fourni par le point de terminaison HTTPS ni ne fourniront les informations d'identification du client. De plus, aucune garantie d'intégrité n'est fournie. Ces connexions ne sont actuellement pas sûres pour fonctionner sur des réseaux non sécurisés et/ou publics.

SSH Tunnels

Kubernetes prend en charge les tunnels SSH pour protéger les communications master -> cluster. Dans cette configuration, l'apiserver initie un tunnel SSH vers chaque nœud du cluster (en se connectant au serveur ssh sur le port 22) et transmet tout le trafic destiné à un kubelet, un nœud, un pod ou un service via un tunnel. Ce tunnel garantit que le trafic n'est pas exposé en dehors du réseau dans lequel les nœuds sont en cours d'exécution.

Les tunnels SSH étant actuellement obsolètes, vous ne devriez pas choisir de les utiliser à moins de savoir ce que vous faites. Un remplacement pour ce canal de communication est en cours de conception.

2.3 - Concepts sous-jacents au Cloud Controller Manager

Le concept de cloud controller manager (CCM) (ne pas confondre avec le binaire) a été créé à l'origine pour permettre au code de fournisseur spécifique de cloud et au noyau Kubernetes d'évoluer indépendamment les uns des autres. Le gestionnaire de contrôleur de cloud fonctionne aux côtés d'autres composants principaux, tels que le gestionnaire de contrôleur Kubernetes, le serveur d'API et le planificateur. Il peut également être démarré en tant qu’addon Kubernetes, auquel cas il s’exécute sur Kubernetes.

La conception du gestionnaire de contrôleur de cloud repose sur un mécanisme de plugin qui permet aux nouveaux fournisseurs de cloud de s'intégrer facilement à Kubernetes à l'aide de plugins. Des plans sont en place pour intégrer de nouveaux fournisseurs de cloud sur Kubernetes et pour migrer les fournisseurs de cloud de l'ancien modèle vers le nouveau modèle CCM.

Ce document discute des concepts derrière le cloud controller manager et donne des détails sur ses fonctions associées.

Voici l'architecture d'un cluster Kubernetes sans le cloud controller manager:

Pre CCM Kube Arch

Conception

Dans le diagramme précédent, Kubernetes et le fournisseur de cloud sont intégrés via plusieurs composants différents:

  • Kubelet
  • Kubernetes controller manager
  • Kubernetes API server

Le CCM consolide toute la logique dépendante du cloud des trois composants précédents pour créer un point d’intégration unique avec le cloud. La nouvelle architecture avec le CCM se présente comme suit:

CCM Kube Arch

Composants du CCM

Le CCM rompt certaines fonctionnalités du Kubernetes Controller Manager (KCM) et les exécute en tant que processus séparé. Plus précisément, il sépare les contrôleurs du KCM qui dépendent du cloud. Le KCM comporte les boucles de contrôle dépendant du cloud suivantes:

  • Contrôleur de nœud
  • Contrôleur de volume
  • Contrôleur de route
  • Contrôleur de service

Dans la version 1.9, le CCM exécute les contrôleurs suivants de la liste précédente:

  • Contrôleur de nœud
  • Contrôleur de route
  • Contrôleur de service

Le plan initial de prise en charge des volumes à l'aide de CCM consistait à utiliser des volumes Flex pour prendre en charge des volumes pouvant être connectés. Cependant, un effort concurrentiel appelé CSI est prévu pour remplacer Flex.

Compte tenu de cette dynamique, nous avons décidé d'avoir une mesure de transition intermédiaire jusqu'à ce que le CSI soit prêt.

Fonctions du CCM

Le CCM hérite ses fonctions des composants de Kubernetes qui dépendent d'un fournisseur de cloud. Cette section est structurée en fonction de ces composants.

1. Kubernetes controller manager

La majorité des fonctions du CCM sont dérivées du KCM. Comme indiqué dans la section précédente, le CCM exécute les boucles de contrôle suivantes:

  • Contrôleur de nœud
  • Contrôleur de route
  • Contrôleur de service

Contrôleur de nœud

Le contrôleur de noeud est responsable de l'initialisation d'un noeud en obtenant des informations sur les noeuds s'exécutant dans le cluster auprès du fournisseur de cloud. Le contrôleur de noeud exécute les fonctions suivantes:

  1. Il initialise le nœud avec des labels de zone/région spécifiques au cloud.
  2. Il initialise le nœud avec des détails d'instance spécifiques au cloud, tels que le type et la taille de l'instance.
  3. Il obtient les adresses réseau et le nom d'hôte du nœud.
  4. Si un nœud ne répond plus, le controlleur vérifie avec le cloud pour voir s'il a été supprimé du cloud. Si le nœud a été supprimé du cloud, le controlleur supprime l'objet Kubernetes Node.

Contrôleur de route

Le contrôleur de route est responsable de la configuration appropriée des itinéraires dans le cloud afin que les conteneurs situés sur différents nœuds du cluster Kubernetes puissent communiquer entre eux. Le contrôleur de route ne s'applique qu'aux clusters Google Compute Engine.

Contrôleur de service

Le contrôleur de service est chargé d'écouter les événements de création, de mise à jour et de suppression de service. En fonction de l'état actuel des services dans Kubernetes, il configure les équilibreurs de charge dans le cloud (tels que ELB, Google LB ou Oracle Cloud Infrastructure LB) pour refléter l'état des services dans Kubernetes. De plus, cela garantit que les services de base des services pour les load balancers dans le cloud sont à jour.

2. Kubelet

Le contrôleur de noeud contient les fonctionnalités du kubelet dépendant du cloud. Avant l'introduction du CCM, la sous-unité était responsable de l'initialisation d'un nœud avec des détails spécifiques au cloud, tels que les adresses IP, les étiquettes de région / zone et les informations de type d'instance. L’introduction du CCM a déplacé cette opération d’initialisation du kubelet vers le CCM.

Dans ce nouveau modèle, le kubelet initialise un nœud sans informations spécifiques au cloud. Cependant, il ajoute un marquage au nœud nouvellement créé, qui rend le nœud non planifiable jusqu'à ce que le CCM initialise le nœud avec des informations spécifiques au cloud. Il supprime ensuite ce marquage.

Mécanisme de plugin

Le cloud controller manager utilise des interfaces Go pour autoriser la mise en œuvre d'implémentations depuis n'importe quel cloud. Plus précisément, il utilise l'interface CloudProvider définie ici.

La mise en œuvre des quatre contrôleurs partagés soulignés ci-dessus, ainsi que certaines configurations ainsi que l'interface partagée du fournisseur de cloud, resteront dans le noyau Kubernetes. Les implémentations spécifiques aux fournisseurs de cloud seront construites en dehors du noyau et implémenteront les interfaces définies dans le noyau.

Pour plus d’informations sur le développement de plugins, consultez Developing Cloud Controller Manager.

Autorisation

Cette section détaille les accès requis par le CCM sur divers objets API pour effectuer ses opérations.

Contrôleur de nœud

Le contrôleur de noeud ne fonctionne qu'avec les objets de noeud. Il nécessite un accès complet aux objets Node via get, list, create, update, patch, watch et delete.

v1/Node:

  • Get
  • List
  • Create
  • Update
  • Patch
  • Watch
  • Delete

Contrôleur de route

Le contrôleur de route écoute les évenements de création d'objet Node et configure les routes de manière appropriée. Cela nécessite un accès get aux objets Node.

v1/Node:

  • Get

Contrôleur de Service

Le contrôleur de service écoute les évenements de création, suppression et mises à jour des objets Service et configure les endpoints pour ces Services de manière appropriée.

Pour accéder aux Services, il faut les accès list et watch. Pour mettre à jour les Services, il faut les accès patch et update.

Pour configurer des points de terminaison pour les services, vous devez avoir accès au create, list, get, watch, et update.

v1/Service:

  • List
  • Get
  • Watch
  • Patch
  • Update

Autres

La mise en œuvre du CCM nécessite un accès pour créer des événements, et pour garantir un fonctionnement sécurisé, un accès est nécessaire pour créer ServiceAccounts.

v1/Event:

  • Create
  • Patch
  • Update

v1/ServiceAccount:

  • Create

Le ClusterRole RBAC pour le CCM ressemble à ceci:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cloud-controller-manager
rules:
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
  - update
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - list
  - patch
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - serviceaccounts
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - persistentvolumes
  verbs:
  - get
  - list
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - endpoints
  verbs:
  - create
  - get
  - list
  - watch
  - update

Implémentations des fournisseurs de cloud

Les fournisseurs de cloud suivants ont implémenté leur CCM:

Administration de cluster

Des instructions complètes pour la configuration et l'exécution du CCM sont fournies ici.

3 - Les conteneurs

Conteneurs Kubernetes

3.1 - Images

Images conteneur Kubernetes

Vous créez une image Docker et la poussez dans un registre avant de la référencer depuis un pod Kubernetes.

La propriété image d'un conteneur utilise la même syntaxe que la commande docker, y compris pour les registres privés et les tags.

Mettre à jour des images

La politique de récupération par défaut est IfNotPresent, Kubelet ne récupère alors pas une image si elle est déjà présente sur le nœud. Si vous voulez forcer une récupération à chaque fois, vous pouvez faire une des actions suivantes :

  • définissez imagePullPolicy du conteneur à Always.
  • omettez imagePullPolicy et utilisez :latest comme tag pour l'image à utiliser.
  • omettez imagePullPolicy et le tag de l'image à utiliser.
  • activez l'admission controller AlwaysPullImages.

Notez que vous devez éviter d'utiliser le tag :latest, voir Bonnes pratiques pour la configuration pour plus d'informations.

Créer des images multi-architecture à partir de manifestes

La CLI Docker prend maintenant en charge la commande docker manifest avec des sous-commandes comme create, annotate et push. Ces commandes peuvent être utilisées pour construire et pousser les manifestes. Vous pouvez utiliser docker manifest inspect pour voir le manifeste.

Vous pouvez voir la documentation Docker ici : https://docs.docker.com/edge/engine/reference/commandline/manifest/

Voici comment nous l'utilisons dans notre outil de build: https://cs.k8s.io/?q=docker%20manifest%20(create%7Cpush%7Cannotate)&i=nope&files=&repos=

Ces commandes se basent et sont implémentées purement sur la CLI Docker. Vous devrez soit éditer $HOME/.docker/config.json et définir la clé experimental à enabled ou vous pouvez simplement définir la variable d'environnement DOCKER_CLI_EXPERIMENTAL à enabled lorsque vous appelez les commandes de la CLI.

Si vous avez des problèmes en téléchargeant des manifestes viciés, nettoyez les anciens manifestes dans $HOME/.docker/manifests pour recommencer de zéro.

Pour Kubernetes, nous avons historiquement utilisé des images avec des suffixes -$(ARCH). Pour une rétrocompatibilité, veuillez générer les anciennes images avec des suffixes. Par exemple, l'image pause qui a le manifeste pour toutes les architetures et l'image pause-amd64 qui est rétrocompatible pour d'anciennes configurations ou des fichiers YAML qui auraient codé en dur les images avec des suffixes.

Utiliser un registre privé

Les registres privés peuvent demander des clés pour pouvoir lire leurs images.

Ces certificats peuvent être fournis de différentes manières :

  • En utilisant la Google Container Registry
    • par cluster
    • automatiqueent configuré dans Google Compute Engine ou Google Kubernetes Engine
    • tous les pods peuvent lire le registre privé du projet
  • En utilisant Amazon Elastic Container Registry (ECR)
    • utilise les rôles et politiques IAM pour contrôler l'accès aux dépôts ECR
    • rafraîchit automatiquement les certificats de login ECR
  • En utilisant Oracle Cloud Infrastructure Registry (OCIR)
    • utilise les rôles et politiques IAM pour contrôler l'accès aux dépôts OCIR
  • En utilisant Azure Container Registry (ACR)
  • En utilisant IBM Cloud Container Registry
    • utilise les rôles et politiques IAM pour contrôler l'accès à l'IBM Cloud Container Registry
  • En configurant les nœuds pour s'authentifier auprès d'un registre privé
    • tous les pods peuvent lire les registres privés configurés
    • nécessite la configuration des nœuds par un administrateur du cluster
  • En utilisant des images pré-chargées
    • tous les pods peuvent utiliser toutes les images mises en cache sur un nœud
    • nécessite l'accès root à tous les nœuds pour la mise en place
  • En spécifiant ImagePullSecrets dans un Pod
    • seuls les pods fournissant ses propres clés peuvent accéder au registre privé

Chaque option est décrite plus en détails ci-dessous.

Utiliser la Google Container Registry

Kubernetes prend en charge nativement la Google Container Registry (GCR), lorsqu'il s'exécute dans Google Compute Engine (GCE). Si vous exécutez votre cluster dans GCE ou Google Kubernetes Engine, utilisez simplement le nom complet de l'image (par ex. gcr.io/my_project/image:tag).

Tous les pods dans un cluster auront un accès en lecture aux images dans le registre.

Kubelet va s'authentifier auprès de GCR en utilisant le compte de service Google de l'instance. Le compte de service dans l'instance aura un https://www.googleapis.com/auth/devstorage.read_only, afin qu'il puisse récupérer depuis le GCR du projet mais qu'il ne puisse pas pousser une image.

Utiliser Amazon Elastic Container Registry

Kubernetes prend en charge nativement Amazon Elastic Container Registry, lorsque les nœuds sont des instances de AWS EC2.

Utilisez simplement le nom complet de l'image (par ex. ACCOUNT.dkr.ecr.REGION.amazonaws.com/imagename:tag) dans la définition du Pod.

Tous les utilisateurs du cluster qui peuvent créer des pods auront la possibilité d'exécuter des pods qui utilisent n'importe quelle image du registre ECR.

Kubelet va aller chercher et rafraîchir périodiquement les certificats ECR. Les permissions suivantes sont requises par kubelet :

  • ecr:GetAuthorizationToken
  • ecr:BatchCheckLayerAvailability
  • ecr:GetDownloadUrlForLayer
  • ecr:GetRepositoryPolicy
  • ecr:DescribeRepositories
  • ecr:ListImages
  • ecr:BatchGetImage

Exigences :

  • Vous devez utiliser kubelet version v1.2.0 ou ultérieure. (exécutez par ex. /usr/bin/kubelet --version=true).
  • Si vos nœuds sont dans une région différente de votre registre, vous devez utiliser la version v1.3.0 ou ultérieure.
  • ECR doit être disponible dans votre région.

Dépannage :

  • Vérifiez toutes les exigences ci-dessus.
  • Copiez les certificats de $REGION (par ex. us-west-2) sur votre poste de travail. Connectez-vous en SSH sur l'hôte et exécutez Docker manuellement avec ces certificats. Est-ce que ça marche ?
  • Vérifiez que kubelet s'exécute avec --cloud-provider=aws.
  • Augmentez la verbosité des logs de kubelet à au moins 3 et recherchez dans les logs de kubelet (par exemple avec journalctl -u kubelet) des lignes similaires à :
    • aws_credentials.go:109] unable to get ECR credentials from cache, checking ECR API
    • aws_credentials.go:116] Got ECR credentials from ECR API for <AWS account ID for ECR>.dkr.ecr.<AWS region>.amazonaws.com

Utiliser Azure Container Registry (ACR)

En utilisant Azure Container Registry vous pouvez vous authentifier en utilisant soit un utilisateur admin soit un service principal. Dans les deux cas, l'authentification est faite via l'authentification standard de Docker. Ces instructions assument l'outil en ligne de commande azure-cli.

Vous devez d'abord créer un registre et générer des certificats, la documentation complète pour cela peut être touvée dans la documentation de Azure container registry.

Une fois votre registre de conteneurs créé, vous utiliserez les certificats suivants pour vous connecter :

  • DOCKER_USER : service principal ou utilisateur admin
  • DOCKER_PASSWORD: mot de passe du service principal ou utilisateur admin
  • DOCKER_REGISTRY_SERVER: ${un-nom-de-registre}.azurecr.io
  • DOCKER_EMAIL: ${une-adresse-email}

Une fois que vous avez défini ces variables, vous pouvez configurer un Secret Kubernetes et l'utiliser pour déployer un Pod.

Utiliser IBM Cloud Container Registry

IBM Cloud Container Registry fournit un registre d'images multi-tenant privé que vous pouvez utiliser pour stocker et partager de manière sécurisée vos images. Par défaut, les images de votre registre privé sont scannées par le Vulnerability Advisor intégré pour détecter des failles de sécurité et des vulnérabilités potentielles. Les utilisateurs de votre compte IBM Cloud peuvent accéder à vos images, ou vous pouvez des rôles et politiques IAM pour fournir l'accès aux namespaces de l'IBM Cloud Container Registry.

Pour installer le plugin du CLI de IBM Cloud Container Registry et créer un namespace pour vos images, voir Débuter avec IBM Cloud Container Registry.

Si vous utilisez le même compte et la même région, vous pouvez déployer des images stockées dans IBM Cloud Container Registry vers la namespace default de votre cluster IBM Cloud Kubernetes Service sans configuration supplémentaire, voir Construire des conteneurs à partir d'images. Pour les autres options de configuration, voir Comprendre comment autoriser votre cluster à télécharger des images depuis un registre.

Configurer les nœuds pour s'authentifier auprès d'un registre privé

Docker stocke les clés pour les regisres privés dans le fichier $HOME/.dockercfg ou $HOME/.docker/config.json. Si vous placez le même fichier dans un des chemins de recherche ci-dessous, kubelet l'utilise comme fournisseur de clés lorsque les images sont récupérées.

  • {--root-dir:-/var/lib/kubelet}/config.json
  • {cwd of kubelet}/config.json
  • ${HOME}/.docker/config.json
  • /.docker/config.json
  • {--root-dir:-/var/lib/kubelet}/.dockercfg
  • {cwd of kubelet}/.dockercfg
  • ${HOME}/.dockercfg
  • /.dockercfg

Voici les étapes recommandées pour configurer vos nœuds pour qu'ils utilisent un registre privé. Dans cet exemple, exécutez-les sur votre poste de travail :

  1. Exécutez docker login [server] pour chaque jeu de certificats que vous désirez utiliser. Ceci met à jour $HOME/.docker/config.json.
  2. Examinez $HOME/.docker/config.json dans un éditeur pour vous assurer qu'il contient uniquement les certificats que vous désirez utiliser.
  3. Récupérez la liste de vos nœuds, par exemple :
    • si vous voulez connaître les noms : nodes=$(kubectl get nodes -o jsonpath='{range.items[*].metadata}{.name} {end}')
    • si vous voulez connaître les IPs : nodes=$(kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}')
  4. Copiez votre fichier .docker/config.json local dans un des chemins de recherche ci-dessus.
    • par exemple : for n in $nodes; do scp ~/.docker/config.json root@$n:/var/lib/kubelet/config.json; done

Vérifiez en créant un pod utilisant une image privée, par ex. :

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: test-image-privee-1
spec:
  containers:
    - name: utilise-image-privee
      image: $NOM_IMAGE_PRIVEE
      imagePullPolicy: Always
      command: [ "echo", "SUCCESS" ]
EOF
pod/test-image-privee-1 created

Si tout fonctionne, alors, après quelques instants, vous pouvez exécuter :

kubectl logs test-image-privee-1

et voir que la commande affiche :

SUCCESS

Si vous suspectez que la commande a échouée, vous pouvez exécuter :

kubectl describe pods/test-image-privee-1 | grep 'Failed'

En cas d'échec, l'affichage sera similaire à :

  Fri, 26 Jun 2015 15:36:13 -0700    Fri, 26 Jun 2015 15:39:13 -0700    19    {kubelet node-i2hq}    spec.containers{uses-private-image}    failed        Failed to pull image "user/privaterepo:v1": Error: image user/privaterepo:v1 not found

Vous devez vous assurer que tous les nœuds du cluster ont le même fichier .docker/config.json. Dans le cas contraire, les pods vont s'exécuter sur certains nœuds et échouer sur d'autres. Par exemple, si vous utilisez l'autoscaling des nœuds, alors chaque modèle d'instance doit inclure le fichier .docker/config.json ou monter un disque le contenant.

Tous les pods auront un accès en lecture aux images d'un registre privé dès que les clés du registre privé sont ajoutées au fichier .docker/config.json.

Images pré-chargées

Par défaut, kubelet essaiera de récupérer chaque image depuis le registre spécifié. Cependant, si la propriété imagePullPolicy du conteneur est IfNotPresent ou Never, alors une image locale est utilisée (respectivement de préférence ou exclusivement).

Si vous désirez vous reposer sur des images pré-chargées pour éviter l'authentification à un registre, vous devez vous assurer que tous les nœuds du cluster ont les mêmes images pré-chargées.

Ceci peut être utilisé pour pré-charger certaines images pour gagner du temps, ou comme une alternative à l'authentification à un registre privé.

Tous les pods auront un accès en lecture aux images pré-chargées.

Spécifier ImagePullSecrets dans un Pod

Kubernetes permet de spécifier des clés de registre dans un pod.

Créer un Secret avec une config Docker

Exécutez la commande suivante, en substituant les valeurs en majuscule :

kubectl create secret docker-registry <name> --docker-server=SERVEUR_REGISTRE_DOCKER --docker-username=UTILISATEUR_DOCKER --docker-password=MOT_DE_PASSE_DOCKER --docker-email=EMAIL_DOCKER
secret/myregistrykey created.

Si vous avez déjà un fichier de clés Docker, alors, plutôt que d'utiliser la commande ci-dessus, vous pouvez importer le fichier de clés comme un Secret Kubernetes. Créer un Secret basé sur des clés Docker existantes explique comment s'y prendre. Ceci est particulièrement utile si vous utilisez plusieurs registres privés, kubectl create secret docker-registry créant un Secret ne fonctionnant qu'avec un seul registre privé.

Se référer à un imagePullSecrets dans un Pod

Vous pouvez maintenant créer des pods qui référencent ce secret en ajoutant une section imagePullSecrets dans la définition du pod.

cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: foo
  namespace: awesomeapps
spec:
  containers:
    - name: foo
      image: janedoe/awesomeapp:v1
  imagePullSecrets:
    - name: myregistrykey
EOF

cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF

Ceci doit être fait pour chaque pod utilisant un registre privé.

Cependant, la définition de ce champ peut être automatisé en définissant imagePullSecrets dans une ressource serviceAccount. Voyez Ajouter un ImagePullSecrets à un Service Account pour des instructions détaillées.

Vous pouvez utiliser cette méthode en conjonction avec un .docker/config.json par nœud. Les certificats seront alors regroupés. Cette approche fonctionnera dans Google Kubernetes Engine.

Cas d'utilisation

Il y a plusieurs solutions pour configurer des registres privés. Voici quelques cas d'utilisation classiques et des propositions de solutions.

  1. Cluster exécutant uniquement des images non propriétaires (par ex. open-source). Inutile de protéger les images.
    • Utilisez des images publiques dans le Hub Docker.
      • Pas de configuration requise.
      • Dans GCE/Google Kubernetes Engine, un miroir local est automatiquement utilisé pour améliorer la vitesse et la disponibilité.
  2. Cluster exécutant quelques images propriétaires qui doivent être protégées de l'extérieur de l'entreprise, mais visibles pour tous les utilisteurs du cluster.
    • Utilisez un registre Docker hébergé privé.
      • Il peut être hébergé sur le Hub Docker, ou ailleurs.
      • Configurez manuellement .docker/config.json sur caque nœud comme décrit ci-dessus.
    • Ou, utilisez un registre privé interne derrière votre pare-feu avec un accès ouvert en lecture.
      • Aucune configuration Kubernetes n'est nécessaire.
    • Ou, dans GCE/Google Kubernetes Engine, utilisez le Google Container Registry du projet.
      • Cela fonctionnera mieux pour l'autoscaling du cluster que la configuration manuelle des nœuds.
    • Ou, dans un cluster où le changement de la configuration des nœuds est difficile, utilisez imagePullSecrets.
  3. Cluster avec des images propriétaires, dont quelques-unes nécessitent un contrôle d'accès plus strict.
    • Assurez-vous que l'admission controller AlwaysPullImages est actif. Autrement, tous les Pods ont potentiellement accès à toutes les images.
    • Déplacez les données sensibles dans une ressource "Secret", plutôt que de les intégrer dans une image.
  4. Un cluster multi-tenant où chaque tenant doit avoir son propre registre privé.
    • Assurez-vous que l'admission controller AlwaysPullImages est actif. Autrement, tous les Pods ont potentiellement accès à toutes les images.
    • Utilisez un registre privé nécessitant l'autorisation.
    • Générez des certificats de registre pour chaque tenant, placez-les dans des secrets, et placez ces secrets dans les namespaces de chaque tenant. pod - Le tenant ajoute ce secret dans les imagePullSecrets de chaque pod.

Si vous devez accéder à plusieurs registres, vous pouvez créer un secret pour chaque registre. Kubelet va fusionner tous les imagePullSecrets dans un unique .docker/config.json virtuel.

3.2 - Classe d'exécution (Runtime Class)

Classe d'execution conteneur pour Kubernetes
FEATURE STATE: Kubernetes v1.12 [alpha]

Cette page décrit la ressource RuntimeClass et le mécanisme de sélection d'exécution (runtime).

Runtime Class

La RuntimeClass est une fonctionnalité alpha permettant de sélectionner la configuration d'exécution du conteneur à utiliser pour exécuter les conteneurs d'un pod.

Installation

En tant que nouvelle fonctionnalité alpha, certaines étapes de configuration supplémentaires doivent être suivies pour utiliser la RuntimeClass:

  1. Activer la fonctionnalité RuntimeClass (sur les apiservers et les kubelets, nécessite la version 1.12+)
  2. Installer la RuntimeClass CRD
  3. Configurer l'implémentation CRI sur les nœuds (dépend du runtime)
  4. Créer les ressources RuntimeClass correspondantes

1. Activer RuntimeClass feature gate (portail de fonctionnalité)

Voir Feature Gates pour une explication sur l'activation des feature gates. La RuntimeClass feature gate doit être activée sur les API servers et les kubelets.

2. Installer la CRD RuntimeClass

La RuntimeClass CustomResourceDefinition (CRD) se trouve dans le répertoire addons du dépôt Git Kubernetes: kubernetes/cluster/addons/runtimeclass/runtimeclass_crd.yaml

Installer la CRD avec kubectl apply -f runtimeclass_crd.yaml.

3. Configurer l'implémentation CRI sur les nœuds

Les configurations à sélectionner avec RuntimeClass dépendent de l'implémentation CRI. Consultez la documentation correspondante pour votre implémentation CRI pour savoir comment le configurer. Comme c'est une fonctionnalité alpha, tous les CRI ne prennent pas encore en charge plusieurs RuntimeClasses.

Les configurations ont un nom RuntimeHandler correspondant , référencé par la RuntimeClass. Le RuntimeHandler doit être un sous-domaine DNS valide selon la norme RFC 1123 (alphanumériques + - et . caractères).

4. Créer les ressources RuntimeClass correspondantes

Les configurations effectuées à l'étape 3 doivent chacune avoir un nom RuntimeHandler associé, qui identifie la configuration. Pour chaque RuntimeHandler (et optionellement les handlers vides ""), créez un objet RuntimeClass correspondant.

La ressource RuntimeClass ne contient actuellement que 2 champs significatifs: le nom RuntimeClass (metadata.name) et le RuntimeHandler (spec.runtimeHandler). la définition de l'objet ressemble à ceci:

apiVersion: node.k8s.io/v1alpha1  # La RuntimeClass est définie dans le groupe d'API node.k8s.io
kind: RuntimeClass
metadata:
  name: myclass  # Le nom avec lequel la RuntimeClass sera référencée
  # La RuntimeClass est une ressource non cantonnées à un namespace
spec:
  runtimeHandler: myconfiguration  # Le nom de la configuration CRI correspondante

Usage

Une fois que les RuntimeClasses sont configurées pour le cluster, leur utilisation est très simple. Spécifiez runtimeClassName dans la spécficiation du pod. Par exemple:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  runtimeClassName: myclass
  # ...

Cela indiquera à la kubelet d'utiliser la RuntimeClass spécifiée pour exécuter ce pod. Si la RuntimeClass n'existe pas, ou si la CRI ne peut pas exécuter le handler correspondant, le pod passera finalement à l'état failed. Recherchez l'événement correspondant pour un message d'erreur.

Si aucun runtimeClassName n'est spécifié, le RuntimeHandler par défault sera utilisé, qui équivaut au comportement lorsque la fonctionnalité RuntimeClass est désactivée.

3.3 - L'environnement du conteneur

L'environnement du conteneur Kubernetes

Cette page décrit les ressources disponibles pour les conteneurs dans l'environnement de conteneur.

L'environnement du conteneur

L’environnement Kubernetes conteneur fournit plusieurs ressources importantes aux conteneurs:

  • Un système de fichier, qui est une combinaison d'une image et un ou plusieurs volumes.
  • Informations sur le conteneur lui-même.
  • Informations sur les autres objets du cluster.

Informations sur le conteneur

Le nom d'hôte d'un conteneur est le nom du pod dans lequel le conteneur est en cours d'exécution. Il est disponible via la commande hostname ou gethostname dans libc.

Le nom du pod et le namespace sont disponibles en tant que variables d'environnement via l'API downward.

Les variables d'environnement définies par l'utilisateur à partir de la définition de pod sont également disponibles pour le conteneur, de même que toutes les variables d'environnement spécifiées de manière statique dans l'image Docker.

Informations sur le cluster

Une liste de tous les services en cours d'exécution lors de la création d'un conteneur est disponible pour ce conteneur en tant que variables d'environnement. Ces variables d'environnement correspondent à la syntaxe des liens Docker.

Pour un service nommé foo qui correspond à un conteneur bar, les variables suivantes sont définies:

FOO_SERVICE_HOST=<l'hôte sur lequel le service est exécuté>
FOO_SERVICE_PORT=<le port sur lequel le service fonctionne>

Les services ont des adresses IP dédiées et sont disponibles pour le conteneur avec le DNS, si le module DNS est activé. 

A suivre

3.4 - Hooks de cycle de vie de conteneurs

Cette page décrit comment un conteneur pris en charge par kubelet peut utiliser le framework de Hooks de cycle de vie de conteneurs pour exécuter du code déclenché par des événements durant son cycle de vie.

Aperçu

De manière similaire à quantité de frameworks de langages de programmation qui ont des hooks de cycle de vie de composants, comme Angular, Kubernetes fournit aux conteneurs des hooks de cycle de vie. Les hooks permettent à un conteneur d'être au courant d'événements durant son cycle de vie et d'exécuter du code implémenté dans un handler lorsque le hook de cycle de vie correspondant est exécuté.

Hooks de conteneurs

Il existe deux hooks exposés aux conteneurs :

PostStart

Ce hook s'exécute immédiatement après qu'un conteneur soit créé. Cependant, il n'y a aucune garantie que le hook s'exécute avant l'ENTRYPOINT du conteneur. Aucun paramètre n'est passé au handler.

PreStop

Ce hook est appelé immédiatement avant qu'un conteneur se termine, en raison d'un appel à l'API ou d'un événement comme un échec de la liveness probe, un droit de préemption, un conflit de ressources ou autres. Un appel au hook preStop échoue si le conteneur est déjà dans l'état terminé ou complété. Il est bloquant, ce qui veut dire qu'il est synchrone, et doit donc se terminer avant que l'appel pour supprimer le conteneur soit envoyé. Aucun paramètre n'est passé au handler.

Une description plus précise du comportement de l'arrêt peut être trouvé dans Arrêt de Pods.

Implémentation d'un handler de hook

Les conteneurs peuvent accéder à un hook en implémentant et enregistrant un handler pour ce hook. Il existe deux types de handlers de hook pouvant être implémentés pour des conteneurs :

  • Exec - Exécute une commande donnée, comme pre-stop.sh, dans les cgroups et namespaces du conteneur. Les ressources consommées par la commande sont comptabilisées pour le conteneur.
  • HTTP - Exécute une requête HTTP sur un endpoint spécifique du conteneur.

Exécution d'un handler de hook

Lorsqu'un hook de cycle de vie de conteneur est appelé, le système de gestion de Kubernetes exécute le handler dans le conteneur enregistré pour ce hook.

Les appels aux handlers de hook sont synchrones dans le contexte du pod contenant le conteneur. Ceci veut dire que pour un hook PostStart, bien que l'ENTRYPOINT du conteneur et le hook soient lancés de manière asynchrone, si le hook prend trop de temps à s'exécuter ou se bloque, le conteneur ne peut pas atteindre l'état running.

Le comportement est similaire pour un hook PreStop. Si le hook se bloque durant l'exécution, la phase du Pod reste en état Terminating et le hook est tué après terminationGracePeriodSeconds que le pod se termine. Si un hook PostStart ou PreStop échoue, le conteneur est tué.

Les utilisateurs doivent rendre leurs handlers de hook aussi légers que possible. Il existe des cas, cependant, où de longues commandes ont un intérêt, comme pour enregistrer un état avant de stopper un conteneur.

Garanties de déclenchement d'un hook

La politique de déclenchement d'un hook est au moins une fois, ce qui veut dire qu'un hook peut être déclenché plus d'une fois pour un événement donné, comme PostStart ou PreStop. Il appartient à l'implémentation du hook de prendre en compte correctement ce comportement.

En général, un seul déclenchement est fait. Si, par exemple, un récepteur de hook HTTP est hors service et ne peut pas prendre en charge du trafic, il n'y a aucune tentative de renvoi. Dans quelques rares cas, cependant, un double envoi peut se produire. Par exemple, si kubelet redémarre au milieu d'un déclenchement de hook, le hook pourrait être re-déclenché après que kubelet redémarre.

Débugger des handlers de hook

Les logs pour un handler de hook ne sont pas exposés dans les événements du Pod. Si un handler échoue pour une raison particulière, il envoie un événement. Pour PostStart, c'est l'événement FailedPostStartHook et pour PreStop, c'est l'événement FailedPreStopHook. Vous pouvez voir ces événements en exécutant kubectl describe pod <pod_name>. Voici un exemple d'affichage d'événements lors de l'exécution de cette commande :

Events:
  FirstSeen  LastSeen  Count  From                                                   SubObjectPath          Type      Reason               Message
  ---------  --------  -----  ----                                                   -------------          --------  ------               -------
  1m         1m        1      {default-scheduler }                                                          Normal    Scheduled            Successfully assigned test-1730497541-cq1d2 to gke-test-cluster-default-pool-a07e5d30-siqd
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Pulling              pulling image "test:1.0"
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Created              Created container with docker id 5c6a256a2567; Security:[seccomp=unconfined]
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Pulled               Successfully pulled image "test:1.0"
  1m         1m        1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Started              Started container with docker id 5c6a256a2567
  38s        38s       1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Killing              Killing container with docker id 5c6a256a2567: PostStart handler: Error executing in Docker Container: 1
  37s        37s       1      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Normal    Killing              Killing container with docker id 8df9fdfd7054: PostStart handler: Error executing in Docker Container: 1
  38s        37s       2      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}                         Warning   FailedSync           Error syncing pod, skipping: failed to "StartContainer" for "main" with RunContainerError: "PostStart handler: Error executing in Docker Container: 1"
  1m         22s       2      {kubelet gke-test-cluster-default-pool-a07e5d30-siqd}  spec.containers{main}  Warning   FailedPostStartHook

A suivre

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

4.1 - Pods

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

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

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

4.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>
      minDomains: <integer>
      topologyKey: <string>
      whenUnsatisfiable: <string>
      labelSelector: <object>

Vous pouvez définir une ou plusieurs topologySpreadConstraint pour indiquer au kube-scheduler comment placer chaque nouveau Pod par rapport aux Pods déjà existants dans votre cluster. Les champs sont :

  • maxSkew décrit le degré avec lequel les Pods peuvent être inégalement distribués. C'est la différence maximale permise entre le nombre de Pods correspondants entre deux quelconques domaines de topologie d'un type donné. Il doit être supérieur à zéro.
  • topologyKey est la clé des labels de noeuds. Si deux noeuds sont étiquettés avec cette clé et ont des valeurs égales pour ce label, le scheduler considère les deux noeuds dans la même topologie. Le scheduler essaie de placer un nombre équilibré de Pods dans chaque domaine de topologie.
  • whenUnsatisfiable indique comment traiter un Pod qui ne satisfait pas les contraintes de propagation :
    • DoNotSchedule (défaut) indique au scheduler de ne pas le programmer.
    • ScheduleAnyway indique au scheduler de le programmer, tout en priorisant les noeuds minimisant le biais (skew).
  • labelSelector est utilisé pour touver les Pods correspondants. Les Pods correspondants à ce sélecteur de labels sont comptés pour déterminer le nombre de Pods dans leurs domaines de topologie correspodants. Voir Sélecteurs de labels pour plus de détails.

Vous pouvez en savoir plus sur ces champ en exécutant kubectl explain Pod.spec.topologySpreadConstraints.

Exemple : Une TopologySpreadConstraint

Supposons que vous ayez un cluster de 4 noeuds où 3 Pods étiquettés foo:bar sont placés sur node1, node2 et node3 respectivement (P représente un Pod) :

+---------------+---------------+
|     zoneA     |     zoneB     |
+-------+-------+-------+-------+
| node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+
|   P   |   P   |   P   |       |
+-------+-------+-------+-------+

Si nous voulons qu'un nouveau Pod soit uniformément réparti avec les Pods existants à travers les zones, la spec peut être :

kind: Pod
apiVersion: v1
metadata:
  name: mypod
  labels:
    foo: bar
spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        foo: bar
  containers:
  - name: pause
    image: k8s.gcr.io/pause:3.1

topologyKey: zone implique que la distribution uniforme sera uniquement appliquée pour les noeuds ayant le label "zone:<any value>" présent. whenUnsatisfiable: DoNotSchedule indique au scheduler de laisser le Pod dans l'état Pending si le Pod entrant ne peut pas satisfaire la contrainte.

Si le scheduler plaçait ce Pod entrant dans "zoneA", la distribution des Pods deviendrait [3, 1], et le biais serait de 2 (3 - 1) - ce qui va à l'encontre de maxSkew: 1. Dans cet exemple, le Pod entrant peut uniquement être placé dans "zoneB":

+---------------+---------------+      +---------------+---------------+
|     zoneA     |     zoneB     |      |     zoneA     |     zoneB     |
+-------+-------+-------+-------+      +-------+-------+-------+-------+
| node1 | node2 | node3 | node4 |  OR  | node1 | node2 | node3 | node4 |
+-------+-------+-------+-------+      +-------+-------+-------+-------+
|   P   |   P   |   P   |   P   |      |   P   |   P   |  P P  |       |
+-------+-------+-------+-------+      +-------+-------+-------+-------+

Vous pouvez ajuster la spec du Pod pour pour répondre à divers types d'exigences :

  • Changez maxSkew pour une valeur plus grande comme "2" pour que le Pod entrant puisse aussi être placé dans la "zoneA".
  • Changez topologyKey pour "node" pour distribuer les Pods uniformément à travers les noeuds et non plus les zones. Dans l'exemple ci-dessus, 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

4.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.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo "L''app s''exécute!" && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo en attente de myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo en attente de mydb; sleep 2; done"]

Les fichiers YAML suivants résument les services mydb et myservice :

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Vous pouvez démarrer ce Pod en exécutant :

kubectl apply -f myapp.yaml
pod/myapp-pod created

Et vérifier son statut avec :

kubectl get -f myapp.yaml
NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

ou pour plus de détails :

kubectl describe -f myapp.yaml
Name:          myapp-pod
Namespace:     default
[...]
Labels:        app.kubernetes.io/name=MyApp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

Pour voir les logs des init containers dans ce Pod, exécuter :

kubectl logs myapp-pod -c init-myservice # Inspecter le premier init container
kubectl logs myapp-pod -c init-mydb      # Inspecter le second init container

À ce stade, ces init containers attendent de découvrir les services nommés mydb et myservice.

Voici une configuration que vous pouvez utiliser pour faire apparaître ces Services :

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

Pour créer les services mydb et myservice :

kubectl apply -f services.yaml
service/myservice created
service/mydb created

Vous verrez ensuite que ces init containers se terminent et que le Pod myapp-pod évolue vers l'état "Running" (en exécution) :

kubectl get -f myapp.yaml
NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

Cet exemple simple devrait suffire à vous inspirer pour créer vos propres init containers. A suivre contient un lien vers un exemple plus détaillé.

Comportement détaillé

Pendant le démarrage d'un Pod, chaque init container démarre en ordre, après que le réseau et les volumes ont été initialisés. Chaque conteneur doit se terminer avec succès avant que le prochain ne démarre. Si un conteneur n'arrive pas à démarrer à cause d'un problème d'exécution ou se termine avec un échec, il est redémarré selon la restartPolicy du Pod. Toutefois, si la restartPolicy du Pod est configurée à "Always", les init containers utilisent la restartPolicy "OnFailure".

Un Pod ne peut pas être Ready tant que tous les init containers ne se sont pas exécutés avec succès. Les ports d'un init container ne sont pas agrégés sous un Service. Un Pod qui s'initialise est dans l'état Pending mais devrait avoir une condition Initialized configurée à "true".

Si le Pod redémarre ou est redémarré, tous les init containers doivent s'exécuter à nouveau.

Les changements aux spec d'un init containers sont limités au champ image du conteneur. Changer le champ image d'un init container équivaut à redémarrer le Pod.

Puisque les init containers peuvent être redémarrés, réessayés ou ré-exécutés, leur code doit être idempotent. En particulier, le code qui écrit dans des fichiers sur EmptyDirs devrait être préparé à la possibilité qu'un fichier de sortie existe déjà.

Les init containers ont tous les champs d'un conteneur d'application. Cependant, Kubernetes interdit l'utilisation de readinessProbe parce que les init containers ne peuvent pas définir une "readiness" distincte de la complétion. Ceci est appliqué lors de la validation.

L'utilisation de activeDeadlineSeconds sur le Pod et livenessProbe sur le conteneur permet d'empêcher les init containers d'échouer tout le temps. La deadline active inclut les init containers.

Le nom de chaque application et init container dans un Pod doit être unique; une erreur de validation est générée pour tout conteneur partageant un nom avec un autre.

Ressources

Étant donné l'ordonnancement et l'exécution des init containers, les règles suivantes s'appliquent pour l'utilisation des ressources :

  • La plus haute requête ou limite particulière de ressource définie pour tous les init containers est la limite/requête d'initialisation effective
  • La limite/requête effective d'un Pod pour une ressource est la plus haute parmis :
    • la somme de toutes les requêtes/limites des conteneurs d'application pour une ressource
    • la limite/requête d'initialisation effective pour une ressource
  • Le Scheduling est effectué sur la base des requêtes/limites effectives, ce qui signifie que les init containers peuvent réserver des ressources pour l'initialisation qui ne sont pas utilisées durant le cycle de vie du Pod.
  • La QoS (qualité de service) tierce de la QoS tierce effective d'un Pod est la QoS tierce aussi bien pour les init containers que pour les conteneurs d'application.

Les quotas et limites sont appliqués sur la base de la requête/limite effective d'un Pod.

Les groupes de contrôle au niveau du Pod (cgroups) sont basés sur la requête/limite effective de Pod, la même que celle du scheduler.

Raisons du redémarrage d'un Pod

Un Pod peut redémarrer, ce qui cause la ré-exécution des init containers, pour les raisons suivantes :

  • Un utilisateur met à jour les spécifications du Pod, ce qui cause le changement de l'image de l'init container. Tout changement à l'image du init container redémarre le Pod. Les changements au conteneur d'application entraînent seulement le redémarrage du conteneur d'application.
  • Le conteneur d'infrastructure Pod est redémarré. Ceci est peu commun et serait effectué par une personne ayant un accès root aux nœuds.
  • Tous les conteneurs dans un Pod sont terminés tandis que restartPolicy est configurée à "Always", ce qui force le redémarrage, et l'enregistrement de complétion du init container a été perdu à cause d'une opération de garbage collection (récupération de mémoire).

A suivre

4.2 - Contrôleurs

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

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

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

5 - Services, Equilibreur de charge, et Réseau

Service Reseau Kubernetes

5.1 - EndpointSlices

FEATURE STATE: Kubernetes v1.17 [beta]

EndpointSlices offrent une méthode simple pour suivre les Endpoints d'un réseau au sein d'un cluster de Kubernetes. Ils offrent une alternative plus évolutive et extensible aux Endpoints.

Ressource pour EndpointSlice

Dans Kubernetes, un EndpointSlice contient des références à un ensemble de Endpoints. Le controleur d'EndpointSlice crée automatiquement des EndpointSlices pour un Service quand un sélecteur est spécifié. Ces EndpointSlices vont inclure des références à n'importe quels Pods qui correspondent aux selecteurs de Service. EndpointSlices groupent ensemble les Endpoints d'un réseau par combinaisons uniques de Services et de Ports.

Par exemple, voici un échantillon d'une ressource EndpointSlice pour le Kubernetes Service exemple.

apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
  name: exemple-abc
  labels:
    kubernetes.io/service-name: exemple
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true
    hostname: pod-1
    topology:
      kubernetes.io/hostname: node-1
      topology.kubernetes.io/zone: us-west2-a

Les EndpointSlices gérés par le contrôleur d'EndpointSlice n'auront, par défaut, pas plus de 100 Endpoints chacun. En dessous de cette échelle, EndpointSlices devraient mapper 1:1 les Endpoints et les Services et devraient avoir une performance similaire.

EndpointSlices peuvent agir en tant que source de vérité pour kube-proxy quand il s'agit du routage d'un trafic interne. Lorsqu'ils sont activés, ils devraient offrir une amélioration de performance pour les services qui ont une grand quantité d'Endpoints.

Types d'addresses

Les EndpointSlices supportent 3 types d'addresses :

  • IPv4
  • IPv6
  • FQDN (Fully Qualified Domain Name) - [nom de domaine entièrement qualifié]

Topologie

Chaque Endpoint dans un EndpointSlice peut contenir des informations de topologie pertinentes. Ceci est utilisé pour indiquer où se trouve un Endpoint, qui contient les informations sur le Node, zone et région correspondantes. Lorsque les valeurs sont disponibles, les labels de Topologies suivants seront définis par le contrôleur EndpointSlice:

  • kubernetes.io/hostname - Nom du Node sur lequel l'Endpoint se situe.
  • topology.kubernetes.io/zone - Zone dans laquelle l'Endpoint se situe.
  • topology.kubernetes.io/region - Région dans laquelle l'Endpoint se situe.

Le contrôleur EndpointSlice surveille les Services et les Pods pour assurer que leurs correspondances avec les EndpointSlices sont à jour. Le contrôleur gère les EndpointSlices pour tous les Services qui ont un sélecteur - [référence: sélecteur] - specifié. Celles-ci représenteront les IPs des Pods qui correspondent au sélecteur.

Capacité d'EndpointSlices

Les EndpointSlices sont limités à une capacité de 100 Endpoints chacun, par défaut. Vous pouvez configurer ceci avec l'indicateur --max-endpoints-per-slice kube-controller-manager jusqu'à un maximum de 1000.

Distribution d'EndpointSlices

Chaque EndpointSlice a un ensemble de ports qui s'applique à tous les Endpoints dans la ressource. Lorsque les ports nommés sont utilisés pour un Service, les Pods peuvent se retrouver avec différents ports cibles pour le même port nommé, nécessitant différents EndpointSlices.

Le contrôleur essaie de remplir les EndpointSlices aussi complètement que possible, mais ne les rééquilibre pas activement. La logique du contrôleur est assez simple:

  1. Itérer à travers les EndpointSlices existants, retirer les Endpoints qui ne sont plus voulus et mettre à jour les Endpoints qui ont changé.
  2. Itérer à travers les EndpointSlices qui ont été modifiés dans la première étape et les remplir avec n'importe quel Endpoint nécéssaire.
  3. S'il reste encore des Endpoints nouveaux à ajouter, essayez de les mettre dans une slice qui n'a pas été changée et/ou en créer une nouvelle.

Par-dessus tout, la troisième étape priorise la limitation de mises à jour d'EndpointSlice sur une distribution complètement pleine d'EndpointSlices. Par exemple, s'il y avait 10 nouveaux Endpoints à ajouter et 2 EndpointSlices qui peuvent contenir 5 Endpoints en plus chacun; cette approche créera un nouveau EndpointSlice au lieu de remplir les EndpointSlice existants. C'est à dire, une seule création EndpointSlice est préférable à plusieurs mises à jour d'EndpointSlices.

Avec kube-proxy exécuté sur chaque Node et surveillant EndpointSlices, chaque changement d'un EndpointSlice devient relativement coûteux puisqu'ils seront transmis à chaque Node du cluster. Cette approche vise à limiter le nombre de modifications qui doivent être envoyées à chaque Node, même si ça peut causer plusieurs EndpointSlices non remplis.

En pratique, cette distribution bien peu idéale devrait être rare. La plupart des changements traités par le contrôleur EndpointSlice seront suffisamment petits pour tenir dans un EndpointSlice existant, et sinon, un nouveau EndpointSlice aurait probablement été bientôt nécessaire de toute façon. Les mises à jour continues des déploiements fournissent également une compaction naturelle des EndpointSlices avec tous leurs pods et les Endpoints correspondants qui se feront remplacer.

Motivation

L'API des Endpoints fournit une méthode simple et facile à suivre pour les Endpoints dans Kubernetes. Malheureusement, comme les clusters Kubernetes et Services sont devenus plus grands, les limitations de cette API sont devenues plus visibles. Plus particulièrement, celles-ci comprennent des limitations liées au dimensionnement vers un plus grand nombre d'Endpoints d'un réseau.

Puisque tous les Endpoints d'un réseau pour un Service ont été stockés dans une seule ressource Endpoints, ces ressources pourraient devenir assez lourdes. Cela affecte les performances des composants Kubernetes (notamment le plan de contrôle) et cause une grande quantité de trafic réseau et de traitements lorsque les Endpoints changent. Les EndpointSlices aident à atténuer ces problèmes ainsi qu'à fournir une plate-forme extensible pour des fonctionnalités supplémentaires telles que le routage topologique.

A suivre

5.2 - Service

Une manière abstraite d'exposer une application s'exécutant sur un ensemble de Pods en tant que service réseau.

Avec Kubernetes, vous n'avez pas besoin de modifier votre application pour utiliser un mécanisme de découverte de services inconnu. Kubernetes donne aux pods leurs propres adresses IP et un nom DNS unique pour un ensemble de pods, et peut équilibrer la charge entre eux.

Motivation

Les Pods Kubernetes sont mortels. Ils naissent et lorsqu'ils meurent, ils ne ressuscitent pas. Si vous utilisez un Déploiement pour exécuter votre application, il peut créer et détruire dynamiquement des pods.

Chaque pod obtient sa propre adresse IP, mais dans un déploiement, l'ensemble de pods s'exécutant en un instant peut être différent de l'ensemble de pods exécutant cette application un instant plus tard.

Cela conduit à un problème: si un ensemble de pods (appelez-les «backends») fournit des fonctionnalités à d'autres pods (appelez-les «frontends») à l'intérieur de votre cluster, comment les frontends peuvent-ils trouver et suivre l'adresse IP à laquelle se connecter, afin que le frontend puisse utiliser la partie backend de la charge de travail?

C'est là où les Services rentrent en jeu.

La ressource Service

Dans Kubernetes, un service est une abstraction qui définit un ensemble logique de pods et une politique permettant d'y accéder (parfois ce modèle est appelé un micro-service). L'ensemble des pods ciblés par un service est généralement déterminé par un selector (voir ci-dessous pourquoi vous voudrez peut-être un service sans un sélecteur).

Par exemple, considérons un backend de traitement d'image sans état qui s'exécute avec 3 replicas. Ces réplicas sont fongibles et les frontends ne se soucient pas du backend qu'ils utilisent. Bien que les pods réels qui composent l'ensemble backend puissent changer, les clients frontends ne devraient pas avoir besoin de le savoir, pas plus qu'ils ne doivent suivre eux-mêmes l'ensemble des backends.

L'abstraction du service permet ce découplage.

Découverte de services native du cloud

Si vous pouvez utiliser les API Kubernetes pour la découverte de services dans votre application, vous pouvez interroger l'API server pour les Endpoints, qui sont mis à jour chaque fois que l'ensemble des pods d'un service change.

Pour les applications non natives, Kubernetes propose des moyens de placer un port réseau ou un load balancer entre votre application et les modules backend.

Définition d'un service

Un service dans Kubernetes est un objet REST, semblable à un pod. Comme tous les objets REST, vous pouvez effectuer un POST d'une définition de service sur le serveur API pour créer une nouvelle instance.

Par exemple, supposons que vous ayez un ensemble de pods qui écoutent chacun sur le port TCP 9376 et portent une étiquette app.kubernetes.io/name=MyApp:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Cette spécification crée un nouvel objet Service nommé «my-service», qui cible le port TCP 9376 sur n'importe quel pod avec l'étiquette «app.kubernetes.io/name=MyApp».

Kubernetes attribue à ce service une adresse IP (parfois appelé l'"IP cluster"), qui est utilisé par les proxies Service (voir IP virtuelles et proxy de service).

Le contrôleur de service recherche en continu les pods qui correspondent à son sélecteur, puis POST toutes les mises à jour d'un objet Endpoint également appelé "my-service".

Les définitions de port dans les pods ont des noms, et vous pouvez référencer ces noms dans l'attribut targetPort d'un service. Cela fonctionne même s'il existe un mélange de pods dans le service utilisant un seul nom configuré, avec le même protocole réseau disponible via différents numéros de port. Cela offre beaucoup de flexibilité pour déployer et faire évoluer vos services. Par exemple, vous pouvez modifier les numéros de port que les pods exposent dans la prochaine version de votre logiciel principal, sans casser les clients.

Le protocole par défaut pour les services est TCP; vous pouvez également utiliser tout autre protocole pris en charge.

Comme de nombreux services doivent exposer plus d'un port, Kubernetes prend en charge plusieurs définitions de port sur un objet Service. Chaque définition de port peut avoir le même protocole, ou un autre.

Services sans sélecteurs

Les services abritent le plus souvent l'accès aux pods Kubernetes, mais ils peuvent également abstraire d'autres types de backends. Par exemple:

  • Vous voulez avoir un cluster de base de données externe en production, mais dans votre environnement de test, vous utilisez vos propres bases de données.
  • Vous souhaitez pointer votre service vers un service dans un autre Namespace ou sur un autre cluster.
  • Vous migrez une charge de travail vers Kubernetes. Lors de l'évaluation de l'approche, vous exécutez uniquement une partie de vos backends dans Kubernetes.

Dans n'importe lequel de ces scénarios, vous pouvez définir un service sans un sélecteur de pod. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Étant donné que ce service n'a pas de sélecteur, l'objet Endpoint correspondant n'est pas créé automatiquement. Vous pouvez mapper manuellement le service à l'adresse réseau et au port où il s'exécute, en ajoutant manuellement un objet Endpoint:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

L'accès à un service sans sélecteur fonctionne de la même manière que s'il avait un sélecteur. Dans l'exemple ci-dessus, le trafic est routé vers le Endpoint unique défini dans le YAML: 192.0.2.42:9376 (TCP).

Un service ExternalName est un cas spécial de service qui n'a pas de sélecteurs et utilise des noms DNS à la place. Pour plus d'informations, consultez la section ExternalName plus loin dans ce document.

Endpoint Slices

FEATURE STATE: Kubernetes v1.17 [beta]

Un Endpoint Slices est une ressource API qui peut fournir une alternative plus évolutive au Endpoints. Bien que conceptuellement assez similaire aux Endpoints, les Endpoint Slices permettent la distribution des endpoints réseau sur plusieurs ressources. Par défaut, un Endpoint Slice est considéré comme "plein" une fois qu'il atteint 100 endpoints, au delà, des Endpoint Slices addtionnels seront crées pour stocker tout autre endpoints.

Les Endpoint Slices fournissent des attributs et des fonctionnalités supplémentaires qui sont décrits en détail dans Endpoint Slices.

IP virtuelles et proxy de service

Chaque nœud d'un cluster Kubernetes exécute un kube-proxy. kube-proxy est responsable de l'implémentation d'une forme d'IP virtuelle pour les Services qui ne sont pas de type ExternalName.

Pourquoi ne pas utiliser le DNS round-robin ?

Une question qui apparaît de temps en temps est pourquoi Kubernetes s'appuie sur le proxy pour transférer le trafic entrant vers les backends. Et les autres approches? Par exemple, serait-il possible de configurer des enregistrements DNS qui ont plusieurs valeurs A (ou AAAA pour IPv6), et de s'appuyer sur la résolution de nom à tour de rôle (round-robin)?

Il existe plusieurs raisons d'utiliser le proxy pour les services:

  • Il existe une longue histoire d'implémentations DNS ne respectant pas les TTL d'enregistrement et mettant en cache les résultats des recherches de noms après leur expiration.
  • Certaines applications n'effectuent des recherches DNS qu'une seule fois et mettent en cache les résultats indéfiniment.
  • Même si les applications et les bibliothèques ont fait une bonne résolution, les TTL faibles ou nuls sur les enregistrements DNS pourraient imposer une charge élevée sur DNS qui devient alors difficile à gérer.

User space proxy mode

Dans ce mode, kube-proxy surveille le maître Kubernetes pour l'ajout et la suppression d'objets Service et Endpoint. Pour chaque service, il ouvre un port (choisi au hasard) sur le nœud local. Toutes les connexions à ce "port proxy" sont transmises par proxy à l'un des modules backend du service (comme indiqué via les Endpoints). kube-proxy prend en compte le paramètre SessionAffinity du service pour décider quel pod backend utiliser.

Enfin, le proxy de l'espace utilisateur installe des règles iptables qui capturent le trafic vers le service clusterIP (qui est virtuel) et port. Les règles redirigent ce trafic vers le port proxy qui fait office de proxy pour le Pod de backend.

Par défaut, kube-proxy en mode espace utilisateur choisit un backend via un algorithme round-robin.

Diagramme de vue d'ensemble des services pour le proxy de l'espace utilisateur

iptables proxy mode

Dans ce mode, kube-proxy surveille le plan de contrôle Kubernetes pour l'ajout et la suppression d'objets Service et Endpoint. Pour chaque service, il installe des règles iptables, qui capturent le trafic vers le «clusterIP» et le «port» du service, et redirigent ce trafic vers l'un des ensembles principaux du service. Pour chaque objet Endpoint, il installe des règles iptables qui sélectionnent un Pod de backend.

Par défaut, kube-proxy en mode iptables choisit un backend au hasard.

L'utilisation d'iptables pour gérer le trafic a un coût système inférieur, car le trafic est géré par Linux netfilter sans avoir besoin de basculer entre l'espace utilisateur et l'espace noyau. Cette approche est également susceptible d'être plus fiable.

Si kube-proxy s'exécute en mode iptables et que le premier pod sélectionné ne répond pas, la connexion échoue. C'est différent du mode espace utilisateur: dans ce scénario, kube-proxy détecterait que la connexion au premier pod avait échoué et réessayerait automatiquement avec un pod backend différent.

Vous pouvez utiliser les readiness probes d'un Pod pour vérifier que les pods backend fonctionnent correctement, de sorte que kube-proxy en mode iptables ne voit que les backends testés comme sains. Cela signifie que vous évitez d'envoyer du trafic via kube-proxy vers un pod connu pour avoir échoué.

Diagramme de présentation des services pour le proxy iptables

IPVS proxy mode

FEATURE STATE: Kubernetes v1.11 [stable]

En mode ipvs, kube-proxy surveille les Services et Endpoints Kubernetes. kube-proxy appelle l'interface netlink pour créer les règles IPVS en conséquence et synchronise périodiquement les règles IPVS avec les Services et Endpoints Kubernetes. Cette boucle de contrôle garantit que l'état IPVS correspond à l'état souhaité. Lors de l'accès à un service, IPVS dirige le trafic vers l'un des pods backend.

Le mode proxy IPVS est basé sur des fonctions hooks de netfilter qui est similaire au mode iptables, mais utilise la table de hachage comme structure de données sous-jacente et fonctionne dans l'espace du noyau. Cela signifie que kube-proxy en mode IPVS redirige le trafic avec une latence plus faible que kube-proxy en mode iptables, avec de bien meilleures performances lors de la synchronisation des règles de proxy. Par rapport aux autres modes proxy, le mode IPVS prend également en charge un débit plus élevé de trafic réseau.

IPVS offre plus d'options pour équilibrer le trafic vers les pods d'arrière-plan; ceux-ci sont:

  • rr: round-robin
  • lc: least connection (plus petit nombre de connexions ouvertes)
  • dh: destination hashing
  • sh: source hashing
  • sed: shortest expected delay
  • nq: never queue

Diagramme de vue d'ensemble des services pour le proxy IPVS

Dans ces modèles de proxy, le trafic lié à l'IP: Port du service est dirigé vers un backend approprié sans que les clients ne sachent quoi que ce soit sur Kubernetes, les services ou les pods.

Si vous souhaitez vous assurer que les connexions d'un client particulier sont transmises à chaque fois au même pod, vous pouvez sélectionner l'affinité de session en fonction des adresses IP du client en définissant service.spec.sessionAffinity sur" ClientIP "(la valeur par défaut est" None"). Vous pouvez également définir la durée maximale de session persistante en définissant service.spec.sessionAffinityConfig.clientIP.timeoutSeconds de manière appropriée (la valeur par défaut est 10800, ce qui correspond à 3 heures).

Services multi-ports

Pour certains services, vous devez exposer plusieurs ports. Kubernetes vous permet de configurer plusieurs définitions de port sur un objet Service. Lorsque vous utilisez plusieurs ports pour un service, vous devez donner tous vos noms de ports afin qu'ils ne soient pas ambigus. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

Choisir sa propre adresse IP

Vous pouvez spécifier votre propre adresse IP de cluster dans le cadre d'une demande de création de Service. Pour ce faire, définissez le champ .spec.clusterIP. Par exemple, si vous avez déjà une entrée DNS existante que vous souhaitez réutiliser, ou des systèmes existants qui sont configurés pour une adresse IP spécifique et difficiles à reconfigurer.

L'adresse IP que vous choisissez doit être une adresse IPv4 ou IPv6 valide dans la plage CIDR service-cluster-ip-range configurée pour le serveur API. Si vous essayez de créer un service avec une valeur d'adresse de clusterIP non valide, le serveur API retournera un code d'état HTTP 422 pour indiquer qu'il y a un problème.

Découvrir les services

Kubernetes prend en charge 2 modes principaux de recherche d'un service: les variables d'environnement et DNS.

Variables d'environnement

Lorsqu'un pod est exécuté sur un nœud, le kubelet ajoute un ensemble de variables d'environnement pour chaque service actif. Il prend en charge à la fois les variables Docker links (voir makeLinkVariables) et plus simplement les variables {SVCNAME}_SERVICE_HOST et {SVCNAME}_SERVICE_PORT, où le nom du service est en majuscules et les tirets sont convertis en underscore.

Par exemple, le service redis-master qui expose le port TCP 6379 et a reçu l'adresse IP de cluster 10.0.0.11, produit les variables d'environnement suivantes:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

DNS

Vous pouvez (et devriez presque toujours) configurer un service DNS pour votre cluster Kubernetes à l'aide d'un add-on.

Un serveur DNS prenant en charge les clusters, tel que CoreDNS, surveille l'API Kubernetes pour les nouveaux services et crée un ensemble d'enregistrements DNS pour chacun. Si le DNS a été activé dans votre cluster, tous les pods devraient automatiquement être en mesure de résoudre les services par leur nom DNS.

Par exemple, si vous avez un service appelé "my-service" dans un namespace Kubernetes "my-ns", le plan de contrôle et le service DNS agissant ensemble et créent un enregistrement DNS pour "my-service.my-ns". Les Pods dans le Namespace "my-ns" devrait être en mesure de le trouver en faisant simplement une recherche de nom pour my-service ("my-service.my-ns" fonctionnerait également).

Les pods dans d'autres namespaces doivent utiliser le nom de my-service.my-ns. Ces noms seront résolus en IP de cluster attribuée pour le service.

Kubernetes prend également en charge les enregistrements DNS SRV (Service) pour les ports nommés. Si le service "my-service.my-ns" a un port nommé http avec un protocole défini sur TCP, vous pouvez effectuer une requête DNS SRV pour _http._tcp.my-service.my-ns pour découvrir le numéro de port de http, ainsi que l'adresse IP.

Le serveur DNS Kubernetes est le seul moyen d'accéder aux services ExternalName. Vous pouvez trouver plus d'informations sur la résolution de ExternalName dans DNS Pods et Services.

Headless Services

Parfois, vous n'avez pas besoin de load-balancing et d'une seule IP de Service. Dans ce cas, vous pouvez créer ce que l'on appelle des services "headless", en spécifiant explicitement "None" pour l'IP du cluster (.spec.clusterIP).

Vous pouvez utiliser un service headless pour interfacer avec d'autres mécanismes de découverte de service, sans être lié à l'implémentation de Kubernetes.

Pour les services headless, une IP de cluster n'est pas allouée, kube-proxy ne gère pas ces services et aucun load-balancing ou proxy n'est effectué par la plateforme pour eux. La configuration automatique de DNS dépend de la définition ou non de sélecteurs par le service:

Avec sélecteurs

Pour les services headless qui définissent des sélecteurs, le controlleur des Endpoints crée des enregistrements Endpoints dans l'API, et modifie la configuration DNS pour renvoyer des enregistrements (adresses) qui pointent directement vers les Pods visés par le Service.

Sans sélecteurs

Pour les services headless qui ne définissent pas de sélecteurs, le contrôleur des Endpoints ne crée pas d'enregistrements Endpoints. Cependant, le système DNS recherche et configure soit:

  • Enregistrements CNAME pour les services de type ExternalName.
  • Un enregistrement pour tous les «Endpoints» qui partagent un nom avec le Service, pour tous les autres types.

Services de publication (ServiceTypes)

Pour certaines parties de votre application (par exemple, les frontaux), vous souhaiterez peut-être exposer un service sur une adresse IP externe, qui est en dehors de votre cluster.

Les «ServiceTypes» de Kubernetes vous permettent de spécifier le type de service que vous souhaitez. La valeur par défaut est «ClusterIP».

Les valeurs de Type et leurs comportements sont:

  • ClusterIP: Expose le service sur une IP interne au cluster. Le choix de cette valeur rend le service uniquement accessible à partir du cluster. Il s'agit du ServiceType par défaut.
  • NodePort: Expose le service sur l'IP de chaque nœud sur un port statique (le NodePort). Un service ClusterIP, vers lequel le service NodePort est automatiquement créé. Vous pourrez contacter le service NodePort, depuis l'extérieur du cluster, en demandant <NodeIP>: <NodePort>.
  • LoadBalancer: Expose le service en externe à l'aide de l'équilibreur de charge d'un fournisseur de cloud. Les services NodePort et ClusterIP, vers lesquels les itinéraires de l'équilibreur de charge externe, sont automatiquement créés.
  • ExternalName: Mappe le service au contenu du champ externalName (par exemple foo.bar.example.com), en renvoyant un enregistrement CNAME avec sa valeur. Aucun proxy d'aucune sorte n'est mis en place.

Vous pouvez également utiliser Ingress pour exposer votre service. Ingress n'est pas un type de service, mais il sert de point d'entrée pour votre cluster. Il vous permet de consolider vos règles de routage en une seule ressource car il peut exposer plusieurs services sous la même adresse IP.

Type NodePort

Si vous définissez le champ type sur NodePort, le plan de contrôle Kubernetes alloue un port à partir d'une plage spécifiée par l'indicateur --service-node-port-range (par défaut: 30000-32767). Chaque nœud assure le proxy de ce port (le même numéro de port sur chaque nœud) vers votre service. Votre service signale le port alloué dans son champ .spec.ports[*].nodePort.

Si vous souhaitez spécifier une ou des adresses IP particulières pour proxyfier le port, vous pouvez définir l'indicateur --nodeport-addresses dans kube-proxy sur des blocs IP particuliers; cela est pris en charge depuis Kubernetes v1.10. Cet indicateur prend une liste délimitée par des virgules de blocs IP (par exemple 10.0.0.0/8, 192.0.2.0/25) pour spécifier les plages d'adresses IP que kube-proxy doit considérer comme locales pour ce nœud.

Par exemple, si vous démarrez kube-proxy avec l'indicateur --nodeport-addresses=127.0.0.0/8, kube-proxy sélectionne uniquement l'interface de boucle locale pour les services NodePort. La valeur par défaut pour --nodeport-addresses est une liste vide. Cela signifie que kube-proxy doit prendre en compte toutes les interfaces réseau disponibles pour NodePort (qui est également compatible avec les versions antérieures de Kubernetes).

Si vous voulez un numéro de port spécifique, vous pouvez spécifier une valeur dans le champ nodePort. Le plan de contrôle vous attribuera ce port ou signalera l'échec de la transaction API. Cela signifie que vous devez vous occuper vous-même des éventuelles collisions de ports. Vous devez également utiliser un numéro de port valide, celui qui se trouve dans la plage configurée pour l'utilisation de NodePort.

L'utilisation d'un NodePort vous donne la liberté de configurer votre propre solution d'équilibrage de charge, de configurer des environnements qui ne sont pas entièrement pris en charge par Kubernetes, ou même d'exposer directement les adresses IP d'un ou plusieurs nœuds.

Notez que ce service est visible en tant que <NodeIP>: spec.ports[*].nodePort et .spec.clusterIP: spec.ports[*].Port. (Si l'indicateur --nodeport-addresses dans kube-proxy est défini, serait filtré NodeIP(s).)

Type LoadBalancer

Sur les fournisseurs de cloud qui prennent en charge les load balancers externes, la définition du champ type sur LoadBalancer provisionne un load balancer pour votre service. La création réelle du load balancer se produit de manière asynchrone et les informations sur le load balancer provisionné sont publiées dans le champ .status.loadBalancer. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  clusterIP: 10.0.171.239
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 192.0.2.127

Le trafic provenant du load balancer externe est dirigé vers les Pods backend. Le fournisseur de cloud décide de la répartition de la charge.

Certains fournisseurs de cloud vous permettent de spécifier le loadBalancerIP. Dans ces cas, le load balancer est créé avec le loadBalancerIP spécifié par l'utilisateur. Si le champ loadBalancerIP n'est pas spécifié, le loadBalancer est configuré avec une adresse IP éphémère. Si vous spécifiez un loadBalancerIP mais que votre fournisseur de cloud ne prend pas en charge la fonctionnalité, le champ loadBalancerIP que vous définissez est ignoré.

Load Balancer interne

Dans un environnement mixte, il est parfois nécessaire d'acheminer le trafic des services à l'intérieur du même bloc d'adresse réseau (virtuel).

Dans un environnement DNS à horizon divisé, vous auriez besoin de deux services pour pouvoir acheminer le trafic externe et interne vers vos endpoints.

Vous pouvez y parvenir en ajoutant une des annotations suivantes à un service. L'annotation à ajouter dépend du fournisseur de services cloud que vous utilisez.

Sélectionnez l'un des onglets.

[...]
metadata:
    name: my-service
    annotations:
        cloud.google.com/load-balancer-type: "Internal"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/azure-load-balancer-internal: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
[...]

[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
[...]

[...]
metadata:
  annotations:
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
[...]

Prise en charge TLS sur AWS

Pour une prise en charge partielle de TLS / SSL sur des clusters exécutés sur AWS, vous pouvez ajouter trois annotations à un service LoadBalancer:

metadata:
  name: my-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012

Le premier spécifie l'ARN du certificat à utiliser. Il peut s'agir soit d'un certificat d'un émetteur tiers qui a été téléchargé sur IAM, soit d'un certificat créé dans AWS Certificate Manager.

metadata:
  name: my-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: (https|http|ssl|tcp)

La deuxième annotation spécifie le protocole utilisé par un pod. Pour HTTPS et SSL, l'ELB s'attend à ce que le pod s'authentifie sur la connexion chiffrée, à l'aide d'un certificat.

HTTP et HTTPS sélectionnent le proxy de couche 7: l'ELB met fin à la connexion avec l'utilisateur, analyse les en-têtes et injecte l'en-tête X-Forwarded-For avec l'adresse IP de l'utilisateur (les pods ne voient que l'adresse IP de l'ELB à l'autre extrémité de sa connexion) lors du transfert des demandes.

TCP et SSL sélectionnent le proxy de couche 4: l'ELB transfère le trafic sans modifier les en-têtes.

Dans un environnement à usage mixte où certains ports sont sécurisés et d'autres non chiffrés, vous pouvez utiliser les annotations suivantes:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
        service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443,8443"

Dans l'exemple ci-dessus, si le service contenait trois ports, «80», «443» et «8443», alors «443» et «8443» utiliseraient le certificat SSL, mais «80» serait simplement un proxy HTTP.

A partir de Kubernetes v1.9, vous pouvez utiliser des stratégies SSL AWS prédéfinies avec des écouteurs HTTPS ou SSL pour vos services. Pour voir quelles politiques sont disponibles, vous pouvez utiliser l'outil de ligne de commande aws:

aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'

Vous pouvez ensuite spécifier l'une de ces stratégies à l'aide de l'annotation "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy"; par exemple:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-2017-01"

Prise en charge du protocole PROXY sur AWS

Pour activer protocole PROXY prise en charge des clusters exécutés sur AWS, vous pouvez utiliser l'annotation de service suivante:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"

Depuis la version 1.3.0, l'utilisation de cette annotation s'applique à tous les ports mandatés par l'ELB et ne peut pas être configurée autrement.

Journaux d'accès ELB sur AWS

Il existe plusieurs annotations pour gérer les journaux d'accès aux services ELB sur AWS.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-enabled contrôle si les journaux d'accès sont activés.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval contrôle l'intervalle en minutes pour la publication des journaux d'accès. Vous pouvez spécifier un intervalle de 5 ou 60 minutes.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name contrôle le nom du bucket Amazon S3 où les journaux d'accès au load balancer sont stockés.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix spécifie la hiérarchie logique que vous avez créée pour votre bucket Amazon S3.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
        # Spécifie si les journaux d'accès sont activés pour le load balancer

        service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
        # L'intervalle de publication des journaux d'accès.
        # Vous pouvez spécifier un intervalle de 5 ou 60 (minutes).

        service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
        # Le nom du bucket Amazon S3 où les journaux d'accès sont stockés

        service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
        # La hiérarchie logique que vous avez créée pour votre bucket Amazon S3, par exemple `my-bucket-prefix/prod`

Drainage de connexion sur AWS

Le drainage des connexions pour les ELB classiques peut être géré avec l'annotation service.beta.kubernetes.io / aws-load-balancer-connection-draining-enabled définie sur la valeur true. L'annotation service.beta.kubernetes.io / aws-load-balancer-connection-draining-timeout peut également être utilisée pour définir la durée maximale, en secondes, pour garder les connexions existantes ouvertes avant de désenregistrer les instances.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
        service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"

Autres annotations ELB

Il existe d'autres annotations pour gérer les Elastic Load Balancers décrits ci-dessous.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60"
        # Délai, en secondes, pendant lequel la connexion peut être inactive (aucune donnée n'a été envoyée via la connexion) avant d'être fermée par le load balancer

        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
        # Spécifie si le load balancing inter-zones est activé pour le load balancer

        service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "environment=prod,owner=devops"
        # Une liste de paires clé-valeur séparées par des virgules qui seront enregistrées en tant que balises supplémentaires dans l'ELB.

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: ""
        # Nombre de contrôles de santé successifs réussis requis pour qu'un backend soit considéré comme sain pour le trafic.
        # La valeur par défaut est 2, doit être comprise entre 2 et 10

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "3"
        # Nombre de contrôles de santé infructueux requis pour qu'un backend soit considéré comme inapte pour le trafic.
        # La valeur par défaut est 6, doit être comprise entre 2 et 10

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "20"
        # Intervalle approximatif, en secondes, entre les contrôles d'intégrité d'une instance individuelle.
        # La valeur par défaut est 10, doit être comprise entre 5 et 300

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5"
        # Durée, en secondes, pendant laquelle aucune réponse ne signifie l'échec d'un contrôle de santé.
        # Cette valeur doit être inférieure à la valeur service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval.
        # La valeur par défaut est 5, doit être comprise entre 2 et 60

        service.beta.kubernetes.io/aws-load-balancer-extra-security-groups: "sg-53fae93f,sg-42efd82e"
        # Une liste de groupes de sécurité supplémentaires à ajouter à l'ELB

Prise en charge du load balancer réseau sur AWS

FEATURE STATE: Kubernetes v1.15 [beta]

Pour utiliser un load balancer réseau sur AWS, utilisez l'annotation service.beta.kubernetes.io/aws-load-balancer-type avec la valeur définie sur nlb.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

Contrairement aux équilibreurs de charge élastiques classiques, les équilibreurs de charge réseau (NLB) transfèrent l'adresse IP du client jusqu'au nœud. Si un service est .spec.externalTrafficPolicy est réglé sur Cluster, l'adresse IP du client n'est pas propagée aux pods finaux.

En définissant .spec.externalTrafficPolicy à Local, les adresses IP des clients sont propagées aux pods finaux, mais cela peut entraîner une répartition inégale du trafic. Les nœuds sans pods pour un service LoadBalancer particulier échoueront au contrôle de santé du groupe cible NLB sur le .spec.healthCheckNodePort attribué automatiquement et ne recevront aucun trafic.

Pour obtenir un trafic uniforme, utilisez un DaemonSet ou spécifiez un pod anti-affinity pour ne pas localiser sur le même noeud.

Vous pouvez également utiliser les services NLB avec l'annotation load balancer internal.

Pour que le trafic client atteigne des instances derrière un NLB, les groupes de sécurité du nœud sont modifiés avec les règles IP suivantes:

Rule Protocol Port(s) IpRange(s) IpRange Description
Health Check TCP NodePort(s) (.spec.healthCheckNodePort for .spec.externalTrafficPolicy = Local) VPC CIDR kubernetes.io/rule/nlb/health=<loadBalancerName>
Client Traffic TCP NodePort(s) .spec.loadBalancerSourceRanges (defaults to 0.0.0.0/0) kubernetes.io/rule/nlb/client=<loadBalancerName>
MTU Discovery ICMP 3,4 .spec.loadBalancerSourceRanges (defaults to 0.0.0.0/0) kubernetes.io/rule/nlb/mtu=<loadBalancerName>

Afin de limiter les IP clientes pouvant accéder à l'équilibreur de charge réseau, spécifiez loadBalancerSourceRanges.

spec:
  loadBalancerSourceRanges:
    - "143.231.0.0/16"

Autres annotations CLB sur Tencent Kubernetes Engine (TKE)

Il existe d'autres annotations pour la gestion des équilibreurs de charge cloud sur TKE, comme indiqué ci-dessous.

    metadata:
      name: my-service
      annotations:
        # Lier des load balancers avec des nœuds spécifiques
        service.kubernetes.io/qcloud-loadbalancer-backends-label: key in (value1, value2)

        # ID d'un load balancer existant
        service.kubernetes.io/tke-existed-lbid:lb-6swtxxxx

        # Paramètres personnalisés pour le load balancer (LB), ne prend pas encore en charge la modification du type LB
        service.kubernetes.io/service.extensiveParameters: ""

        # Paramètres personnalisés pour le listener LB
        service.kubernetes.io/service.listenerParameters: ""

        # Spécifie le type de Load balancer;
        # valeurs valides: classic (Classic Cloud Load Balancer) ou application (Application Cloud Load Balancer)
        service.kubernetes.io/loadbalance-type: xxxxx

        # Spécifie la méthode de facturation de la bande passante du réseau public;
        # valid values: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic) and BANDWIDTH_POSTPAID_BY_HOUR (bill-by-bandwidth).
        service.kubernetes.io/qcloud-loadbalancer-internet-charge-type: xxxxxx

        # Spécifie la valeur de bande passante (plage de valeurs: [1,2000] Mbps).
        service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out: "10"

        # Lorsque cette annotation est définie, les équilibreurs de charge n'enregistrent que les nœuds sur lesquels le pod s'exécute, sinon tous les nœuds seront enregistrés.
        service.kubernetes.io/local-svc-only-bind-node-with-pod: true

Type ExternalName

Les services de type ExternalName mappent un service à un nom DNS, et non à un sélecteur standard tel que my-service ou cassandra. Vous spécifiez ces services avec le paramètre spec.externalName.

Cette définition de service, par exemple, mappe le service my-service dans l'espace de noms prod à my.database.example.com:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

Lors de la recherche de l'hôte my-service.prod.svc.cluster.local, le service DNS du cluster renvoie un enregistrement CNAME avec la valeur my.database.example.com. L'accès à «mon-service» fonctionne de la même manière que les autres services, mais avec la différence cruciale que la redirection se produit au niveau DNS plutôt que via un proxy ou un transfert. Si vous décidez ultérieurement de déplacer votre base de données dans votre cluster, vous pouvez démarrer ses pods, ajouter des sélecteurs ou des Endpoints appropriés et modifier le type du service.

IP externes

S'il existe des adresses IP externes qui acheminent vers un ou plusieurs nœuds de cluster, les services Kubernetes peuvent être exposés sur ces "IP externes". Le trafic qui pénètre dans le cluster avec l'IP externe (en tant qu'IP de destination), sur le port de service, sera routé vers l'un des Endpoints de service. Les externalIPs ne sont pas gérées par Kubernetes et relèvent de la responsabilité de l'administrateur du cluster.

Dans la spécification de service, «externalIPs» peut être spécifié avec n'importe lequel des «ServiceTypes». Dans l'exemple ci-dessous, "my-service" peut être consulté par les clients sur "80.11.12.10:80" (externalIP:port)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
  externalIPs:
    - 80.11.12.10

Lacunes

Le proxy fonctionnant dans l'espace utilisateur pour les VIP peut fonctionner à petite ou moyenne échelle, mais montrera ses limites dans de très grands clusters avec des milliers de services. La proposition de conception originale pour les portails a plus de détails à ce sujet.

L'utilisation du proxy de l'espace utilisateur masque l'adresse IP source d'un paquet accédant à un service. Cela rend certains types de filtrage réseau (pare-feu) impossibles. Le mode proxy iptables n'obscurcit pas les adresses IP source dans le cluster, mais il affecte toujours les clients passant par un LoadBalancer ou un NodePort.

Le champ Type est conçu comme une fonctionnalité imbriquée - chaque niveau s'ajoute au précédent. Cela n'est pas strictement requis sur tous les fournisseurs de cloud (par exemple, Google Compute Engine n'a pas besoin d'allouer un NodePort pour faire fonctionner LoadBalancer, mais AWS le fait) mais l'API actuelle le requiert.

Implémentation IP virtuelle

Les informations précédentes devraient être suffisantes pour de nombreuses personnes qui souhaitent simplement utiliser les Services. Cependant, il se passe beaucoup de choses dans les coulisses qui méritent d'être comprises.

Éviter les collisions

L'une des principales philosophies de Kubernetes est que vous ne devez pas être exposé à des situations qui pourraient entraîner l'échec de vos actions sans aucune faute de votre part. Pour la conception de la ressource Service, cela signifie de ne pas vous faire choisir votre propre numéro de port si ce choix pourrait entrer en collision avec le choix de quelqu'un d'autre. C'est un échec d'isolement.

Afin de vous permettre de choisir un numéro de port pour vos Services, nous devons nous assurer qu'aucun deux Services ne peuvent entrer en collision. Kubernetes le fait en attribuant à chaque service sa propre adresse IP.

Pour garantir que chaque service reçoit une adresse IP unique, un allocateur interne met à jour atomiquement une carte d'allocation globale dans etcd avant de créer chaque service. L'objet de mappage doit exister dans le registre pour que les services obtiennent des affectations d'adresse IP, sinon les créations échoueront avec un message indiquant qu'une adresse IP n'a pas pu être allouée.

Dans le plan de contrôle, un contrôleur d'arrière-plan est responsable de la création de cette carte (nécessaire pour prendre en charge la migration à partir d'anciennes versions de Kubernetes qui utilisaient le verrouillage en mémoire). Kubernetes utilise également des contrôleurs pour vérifier les affectations non valides (par exemple en raison d'une intervention de l'administrateur) et pour nettoyer les adresses IP allouées qui ne sont plus utilisées par aucun service.

Service IP addresses

Contrairement aux adresses IP des pods, qui acheminent réellement vers une destination fixe, les adresses IP des services ne sont pas réellement répondues par un seul hôte. Au lieu de cela, kube-proxy utilise iptables (logique de traitement des paquets sous Linux) pour définir les adresses IP virtual qui sont redirigées de manière transparente selon les besoins. Lorsque les clients se connectent au VIP, leur trafic est automatiquement transporté vers un Endpoint approprié. Les variables d'environnement et DNS pour les services sont en fait remplis en termes d'adresse IP virtuelle (et de port) du service.

kube-proxy prend en charge trois modes proxy — espace utilisateur, iptables et IPVS — qui fonctionnent chacun légèrement différemment.

Userspace

À titre d'exemple, considérons l'application de traitement d'image décrite ci-dessus. Lorsque le service backend est créé, le maître Kubernetes attribue une adresse IP virtuelle, par exemple 10.0.0.1. En supposant que le port de service est 1234, le service est observé par toutes les instances kube-proxy dans le cluster. Lorsqu'un proxy voit un nouveau service, il ouvre un nouveau port aléatoire, établit une redirection iptables de l'adresse IP virtuelle vers ce nouveau port et commence à accepter les connexions sur celui-ci.

Lorsqu'un client se connecte à l'adresse IP virtuelle du service, la règle iptables entre en jeu et redirige les paquets vers le propre port du proxy. Le “Service proxy” choisit un backend, et commence le proxy du trafic du client vers le backend.

Cela signifie que les propriétaires de services peuvent choisir le port de leur choix sans risque de collision. Les clients peuvent simplement se connecter à une adresse IP et à un port, sans savoir à quels pods ils accèdent réellement.

iptables

Considérons à nouveau l'application de traitement d'image décrite ci-dessus. Lorsque le service backend est créé, le plan de contrôle Kubernetes attribue une adresse IP virtuelle, par exemple 10.0.0.1. En supposant que le port de service est 1234, le service est observé par toutes les instances de kube-proxy dans le cluster. Lorsqu'un proxy voit un nouveau service, il installe une série de règles iptables qui redirigent de l'adresse IP virtuelle vers des règles par service. Les règles par service sont liées aux règles des Endpoints qui redirigent le trafic (à l'aide du NAT de destination) vers les backends.

Lorsqu'un client se connecte à l'adresse IP virtuelle du service, la règle iptables entre en jeu. Un backend est choisi (soit en fonction de l'affinité de la session, soit au hasard) et les paquets sont redirigés vers le backend. Contrairement au proxy de l'espace utilisateur, les paquets ne sont jamais copiés dans l'espace utilisateur, le proxy de kube n'a pas besoin d'être exécuté pour que l'adresse IP virtuelle fonctionne et les nœuds voient le trafic provenant de l'adresse IP du client non modifiée.

Ce même flux de base s'exécute lorsque le trafic arrive via un port de nœud ou via un load balancer, bien que dans ces cas, l'adresse IP du client soit modifiée.

IPVS

Les opérations iptables ralentissent considérablement dans un cluster à grande échelle, par exemple 10000 services. IPVS est conçu pour l'équilibrage de charge et basé sur des tables de hachage dans le noyau. Ainsi, vous pouvez obtenir une cohérence des performances dans un grand nombre de services à partir d'un kube-proxy basé sur IPVS. De plus, kube-proxy basé sur IPVS a des algorithmes d'équilibrage de charge plus sophistiqués (le moins de connexions, localité, pondéré, persistance).

Objet API

Le service est une ressource de niveau supérieur dans l'API REST Kubernetes. Vous pouvez trouver plus de détails sur l'objet API sur: Service API object.

Protocoles pris en charge

TCP

FEATURE STATE: Kubernetes v1.0 [stable]

Vous pouvez utiliser TCP pour tout type de service, et c'est le protocole réseau par défaut.

UDP

FEATURE STATE: Kubernetes v1.0 [stable]

Vous pouvez utiliser UDP pour la plupart des services. Pour Services de type LoadBalancer, la prise en charge UDP dépend du fournisseur de cloud offrant cette fonctionnalité.

HTTP

FEATURE STATE: Kubernetes v1.1 [stable]

Si votre fournisseur de cloud le prend en charge, vous pouvez utiliser un service dans le mode LoadBalancer pour configurer le proxy inverse HTTP / HTTPS externe, transmis au Endpoints du Service.

Protocole PROXY

FEATURE STATE: Kubernetes v1.1 [stable]

Si votre fournisseur de cloud le prend en charge(eg, AWS), vous pouvez utiliser un service en mode LoadBalancer pour configurer un load balancer en dehors de Kubernetes lui-même, qui transmettra les connexions préfixées par PROXY protocol.

Le load balancer enverra une première série d'octets décrivant la connexion entrante, similaire à cet exemple

PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n

suivi des données du client.

SCTP

FEATURE STATE: Kubernetes v1.12 [alpha]

Kubernetes prend en charge SCTP en tant que valeur de «protocole» dans les définitions de Service, Endpoint, NetworkPolicy et Pod en tant que fonctionnalité alpha. Pour activer cette fonction, l'administrateur du cluster doit activer le flag SCTPSupport sur l'apiserver, par exemple, --feature-gates=SCTPSupport=true,….

When the feature gate is enabled, you can set the protocol field of a Service, Endpoint, NetworkPolicy or Pod to SCTP. Kubernetes sets up the network accordingly for the SCTP associations, just like it does for TCP connections.

Avertissements

Prise en charge des associations SCTP multi-hôtes
Service avec type=LoadBalancer
Windows
Userspace kube-proxy

Futurs développements

À l'avenir, la stratégie de proxy pour les services peut devenir plus nuancée que le simple équilibrage alterné, par exemple master-elected ou sharded. Nous prévoyons également que certains services auront des load balancer «réels», auquel cas l'adresse IP virtuelle y transportera simplement les paquets.

Le projet Kubernetes vise à améliorer la prise en charge des services L7 (HTTP).

Le projet Kubernetes prévoit d'avoir des modes d'entrée plus flexibles pour les services, qui englobent les modes ClusterIP, NodePort et LoadBalancer actuels et plus encore.

A suivre

5.3 - DNS pour les services et les pods

DNS services pods Kubernetes

Cette page fournit une vue d'ensemble du support DNS par Kubernetes.

Introduction

Kubernetes planifie un pod et un service DNS sur le cluster et configure les kubelets pour indiquer à chaque conteneur d'utiliser l'adresse IP du service DNS pour résoudre les noms DNS.

Quels composants obtiennent des noms DNS?

Chaque service défini dans le cluster (y compris le serveur DNS lui-même) a un nom DNS. Par défaut, la liste de recherche DNS du client d'un pod inclura le namespace (espace de nommage) du pod et le domaine par défaut du cluster. C'est mieux illustré par un exemple :

Supposons un service nommé foo dans le namespace Kubernetes bar. Un pod en cours d'exécution dans le namespace bar peut rechercher ce service en faisant simplement une requête DNS "foo". Un pod qui tourne dans le namespace quux peut rechercher ce service en effectuant une requête DNS foo.bar.

Les sections suivantes détaillent les types d’enregistrement et la structure supportée par Kubernetes. Toute autre structure ou noms ou requêtes qui fonctionnent sont considérés comme des détails d'implémentation et peuvent changer sans préavis. Pour une spécification plus à jour, voir Découverte des services basée sur le DNS Kubernetes.

Services

Enregistrement A

Les services "normaux" (pas sans en-tête) se voient attribuer un enregistrement DNS A, et ont un nom sous la forme : mon-service.mon-namespace.svc.cluster.local. La résolution de ce nom donne l'adresse ClusterIP du service.

Les Services "Headless" (ou sans en-tête, c'est à dire sans ClusterIP) auront également un enregistrement type A, donc un nom sous la forme : mon-service.mon-namespace.svc.cluster.local. Contrairement aux Services Normaux, cela résout l'ensemble des adresses IP des pods sélectionnés par le Service. On s'attend à ce que les clients consomment l'ensemble ou utilisent le standard de sélection round-robin de l'ensemble.

Enregistrement SRV

Les enregistrements SRV sont créés pour les ports nommés faisant partie des services normaux ou Headless (sans en-tête). Pour chaque port nommé, l'enregistrement SRV aurait la forme _mon-nom-de-port._mon-protocole-de-port.mon-service.mon-namespace.svc.cluster.local. Pour un service régulier, cela se traduit par le numéro de port et le nom de domaine : mon-service.mon-namespace.svc.cluster.local. Pour un service sans en-tête, cela pourrait être résolu en plusieurs réponses, une réponse pour chaque pod lié à ce service et qui contient le numéro de port, ainsi le nom de domaine du pod est sous la forme nom-auto-genere.mon-service.mon-namespace.svc.cluster.local.

Pods

Enregistrement A

Lorsque cette option est activée, un enregistrement DNS A est attribué aux pods sous la forme adresse-ip-du-pod.mon-namespace.pod.cluster.local.

Par exemple, un pod avec l’IP 1.2.3.4 dans le namespace (espace de nommage) default avec un nom DNS de cluster.local aurait une entrée : 1-2-3-4.default.pod.cluster.local.

Nom d'hôte et sous-domaine d'un pod

Actuellement, lorsqu'un pod est créé, son nom d'hôte a la valeur metadata.name du pod.

La spécification du pod a un champ optionnel hostname, qui peut être utilisé pour spécifier la valeur du nom d'hôte du pod. Quand c'est spécifié, ce dernier a la priorité sur le nom du pod. Par exemple, si un pod a un hostname ayant la valeur "mon-hote", son nom d'hôte sera "mon-hote".

La spécification du pod a également un champ optionnel subdomain qui peut être utilisé pour spécifier son sous-domaine. Par exemple, un pod avec une valeur "foo" du champ hostname et une valeur "bar" du champ subdomain, dans le namespace "mon-namespace", aura un nom de domaine (FQDN) "foo.bar.mon-namespace.svc.cluster.local".

Exemple :

apiVersion: v1
kind: Service
metadata:
  name: sous-domaine-par-default
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: foo # En vrai, cette définition de port est à titre d'exemple, nous n'avons pas vraiment besoin de ports pour cette application.
    port: 1234
    targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox
spec:
  hostname: busybox-1
  subdomain: sous-domaine-par-default
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox
spec:
  hostname: busybox-2
  subdomain: sous-domaine-par-default
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox

Si un service sans en-tête (headless) est dans le même namespace que son pod et avec le même nom que le sous-domaine, le serveur KubeDNS du cluster renvoie également un enregistrement A pour le nom d’hôte (hostname) du pod. Par exemple, si un pod dont le nom d’hôte est " busybox-1" et le sous-domaine est "sous-domaine-par-default", et un service sans en-tête nommé "sous-domaine-par-default" dans le même namespace, le pod verra son propre nom de domaine complet "busybox-1.sous-domaine-par-default.mon-namespace.svc.cluster.local". Le DNS sert un enregistrement A portant ce nom, et pointant vers l'adresse IP du pod. Les deux Pods "busybox1" et " busybox2" peuvent avoir leurs enregistrements A distincts.

L’objet Endpoints peut spécifier le hostname pour n’importe quelle adresse d'endpoint (noeud final), avec son adresse IP.

Politique DNS du Pod

Les stratégies DNS peuvent être définies par pod. Actuellement, Kubernetes supporte des stratégies DNS qui sont spécifiques au pod. Ces politiques sont spécifiées dans le Champ dnsPolicy de la spécification du pod.

  • "Default" : le pod hérite de la configuration de résolution des noms du node (noeud) sur lequel ce même pod est en train de tourner. Voir discussion liée pour plus de détails.
  • "ClusterFirst" : toute requête DNS ne correspondant pas au suffixe du domaine configuré dans le cluster, tel que "www.kubernetes.io", sera transmise au serveur en amont hérité du node (noeud). Les administrateurs du cluster peuvent configurer des serveurs DNS supplémentaires que ce soit des serveurs secondaires (locaux) ou des vrais serveurs récursifs en amont pour faire la résolution.   Voir discussion liée pour plus de détails sur la manière dont les requêtes DNS sont traitées dans ces cas.
  • "ClusterFirstWithHostNet" : pour les pods exécutés avec hostNetwork, vous devez explicitement définir sa politique DNS "ClusterFirstWithHostNet".
  • "None" : une nouvelle valeur optionnelle introduite dans Kubernetes v1.9 (Beta dans v1.10). Elle permet à un pod d’ignorer les configurations DNS de l’environnement Kubernetes. Ainsi, toutes les configurations DNS sont supposées être fournies dans le champ dnsConfig de la spécification du pod.   Voir la sous-section Config DNS ci-dessous.

L’exemple ci-dessous montre un pod avec une stratégie DNS "ClusterFirstWithHostNet" car il a le champ hostNetwork défini à true.

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

Configuration DNS du pod

Kubernetes v1.9 introduit une fonctionnalité Alpha (version beta de v1.10) qui permet aux utilisateurs d'avoir plus de contrôle sur les paramètres DNS d'un pod. Cette fonctionnalité est activée par défaut dans la version 1.10. Pour activer cette fonctionnalité dans la version 1.9, l'administrateur du cluster doit activer la feature gate (porte de fonctionnalité) CustomPodDNS sur les serveurs apiserver et kubelet, par exemple, "--feature-gates=CustomPodDNS=true,...". Lorsque la fonction est activée, les utilisateurs peuvent mettre le champ dnsPolicy d’un pod à "None" et ils peuvent rajouter un nouveau champ dnsConfig à la spécification du pod.

Le champ dnsConfig est facultatif et peut fonctionner avec toute configuration dnsPolicy. Cependant, quand dnsPolicy du pod est réglé sur "None", le champ dnsConfig doit être explicitement spécifié.

Vous trouverez ci-dessous les propriétés qu'un utilisateur peut spécifier dans le champ dnsConfig:

  • nameservers : liste d'adresses IP qui seront utilisées comme serveurs DNS pour le Pod. Il peut y avoir au plus 3 adresses IP spécifiées. Quand le champ dnsPolicy du Pod est mis à "None", la liste doit contenir au moins une adresse IP, sinon cette propriété est facultative.   Les serveurs listés seront combinés avec les nameservers (serveurs de noms) de base générés à partir de la stratégie DNS spécifiée, tout en supprimant les adresses en double.
  • searches : liste des domaines de recherche DNS pour la recherche du nom d'hôte dans le pod.   Cette propriété est facultative. Si elle est spécifiée, la liste fournie sera fusionnée avec les noms de domaine de recherche de base générés à partir de la stratégie DNS choisie.   Les noms de domaine en double sont supprimés.   Kubernetes permet au plus 6 domaines de recherche.
  • options: une liste optionnelle d'objets où chaque objet peut avoir une propriété name (obligatoire) et une propriété value (facultatif). Le contenu de cette propriété sera fusionné avec les options générées à partir de la stratégie DNS spécifiée.   Les entrées en double sont supprimées.

Voici un exemple de Pod avec des configurations DNS personnalisées :

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: exemple-dns
spec:
  containers:
    - name: test
      image: nginx
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4
    searches:
      - ns1.svc.cluster.local
      - mon.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

Lorsque le Pod ci-dessus est créé, le conteneur test obtient le contenu suivant dans son fichier /etc/resolv.conf :

nameserver 1.2.3.4
search ns1.svc.cluster.local mon.dns.search.suffix
options ndots:2 edns0

Pour la configuration IPv6, le chemin de recherche et le serveur de noms doivent être configurés comme suit :

$ kubectl exec -it exemple-dns -- cat /etc/resolv.conf
nameserver fd00:79:30::a
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

A suivre

Pour obtenir des recommendations sur l’administration des configurations DNS, consultez Configurer le service DNS

5.4 - Ingress

Un Ingress est un objet Kubernetes qui gère l'accès externe aux services dans un cluster, généralement du trafic HTTP.

Un Ingress peut fournir un équilibrage de charge, une terminaison TLS et un hébergement virtuel basé sur un nom.

Terminologie

Par souci de clarté, ce guide définit les termes suivants :

  • Nœud (Node) : une seule machine virtuelle ou physique dans un cluster Kubernetes.
  • Cluster : groupe de nœuds protégés par un pare-feu du trafic provenant d'Internet et constituant les principales ressources de calcul gérées par Kubernetes.
  • Routeur Edge : routeur appliquant la stratégie de pare-feu pour votre cluster. Il peut s’agir d’une passerelle gérée par un fournisseur de cloud ou d’un matériel physique.
  • Réseau de cluster : ensemble de liens, logiques ou physiques, facilitant la communication au sein d'un cluster selon le modèle de réseau Kubernetes.
  • Service : un Kubernetes Service identifiant un ensemble de pods à l'aide de sélecteurs d'étiquettes. Sauf indication contraire, les services sont supposés avoir des adresses IP virtuelles routables uniquement dans le réseau du cluster.

Qu'est-ce qu'un Ingress ?

Ingress (ou une entrée réseau), ajouté à Kubernetes v1.1, expose les routes HTTP et HTTPS de l'extérieur du cluster à des services au sein du cluster. Le routage du trafic est contrôlé par des règles définies sur la ressource Ingress.

    internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

Un Ingress peut être configuré pour donner aux services des URLs accessibles de l'extérieur, un équilibrage du trafic de charge externe, la terminaison SSL/TLS et un hébergement virtuel basé sur le nom. Un contrôleur d'Ingress est responsable de l'exécution de l'Ingress, généralement avec un load-balancer (équilibreur de charge), bien qu'il puisse également configurer votre routeur périphérique ou des interfaces supplémentaires pour aider à gérer le trafic.

Un Ingress n'expose pas de ports ni de protocoles arbitraires. Exposer des services autres que HTTP et HTTPS à Internet généralement utilise un service de type Service.Type=NodePort ou Service.Type=LoadBalancer.

Conditions préalables

FEATURE STATE: Kubernetes v1.1 [beta]

Avant de commencer à utiliser un Ingress, vous devez comprendre certaines choses. Un Ingress est une ressource en "version Beta".

GCE/GKE (Google Cloud Engine / Google Kubernetes Engine) déploie un contrôleur d’Ingress sur le master (le maître de kubernetes). Revoir les limitations beta de ce contrôleur si vous utilisez GCE/GKE.

Dans les environnements autres que GCE/GKE, vous devrez peut-être déployer un contrôleur d'Ingress. Il y a un certain nombre de contrôleurs d'Ingress parmi lesquels vous pouvez choisir.

Avant de commencer

Dans l’idéal, tous les contrôleurs d’Ingress devraient correspondre à cette spécification. Cependant le fonctionnement est légèrement différent d'un contrôleur à un autre (en fonction de son implémentation).

La ressource Ingress

Exemple de ressource Ingress minimale :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Comme pour toutes les autres ressources Kubernetes, un Ingress (une entrée) a besoin des champs apiVersion, kind et metadata.  Pour des informations générales sur l'utilisation des fichiers de configuration, voir déployer des applications, configurer des conteneurs, gestion des ressources.  Ingress utilise fréquemment des annotations pour configurer certaines options en fonction du contrôleur Ingress, dont un exemple  est l'annotation rewrite-target.  Différents Ingress controller prennent en charge différentes annotations. Consultez la documentation du contrôleur Ingress de votre choix pour savoir quelles annotations sont prises en charge.

La spécification de la ressource Ingress dispose de toutes les informations nécessaires pour configurer un loadbalancer ou un serveur proxy. Plus important encore, il contient une liste de règles d'appariement de toutes les demandes entrantes. La ressource Ingress ne supporte que les règles pour diriger le trafic HTTP.

Ingress rules

Chaque règle http contient les informations suivantes :

  • Un hôte optionnel. Dans cet exemple, aucun hôte n'est spécifié. La règle s'applique donc à tous les appels entrants.   Le trafic HTTP via l'adresse IP est spécifié. Si un hôte est fourni (par exemple,   foo.bar.com), les règles s’appliquent à cet hôte.
  • une liste de chemins (par exemple, /testpath), chacun étant associé à un backend associé défini par un serviceName et servicePort. L’hôte et le chemin doivent correspondre au contenu d’une demande entrante avant que le load-balancer ne dirige le trafic vers le service référencé.
  • Un backend est une combinaison de noms de services et de ports, comme décrit dans services doc. Les requêtes HTTP (et HTTPS) envoyées à l'Ingress correspondant à l'hôte et au chemin de la règle seront envoyées au backend indiqué.

Un backend par défaut est souvent configuré dans un contrôleur d’Ingress qui traite toutes les demandes qui ne correspondent à aucun chemin dans la spécification.

Backend par défaut

Un Ingress sans règles envoie tout le trafic à un seul backend par défaut. Le backend par défaut est généralement une option de configuration du Contrôleur d'ingress et n'est pas spécifié dans vos ressources Ingress.

Si aucun des hôtes ou chemins ne correspond à la demande HTTP dans les objets Ingress, le trafic est routé vers votre backend par défaut.

Types d'Ingress

Ingress pour service unique

Il existe des concepts Kubernetes qui vous permettent d’exposer un seul service. (voir alternatives). Vous pouvez également le faire avec un Ingress en spécifiant un backend par défaut sans règles.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  defaultBackend:
    service:
      name: testsvc
      port:
        number: 80

Si vous le créez en utilisant kubectl create -f, vous devriez voir :

kubectl get ingress test-ingress
NAME           HOSTS     ADDRESS           PORTS     AGE
test-ingress   *         107.178.254.228   80        59s

107.178.254.228 est l’adresse IP allouée par le contrôleur d’Ingress pour satisfaire cette entrée.

Fanout simple

Une configuration de type fanout achemine le trafic d'une adresse IP unique vers plusieurs services, en se basant sur l'URI HTTP demandée. Une entrée vous permet de garder le nombre de loadbalancers au minimum. Par exemple, une configuration comme :

foo.bar.com -> 178.91.123.132 -> / foo    service1:4200
                                 / bar    service2:8080

ceci nécessitera un Ingress défini comme suit :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-fanout-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 4200
      - path: /bar
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 8080

Lorsque vous créez l'ingress avec kubectl create -f:

kubectl describe ingress simple-fanout-example
Name:             simple-fanout-example
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:4200 (10.8.0.90:4200)
               /bar   service2:8080 (10.8.0.91:8080)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     22s                loadbalancer-controller  default/test

Le contrôleur d’Ingress fournit une implémentation spécifique aux load-balancers qui satisfait l'Ingress, tant que les services (s1, s2) existent. Lorsque cela est fait, vous pouvez voir l’adresse du load-balancer sur le champ d'adresse.

Hébergement virtuel basé sur le nom

Les hôtes virtuels basés sur des noms prennent en charge le routage du trafic HTTP vers plusieurs noms d'hôte basés sur la même adresse IP.

foo.bar.com --|                 |-> foo.bar.com s1:80
              | 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com s2:80

L’Ingress suivant indique au load-balancer de router les requêtes en fonction de En-tête du hôte.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: bar.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 80

Si vous créez une ressource Ingress sans aucun hôte défini dans les règles, tout trafic Web à destination de l'adresse IP de votre contrôleur d'Ingress peut être mis en correspondance sans qu'un hôte virtuel basé sur le nom ne soit requis. Par exemple, la ressource Ingress suivante acheminera le trafic demandé pour first.bar.com au service1, second.foo.com au service2, et à tout trafic à l'adresse IP sans nom d'hôte défini dans la demande (c'est-à-dire sans en-tête de requête présenté) au service3.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: first.bar.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: second.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 80
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service3
            port:
              number: 80

TLS

Vous pouvez sécuriser un Ingress en définissant un secret qui contient une clé privée et un certificat TLS. Actuellement, l'Ingress prend seulement en charge l'unique port TLS, 443, et suppose une terminaison TLS. Si la section de configuration TLS dans un Ingress spécifie différents hôtes, ils seront multiplexés sur le même port en fonction du nom d’hôte spécifié via l'extension SNI TLS (à condition que le contrôleur d’Ingress prenne en charge SNI). Le secret de TLS doit contenir les clés tls.crt et tls.key contenant le certificat et clé privée à utiliser pour TLS, par exemple :

apiVersion: v1
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
type: kubernetes.io/tls

Référencer ce secret dans un Ingress indiquera au contrôleur d'Ingress de sécuriser le canal du client au load-balancer à l'aide de TLS. Vous devez vous assurer que le secret TLS que vous avez créé provenait d'un certificat contenant un Common Name (CN), aussi appelé nom de domaine pleinement qualifié (FQDN), pour https-example.foo.com.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - https-example.foo.com
    secretName: testsecret-tls
  rules:
  - host: https-example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80

L'équilibrage de charge

Un contrôleur d’Ingress est démarré avec certains paramètres de politique d’équilibrage de charge qui s'appliquent à toutes les entrées, tels que l'algorithme d'équilibrage de la charge, le régime de pondérations des backends, et d'autres. Les concepts un peu plus avancés d'équilibrage de charge (p. ex. sessions persistantes, pondérations dynamiques) ne sont pas encore exposés pour l'Ingress. Vous pouvez toujours obtenir ces fonctionnalités via le service loadbalancer.

Il est également intéressant de noter que même si les health checks (contrôles de santé) ne sont pas exposés directement via l'Ingress, il existe des concepts parallèles dans Kubernetes, tels que readiness probes qui vous permettent d'obtenir le même résultat final. Veuillez consulter les documents spécifiques au contrôleur pour voir comment il gère les health checks. (nginx,GCE).

Mise à jour d'un Ingress

Pour mettre à jour un Ingress existant afin d'ajouter un nouvel hôte, vous pouvez le mettre à jour en modifiant la ressource :

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   s1:80 (10.8.0.90:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     35s                loadbalancer-controller  default/test
kubectl edit ingress test

Cela devrait faire apparaître un éditeur avec le yaml existant, modifiez-le pour inclure le nouvel hôte :

spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          service:
            name: s1
            port:
              number: 80
        path: /foo
        pathType: Prefix
  - host: bar.baz.com
    http:
      paths:
      - backend:
          service:
            name: s2
            port:
              number: 80
        path: /foo
        pathType: Prefix
..

L'enregistrement du yaml mettra à jour la ressource dans le serveur d'API, ce qui devrait indiquer au contrôleur d'Ingress de reconfigurer le load-balancer.

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   s1:80 (10.8.0.90:80)
  bar.baz.com
               /foo   s2:80 (10.8.0.91:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     45s                loadbalancer-controller  default/test

Vous pouvez obtenir le même résultat en appelant kubectl replace -f sur un fichier Ingress yaml modifié.

Échec dans les zones de disponibilité

Les techniques permettant de répartir le trafic sur plusieurs domaines de défaillance diffèrent d'un fournisseur de cloud à l'autre. Veuillez consulter la documentation du Contrôleur d'ingress pour plus de détails. Vous pouvez également vous référer à la documentation de la fédération pour plus d'informations sur le déploiement d'Ingress dans un cluster fédéré.

Travail futur

Suivez SIG network (groupe d'intérêt spécial Réseau) pour plus de détails sur l'évolution de l'Ingress et des ressources associées. Vous pouvez également suivre le Dépôt Ingress pour plus de détails sur l'évolution des différents contrôleurs d’Ingress.

Alternatives

Vous pouvez exposer un service de plusieurs manières sans impliquer directement la ressource Ingress :

A suivre

6 - Stockage

6.1 - Volumes

Les fichiers sur disque dans un conteneur sont éphémères, ce qui présente des problèmes pour des applications non-triviales lorsqu'elles s'exécutent dans des conteneurs. Premièrement, lorsqu'un conteneur plante, kubelet va le redémarrer mais les fichiers seront perdus - le conteneur démarre avec un état propre. Deuxièmement, lorsque plusieurs conteneurs s'exécutent ensemble dans un Pod, il est souvent nécessaire de partager des fichiers entre ces conteneurs. L'abstraction Kubernetes Volume résout ces deux problèmes.

Une connaissance des Pods est suggérée.

Contexte

Docker a également un concept de volumes, bien qu'il soit, dans une certaine mesure, plus relâché et moins géré. Avec Docker, un volume est simplement un dossier sur le disque ou dans un autre conteneur. Les durées de vie ne sont pas gérées et, jusqu'à très récemment, seuls les volumes supportés par un disque local l'étaient. Docker fournit maintenant des pilotes de volume, mais la fonctionnalité est très limitée pour le moment (par exemple, à partir de Docker 1.7, seulement un pilote de volume est autorisé par conteneur et il n'est pas possible de passer des paramètres aux volumes).

Un volume Kubernetes, en revanche, a une durée de vie explicite - la même que le Pod qui l'inclut. Par conséquent, un volume survit aux conteneurs qui s'exécutent à l'intérieur du Pod et les données sont préservées lorsque le conteneur redémarre. Bien sûr, lorsqu'un Pod cesse d'exister, le volume va également cesser d'exister. Peut-être plus important encore, Kubernetes supporte de nombreux types de volumes et un Pod peut en utiliser plusieurs simultanément.

À la base, un volume est juste un dossier, contenant possiblement des données, qui est accessible aux conteneurs dans un Pod. La manière dont ce dossier est créé, le support qui le sauvegarde et son contenu sont déterminés par le type de volume utilisé.

Pour utiliser un volume, un Pod spécifie les volumes à fournir au Pod (le champ .spec.volumes) et où les monter dans les conteneurs (le champ .spec.containers.volumeMounts).

Un processus dans un conteneur a une vue système de fichiers composée de son image et de ses volumes Docker. L'image Docker est à la racine de la hiérarchie du système de fichiers et tous les volumes sont montés sur les chemins spécifiés dans l'image. Les volumes ne peuvent pas être montés sur d'autres volumes ou avoir des liens physiques vers d'autres volumes. Chaque conteneur dans le Pod doit spécifier indépendamment où monter chaque volume.

Types de Volumes

Kubernetes supporte plusieurs types de Volumes:

Toute contribution supplémentaire est la bienvenue.

awsElasticBlockStore

Un type de volume awsElasticBlockStore monte un Volume EBS d'Amazon Web Services (AWS) dans un Pod. À la différence de emptyDir, qui est écrasé lorsqu'un Pod est supprimé, le contenu d'un volume EBS est préservé et le volume est seulement démonté. Cela signifie qu'un volume EBS peut être prérempli avec des données et que les données peuvent être transmises entre les Pods.

Des restrictions existent lorsque l'on utilise un volume awsElasticBlockStore :

  • les nœuds dans lesquels les Pods s'exécutent doivent être des instances AWS EC2
  • ces instances doivent être dans la même région et la même zone de disponibilité que le volume EBS
  • EBS supporte uniquement le montage d'un volume par une seule instance EC2

Création d'un volume EBS

Avant que vous puissiez utiliser un volume EBS dans un Pod, vous devez le créer.

aws ec2 create-volume --availability-zone=eu-west-1a --size=10 --volume-type=gp2

Assurez-vous que la zone correspond à la zone de votre grappe de serveurs (cluster). (Et vérifiez aussi que la taille et le type du volume EBS conviennent à votre utilisation!)

Exemple de configuration AWS EBS

apiVersion: v1
kind: Pod
metadata:
  name: test-ebs
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-ebs
      name: test-volume
  volumes:
  - name: test-volume
    # Ce volume AWS EBS doit déjà exister.
    awsElasticBlockStore:
      volumeID: <volume-id>
      fsType: ext4

Migration CSI

FEATURE STATE: Kubernetes v1.14 [alpha]

La fonctionnalité de migration CSI pour awsElasticBlockStore, lorsque activée, fixe toutes les opérations de plugin depuis le plugin "in-tree" vers le pilote de l'interface CSI (Container Storage Interface) ebs.csi.aws.com. Afin d'utiliser cette fonctionnalité, le Pilote AWS EBS CSI doit être installé dans le cluster et les fonctionnalités Alpha CSIMigration et CSIMigrationAWS doivent être activées.

azureDisk

Un type de volume azureDisk est utilisé pour monter un disque de données (Data Disk) dans un Pod.

Plus de détails sont disponibles ici.

Migration CSI

FEATURE STATE: Kubernetes v1.15 [alpha]

La fonctionnalité de migration CSI pour azureDisk, lorsque activée, fixe toutes les opérations de plugin depuis le plugin "in-tree" vers le pilote de l'interface CSI (Container Storage Interface) disk.csi.azure.com. Afin d'utiliser cette fonctionnalité, le Pilote Azure Disk CSI doit être installé dans le cluster et les fonctionnalités Alpha CSIMigration et CSIMigrationAzureDisk doivent être activées.

azureFile

Un type de volume azureFile est utilisé pour monter un volume de fichier Microsoft Azure (SMB 2.1 et 3.0) dans un Pod.

Plus de détails sont disponibles ici.

Migration CSI

FEATURE STATE: Kubernetes v1.15 [alpha]

La fonctionnalité de migration CSI pour azureFile, lorsque activée, fixe toutes les opérations de plugin depuis le plugin "in-tree" vers le pilote de l'interface CSI (Container Storage Interface) file.csi.azure.com. Afin d'utiliser cette fonctionnalité, le Pilote Azure File CSI doit être installé dans le cluster et les fonctionnalités Alpha CSIMigration et CSIMigrationAzureFile doivent être activées.

cephfs

Un volume cephfs permet de monter un volume CephFS existant dans un Pod. Contrairement à emptyDir, qui est écrasé quand un Pod est supprimé, le contenu d'un volume cephfs est préservé et le volume est simplement démonté. Cela signifie qu'un volume CephFS peut être prérempli avec des données et ces données peuvent être transmises entre les Pods. CephFS peut être monté plusieurs fois en écriture simultanément.

Voir l'exemple CephFS pour plus de détails.

cinder

cinder est utilisé pour monter un volume Cinder OpenStack dans un Pod.

Exemple de configuration d'un volume Cinder

apiVersion: v1
kind: Pod
metadata:
  name: test-cinder
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-cinder-container
    volumeMounts:
    - mountPath: /test-cinder
      name: test-volume
  volumes:
  - name: test-volume
    # Ce volume OpenStack doit déjà exister.
    cinder:
      volumeID: <volume-id>
      fsType: ext4

Migration CSI

FEATURE STATE: Kubernetes v1.14 [alpha]

La fonctionnalité de migration CSI pour Cinder, lorsque activée, fixe toutes les opérations de plugin depuis le plugin "in-tree" vers le pilote de l'interface CSI (Container Storage Interface) cinder.csi.openstack.org. Afin d'utiliser cette fonctionnalité, le Pilote Cinder CSI doit être installé dans le cluster et les fonctionnalités Alpha CSIMigration et CSIMigrationOpenStack doivent être activées.

configMap

La ressource configMap fournit un moyen d'injecter des données de configuration dans les Pods. Les données stockées dans un objet ConfigMap peuvent être référencées dans un volume de type configMap et être ensuite consommées par des applications conteneurisées s'exécutant dans un Pod.

Lorsque l'on référence un objet configMap, on peut simplement fournir son nom dans le volume pour le référencer. On peut également personnaliser le chemin pour utiliser une entrée spécifique dans la ConfigMap. Par exemple, pour monter la ConfigMap log-config sur un Pod appelé configmap-pod, vous pourriez utiliser le YAML suivant :

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: log-config
        items:
          - key: log_level
            path: log_level

La ConfigMap log-config est montée comme un volume et tout le contenu stocké dans son entrée log_level est monté dans le Pod au chemin "/etc/config/log_level". À noter que ce chemin est dérivé du mountPath du volume et le path est étiqueté avec la clef log_level.

downwardAPI

Un volume downwardAPI est utilisé pour rendre disponibles aux applications les données de l'API Downward. Il monte un dossier et écrit les données demandées dans des fichiers de texte brut.

Voir l'exemple de volume downwardAPI pour plus de détails.

emptyDir

Un volume emptyDir est d'abord créé lorsqu'un Pod est assigné à un nœud et existe aussi longtemps que le Pod s'exécute sur ce nœud. Comme le nom l'indique, le volume est initialement vide. Les conteneurs dans le Pod peuvent tous lire et écrire les mêmes fichiers dans le volume emptyDir, bien que ce volume puisse être monté sur le même ou différents chemins dans chaque conteneur. Lorsqu'un Pod est supprimé d'un nœud pour une raison quelconque, les données dans le emptyDir sont supprimées à jamais.

Des cas d'utilisation pour un emptyDir peuvent être :

  • un espace de travail, par exemple pour un tri fusion sur disque.
  • l'établissement d'un point de reprise d'un long calcul à des fins de récupération des données après un crash.
  • le stockage de fichiers qu'un conteneur de gestion de contenu va chercher pendant qu'un conteneur serveur web expose les données.

Par défaut, les volumes emptyDir sont stockés sur tout médium supporté par le nœud - que ce soit un disque dur, un disque SSD ou un stockage réseau, dépendamment de l'environnement. Cependant, vous pouvez définir le champ emptyDir.medium à "Memory" pour indiquer à Kubernetes de monter un tmpfs (système de fichiers supporté par la RAM) pour vous à la place. Tandis que tmpfs est très rapide, soyez conscient qu'au contraire des disques, un tmpfs est effacé au redémarrage du nœud et tous les fichiers que vous écrivez seront comptabilisés dans la limite de mémoire de votre conteneur.

Exemple de Pod

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

fc (fibre channel)

Un volume fc permet à un volume Fibre Channel existant d'être monté dans un Pod. Vous pouvez spécifier une ou plusieurs cibles World Wide Names en utilisant le paramètre targetWWNs dans votre configuration de volume. Si plusieurs WWNs sont spécifiés, targetWWNs s'attend à ce que ces WWNs proviennent de connexions multi-path.

Voir l'exemple FC pour plus de détails.

flocker

Flocker est un gestionnaire de volumes de données en cluster open-source. Il assure la gestion et l'orchestration de volumes de données supportés par divers serveurs de stockage.

Un volume flocker permet de monter un ensemble de données Flocker dans un Pod. Si l'ensemble de données n'existe pas déjà dans Flocker, il doit d'abord être créé avec la CLI Flocker ou en utilisant l'API Flocker. Si l'ensemble de données existe déjà, il sera réattaché par Flocker au nœud sur lequel le Pod est planifié. Cela signifie que les données peuvent être transmises entre les Pods selon les besoins.

Voir l'exemple Flocker pour plus de détails.

gcePersistentDisk

Un volume gcePersistentDisk monte un Disque Persistant Google Compute Engine (GCE) dans un Pod. À la différence d'un emptyDir, qui est écrasé lorsqu'un Pod est supprimé, le contenu d'un disque persistant est préservé et le volume est simplement démonté. Cela signifie qu'un disque persistant peut être prérempli avec des données et que ces données peuvent être transmises entre les Pods.

Des restrictions existent lors de l'utilisation d'un gcePersistentDisk:

  • les nœuds sur lesquels les Pods s'exécutent doivent être des machines virtuelles (VMs) GCE.
  • ces VMs doivent se trouver dans le même projet et la même zone GCE que le disque persistant

Une fonctionnalité des disques persistants est qu'ils peuvent être montés en lecture seule par plusieurs consommateurs simultanément. Cela signifie que vous pouvez préremplir un disque persistant avec votre jeu de données et l'exposer en parallèle à partir d'autant de Pods que nécessaire. Malheureusement, les disques persistants peuvent seulement être montés par un seul consommateur en mode lecture-écriture - les écritures simultanées ne sont pas autorisées.

Utiliser un disque persistant dans un Pod contrôlé par un ReplicationController échouera à moins que le disque persistant soit en lecture seule ou que le nombre de répliques soit de 0 ou 1.

Création d'un disque persistant

Avant de pouvoir utiliser un disque persistant GCE avec un Pod, vous devez le créer.

gcloud compute disks create --size=500GB --zone=us-central1-a my-data-disk

Exemple de Pod

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    # Ce disque persistant GCE doit déjà exister.
    gcePersistentDisk:
      pdName: my-data-disk
      fsType: ext4

Disques persistants régionaux

FEATURE STATE: Kubernetes v1.10 [beta]

La fonctionnalité de disques persistants régionaux (Regional Persistent Disks) permet la création de disques persistants disponibles dans deux zones à l'intérieur d'une même région. Afin d'utiliser cette fonctionnalité, le volume doit être provisionné en tant que PersistentVolume; le référencement du volume directement depuis un Pod n'est pas supporté.

Provisionnement manuel d'un disque persistant régional en tant que PersistentVolume

Le provisionnement dynamique est possible en utilisant une StorageClass pour un disque persistant GCE. Avant de créer un PersistentVolume, vous devez créer le disque persistant :

gcloud beta compute disks create --size=500GB my-data-disk
    --region us-central1
    --replica-zones us-central1-a,us-central1-b

Exemple de spec PersistentVolume :

apiVersion: v1
kind: PersistentVolume
metadata:
  name: test-volume
  labels:
    failure-domain.beta.kubernetes.io/zone: us-central1-a__us-central1-b
spec:
  capacity:
    storage: 400Gi
  accessModes:
  - ReadWriteOnce
  gcePersistentDisk:
    pdName: my-data-disk
    fsType: ext4

Migration CSI

FEATURE STATE: Kubernetes v1.14 [alpha]

La fonctionnalité de migration CSI pour un disque persistant GCE, lorsque activée, fixe toutes les opérations de plugin depuis le plugin "in-tree" vers le pilote de l'interface CSI (Container Storage Interface) pd.csi.storage.gke.io. Afin d'utiliser cette fonctionnalité, le Pilote CSI de disque persistant GCE doit être installé dans le cluster et les fonctionnalités Alpha CSIMigration et CSIMigrationGCE doivent être activées.

gitRepo (obsolète)

Un volume gitRepo est un exemple de ce qui peut être réalisé en tant que plugin de volume. Cela monte un dossier vide et clone un dépôt git à l'intérieur, à la disposition d'un Pod. Dans le futur, de tels volumes pourraient être déplacé vers un modèle encore plus découplé plutôt qu'étendre l'API Kubernetes pour chaque cas d'utilisation.

Voici un exemple d'un volume gitRepo :

apiVersion: v1
kind: Pod
metadata:
  name: server
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /mypath
      name: git-volume
  volumes:
  - name: git-volume
    gitRepo:
      repository: "git@somewhere:me/my-git-repository.git"
      revision: "22f1d8406d464b0c0874075539c1f2e96c253775"

glusterfs

Un volume glusterfs permet à un volume Glusterfs (un système de fichiers en réseau open source) d'être monté dans un Pod. À la différence d'un emptyDir, qui est écrasé lorsqu'un Pod est supprimé. le contenu d'un volume glusterfs est préservé et le volume est simplement démonté. Cela signifie qu'un volume glusterfs peut être prérempli avec des données et que ces données peuvent être transmises entre les Pods. GlusterFS peut être monté plusieurs fois en écriture simultanément.

Voir l'exemple GlusterFS pour plus de détails.

hostPath

Un volume hostPath monte un fichier ou un dossier depuis le système de fichiers du nœud hôte à l'intérieur d'un Pod. Ce ne sera pas requis pour la plupart des Pods, mais cela offre une puissante solution de secours pour certaines applications.

Par exemple, des utilisations du hostPath peuvent être :

  • exécuter un conteneur qui nécessite l'accès aux éléments internes de Docker; utiliser un hostPath de /var/lib/docker
  • exécuter cAdvisor dans un conteneur; utiliser un hostPath de /sys
  • autoriser un Pod à spécifier si un hostPath donné devrait exister avant la mise en exécution du Pod, s'il devrait être créé et en tant que quoi il devrait exister.

En plus de la propriété requise path, un utilisateur peut optionnellement spécifier un type pour un volume hostPath.

Les valeurs supportées pour le champ type sont les suivantes :

Valeur Comportement
Une chaîne de caractères vide (par défaut) sert à la rétrocompatibilité, ce qui signifie qu'aucune vérification ne sera effectuée avant de monter le volume hostPath.
DirectoryOrCreate Si rien n'existe au chemin fourni, un dossier vide y sera créé au besoin avec les permissions définies à 0755, avec le même groupe et la même possession que Kubelet.
Directory Un dossier doit exister au chemin fourni
FileOrCreate Si rien n'existe au chemin fourni, un fichier vide y sera créé au besoin avec les permissions définies à 0644, avec le même groupe et la même possession que Kubelet.
File Un fichier doit exister au chemin fourni
Socket Un socket UNIX doit exister au chemin fourni
CharDevice Un périphérique en mode caractère doit exister au chemin fourni
BlockDevice Un périphérique en mode bloc doit exister au chemin fourni

Une attention particulière doit être portée lors de l'utilisation de ce type de volume car :

  • les Pods avec une configuration identique (tels que ceux créés depuis un podTemplate) peuvent se comporter différemment sur des nœuds différents à cause de fichiers différents sur les nœuds.
  • lorsque Kubernetes ajoute une planification tenant compte des ressources, comme prévu, il ne pourra pas prendre en compte les ressources utilisées par un hostPath.
  • les fichiers ou dossiers créés sur les hôtes sous-jacents ne sont accessibles en écriture que par root. Vous devez soit exécuter votre programme en tant que root dans un conteneur privilégié ou modifier les permissions du fichier sur l'hôte pour pouvoir écrire dans un volume hostPath.

Exemple de Pod

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # chemin du dossier sur l'hôte
      path: /data
      # ce champ est optionnel
      type: Directory

iscsi

Un volume iscsi permet à un volume existant iSCSI (SCSI over IP) d'être monté dans un Pod. À la différence d'un emptyDir, qui est écrasé lorsqu'un Pod est supprimé, le contenu d'un volume iscsi est préservé et le volume est simplement démonté. Cela signifie qu'un volume iscsi peut être prérempli avec des données que ces données peuvent être transmises entre les Pods.

Une fonctionnalité de iSCSI est qu'il peut être monté en lecture seule par plusieurs consommateurs simultanément. Cela signifie que vous pouvez préremplir un volume avec votre jeu de données et l'exposer en parallèle à partir d'autant de Pods que nécessaire. Malheureusement, les volumes iSCSI peuvent seulement être montés par un seul consommateur en mode lecture-écriture - les écritures simultanées ne sont pas autorisées.

Voir l'exemple iSCSI pour plus de détails.

local

FEATURE STATE: Kubernetes v1.14 [stable]

Un volume local représente un périphérique de stockage local monté tels qu'un disque, une partition ou un dossier.

Les volumes locaux peuvent seulement être utilisés comme un PersistentVolume créé statiquement. Le provisionnement dynamique n'est pas encore supporté.

Comparés aux volumes hostPath, les volumes locaux peuvent être utilisés de manière durable et portable sans planifier manuellement des Pods sur les nœuds, puisque le système est conscient des contraintes de nœud du volume en examinant l'affinité de nœud sur le PersistentVolume.

Toutefois, les volumes locaux sont encore sujets à la disponibilité du nœud sous-jacent et ne conviennent pas à toutes les applications. Si un nœud devient "en mauvaise santé" (unhealthy), alors le volume local deviendra également inaccessible et un Pod qui l'utilise ne sera pas en mesure de s'exécuter. Les applications qui utilisent des volumes locaux doivent être en mesure de tolérer cette disponibilité réduite, ainsi que de potentielles pertes de données, dépendamment des caractéristiques de durabilité du disque sous-jacent.

L'exemple suivant traite d'une spec d'un PersistentVolume utilisant un volume local et une nodeAffinity:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Gi
  # le champ volumeMode requiert l'activation de la "feature gate" Alpha BlockVolume
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node

La nodeAffinity d'un PersistentVolume est requise lors de l'utilisation de volumes locaux. Cela permet au planificateur (scheduler) Kubernetes de planifier correctement des Pods utilisant des volumes locaux aux bons nœuds.

Le volumeMode d'un PersistentVolume peut maintenant être configuré à "Block" (au lieu de la valeur par défaut "Filesystem") pour exposer le volume local en tant que périphérique bloc brut (raw block device). Le champ volumeMode requiert l'activation de la "feature gate" Alpha BlockVolume.

Lors de l'utilisation des volumes locaux, il est recommandé de créer une StorageClass avec volumeBindingMode configuré à WaitForFirstConsumer. Voir l'exemple. Retarder la liaison (binding) du volume garantit que la décision de liaison du PersistentVolumeClaim sera également évaluée avec toutes les autres contraintes de nœud que le Pod peut avoir, tels que les exigences en ressources du nœud, les sélecteurs de nœud, leur affinité et leur anti-affinité.

Un provisionneur statique externe peut être exécuté séparément pour une gestion améliorée du cycle de vie du volume local. Noter que ce provisionneur ne supporte pas encore le provisionnement dynamique. Pour un exemple sur la façon d'exécuter un provisionneur externe local, voir le guide utilisateur de provisionneur de volume local.

nfs

Un volume nfs permet à un partage NFS (Network File System) existant d'être monté dans un Pod. À la différence d'un emptyDir, qui est écrasé lorsqu'un Pod est supprimé, le contenu d'un volume nfs est préservé et le volume est simplement démonté. Cela signifie qu'un volume NFS peut être prérempli avec des données et que les données peuvent être transmises entre les Pods. NFS peut être monté plusieurs fois en écriture simultanément.

Voir l'exemple NFS pour plus de détails.

persistentVolumeClaim

Un volume persistentVolumeClaim est utilisé pour monter un PersistentVolume dans un Pod. Les PersistentVolumes sont une manière pour les utilisateurs de "revendiquer" un stockage durable (comme un PersistentDisk GCE ou un volume iSCSI) sans savoir les détails d'un environnement cloud particulier.

Voir l'exemple PersistentVolumes pour plus de détails.

projected

Un volume projected mappe plusieurs sources de volume existantes dans le même dossier.

Actuellement, les types de sources de volume suivantes peuvent être projetés :

Toutes les sources doivent se trouver dans le même namespace que celui du Pod. Pour plus de détails, voir le document de conception tout-en-un .

La projection des jetons de compte de service (service account) est une fonctionnalité introduite dans Kubernetes 1.11 et promue en Beta dans la version 1.12. Pour activer cette fonctionnalité dans la version 1.11, il faut configurer explicitement la "feature gate" TokenRequestProjection à "True".

Exemple d'un Pod avec un secret, une API downward et une configmap.

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
  - name: container-test
    image: busybox
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: mysecret
          items:
            - key: username
              path: my-group/my-username
      - downwardAPI:
          items:
            - path: "labels"
              fieldRef:
                fieldPath: metadata.labels
            - path: "cpu_limit"
              resourceFieldRef:
                containerName: container-test
                resource: limits.cpu
      - configMap:
          name: myconfigmap
          items:
            - key: config
              path: my-group/my-config

Exemple d'un Pod avec plusieurs secrets avec une configuration de mode de permission autre que celle par défaut.

apiVersion: v1
kind: Pod
metadata:
  name: volume-test
spec:
  containers:
  - name: container-test
    image: busybox
    volumeMounts:
    - name: all-in-one
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: all-in-one
    projected:
      sources:
      - secret:
          name: mysecret
          items:
            - key: username
              path: my-group/my-username
      - secret:
          name: mysecret2
          items:
            - key: password
              path: my-group/my-password
              mode: 511

Chaque source de volume projeté est listée dans la spec, sous sources. Les paramètres sont à peu près les mêmes avec deux exceptions :

  • Pour les secrets, le champ secretName a été changé par name pour être consistant avec le nommage des ConfigMap.
  • Le defaultMode peut seulement être spécifié au niveau projeté et non pour chaque source de volume. Cependant, tel qu'illustré au-dessus, il est possible de configurer explicitement le mode pour chaque projection individuelle.

Lorsque la fonctionnalité TokenRequestProjection est activée, vous pouvez injecter le jeton pour le service account courant dans un Pod au chemin spécifié. Ci-dessous, un exemple :

apiVersion: v1
kind: Pod
metadata:
  name: sa-token-test
spec:
  containers:
  - name: container-test
    image: busybox
    volumeMounts:
    - name: token-vol
      mountPath: "/service-account"
      readOnly: true
  volumes:
  - name: token-vol
    projected:
      sources:
      - serviceAccountToken:
          audience: api
          expirationSeconds: 3600
          path: token

Le pod d'exemple possède un volume projeté contenant le jeton injecté du service account. Ce jeton peut être utilisé par des conteneurs de Pod pour accéder au service d'API Kubernetes API, par exemple. Le champ audience contient l'audience-cible du jeton. Un destinataire du jeton doit s'identifier avec un identificateur spécifié dans l'audience du jeton, sinon il doit rejeter le jeton. Ce champ est facultatif et sa valeur par défaut est l'identifiant du serveur API.

Le champ expirationSeconds est la durée de validité attendue du jeton de service account. Sa valeur par défaut est de 1 heure et doit être au moins de 10 minutes (600 secondes). Un administrateur peut aussi limiter sa valeur maximum en spécifiant l'option --service-account-max-token-expiration pour le serveur API. Le champ path spécifie un chemin relatif au point de montage du volume projeté.

portworxVolume

Un portworxVolume est une couche de stockage bloc élastique qui s'exécute de manière hyperconvergée avec Kubernetes. Portworx donne l'empreinte digitale d'un stockage dans un serveur, tiers basés sur les capacités et agrège la capacité sur plusieurs serveurs. Portworx s'exécute en invité sur des machines virtuelles ou sur des nœuds Linux bare metal.

Un portworxVolume peut être créé dynamiquement à travers Kubernetes ou il peut également être pré-provisionné et référencé à l'intérieur d'un Pod Kubernetes. Voici un exemple de Pod référençant un PortworxVolume pré-provisionné :

apiVersion: v1
kind: Pod
metadata:
  name: test-portworx-volume-pod
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /mnt
      name: pxvol
  volumes:
  - name: pxvol
    # Ce volume Portworx doit déjà exister.
    portworxVolume:
      volumeID: "pxvol"
      fsType: "<fs-type>"

Plus de détails et d'exemples peuvent être trouvé ici.

quobyte

Un volume quobyte permet à un volume existant Quobyte d'être monté dans un Pod.

Quobyte supporte le Container Storage Interface. CSI est le plugin recommandé pour utiliser les volumes Quobyte volumes dans Kubernetes. Le projet GitHub Quobyte dispose d'instructions pour déployer Quobyte en utilisant CSI, avec des exemples.

rbd

Un volume rbd permet à un volume périphérique bloc Rados (Rados Block Device) d'être monté dans un Pod. À la différence d'un emptyDir, qui est écrasé lorsqu'un Pod est supprimé, le contenu d'un volume rbd est préservé et le volume est simplement démonté. Cela signifie qu'un volume RBD peut être prérempli avec des données et que ces données peuvent être transmises entre les Pods.

Une fonctionnalité de RBD est qu'il peut être monté en lecture seule par plusieurs consommateurs simultanément. Cela signifie que vous pouvez préremplir un volume avec votre jeu de données et l'exposer en parallèle à partir d'autant de Pods que nécessaire. Malheureusement, les volumes RBD peuvent seulement être montés par un seul consommateur en mode lecture-écriture - les écritures simultanées ne sont pas autorisées.

Voir l'exemple RBD pour plus de détails.

scaleIO

ScaleIO est une plateforme de stockage logicielle qui peut utiliser du matériel physique existant pour créer des clusters de stockage bloc partagé en réseau évolutif. Le plugin de volume scaleIO permet aux Pods déployés d'accéder à des volumes ScaleIO existants (ou il peut provisionner dynamiquement de nouveaux volumes pour des revendications de volumes persistants, voir ScaleIO Persistent Volumes).

L'exemple suivant montre une configuration de Pod avec ScaleIO :

apiVersion: v1
kind: Pod
metadata:
  name: pod-0
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: pod-0
    volumeMounts:
    - mountPath: /test-pd
      name: vol-0
  volumes:
  - name: vol-0
    scaleIO:
      gateway: https://localhost:443/api
      system: scaleio
      protectionDomain: sd0
      storagePool: sp1
      volumeName: vol-0
      secretRef:
        name: sio-secret
      fsType: xfs

Pour plus de détails, consulter les exemples ScaleIO.

secret

Un volume secret est utilisé pour fournir des informations sensibles, comme des mots de passe, aux Pods. Vous pouvez stocker des secrets dans l'API Kubernetes et les monter en tant que fichiers pour être utilisés par les Pods sans les coupler directement avec Kubernetes. Les volumes secret sont supportés par tmpfs (un système de fichiers en RAM) pour qu'ils ne soient jamais écrits sur du stockage non volatil.

Les secrets sont décrits plus en détails ici.

storageOS

Un volume storageos permet à un volume StorageOS existant d'être monté dans un Pod.

StorageOS s'exécute en tant que conteneur dans l'environnement Kubernetes en rendant le stockage local ou attaché accessible depuis n'importe quel nœud dans le cluster Kubernetes. Les données peuvent être répliquées pour se protéger des défaillances de nœuds. Les techniques d'allocation fine et dynamique et de compression peuvent améliorer l'utilisation et réduire les coûts.

À la base, StorageOS fournit un stockage bloc aux conteneurs accessible via un système de fichiers.

Le conteneur StorageOS requiert Linux 64-bit et n'a pas besoin de dépendances supplémentaires. Une licence développeur libre est disponible.

apiVersion: v1
kind: Pod
metadata:
  labels:
    name: redis
    role: master
  name: test-storageos-redis
spec:
  containers:
    - name: master
      image: kubernetes/redis:v1
      env:
        - name: MASTER
          value: "true"
      ports:
        - containerPort: 6379
      volumeMounts:
        - mountPath: /redis-master-data
          name: redis-data
  volumes:
    - name: redis-data
      storageos:
        # Le volume `redis-vol01` doit déjà exister dans StorageOS, dans le namespace `default`.
        volumeName: redis-vol01
        fsType: ext4

Pour plus d'informations incluant le provisionnement dynamique (Dynamic Provisioning) et les réclamations de volume persistant (Persistent Volume Claims), consulter les exemples StorageOS.

vsphereVolume

Un volume vsphereVolume est utilisé pour monter un volume vSphere VMDK dans un Pod. Le contenu d'un volume est préservé lorsqu'il est démonté. Il supporte les banques de données (datastore) VMFS and VSAN.

Création d'un volume VMDK

Choisir une des méthodes suivantes pour créer un VMDK.

Premièrement, se connecter en ssh dans l'ESX, ensuite, utiliser la commande suivante pour créer un VMDK :

vmkfstools -c 2G /vmfs/volumes/DatastoreName/volumes/myDisk.vmdk

Utiliser la commande suivante pour créer un VMDK:

vmware-vdiskmanager -c -t 0 -s 40GB -a lsilogic myDisk.vmdk

Exemple de configuration vSphere VMDK

apiVersion: v1
kind: Pod
metadata:
  name: test-vmdk
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-vmdk
      name: test-volume
  volumes:
  - name: test-volume
    # Ce volume VMDK doit déjà exister.
    vsphereVolume:
      volumePath: "[DatastoreName] volumes/myDisk"
      fsType: ext4

Plus d'exemples sont disponibles ici.

Utilisation de subPath

Parfois, il est utile de partager un volume pour plusieurs utilisations dans un même Pod. La propriété volumeMounts.subPath peut être utilisée pour spécifier un sous-chemin à l'intérieur du volume référencé au lieu de sa racine.

Voici un exemple d'un Pod avec une stack LAMP (Linux Apache Mysql PHP) utilisant un unique volume partagé. Le contenu HTML est mappé à son dossier html et les bases de données seront stockées dans son dossier mysql :

apiVersion: v1
kind: Pod
metadata:
  name: my-lamp-site
spec:
    containers:
    - name: mysql
      image: mysql
      env:
      - name: MYSQL_ROOT_PASSWORD
        value: "rootpasswd"
      volumeMounts:
      - mountPath: /var/lib/mysql
        name: site-data
        subPath: mysql
    - name: php
      image: php:7.0-apache
      volumeMounts:
      - mountPath: /var/www/html
        name: site-data
        subPath: html
    volumes:
    - name: site-data
      persistentVolumeClaim:
        claimName: my-lamp-site-data

Utilisation d'un subPath avec des variables d'environnement étendues

FEATURE STATE: Kubernetes v1.15 [beta]

Utiliser le champ subPathExpr pour construire des noms de dossier subPath depuis les variables d'environnement de l'API Downward. Avant d'utiliser cette fonctionnalité, vous devez activer la "feature gate" VolumeSubpathEnvExpansion. Les propriétés subPath et subPathExpr sont mutuellement exclusives.

Dans cet exemple, un Pod utilise subPathExpr pour créer un dossier pod1 à l'intérieur du volume hostPath /var/log/pods, en utilisant le nom du pod depuis l'API Downward. Le dossier hôte /var/log/pods/pod1 est monté sur /logs dans le conteneur.

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
  - name: container1
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    image: busybox
    command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
    volumeMounts:
    - name: workdir1
      mountPath: /logs
      subPathExpr: $(POD_NAME)
  restartPolicy: Never
  volumes:
  - name: workdir1
    hostPath:
      path: /var/log/pods

Ressources

Le support de stockage (Disk, SSD, etc.) d'un volume emptyDir est déterminé par le support du système de fichiers contenant le dossier racine de kubelet (typiquement /var/lib/kubelet). Il n'y a pas de limite sur l'espace qu'un volume emptyDir ou hostPath peut consommer et pas d'isolation entre les conteneurs ou entre les Pods.

Dans le futur, il est prévu que les volumes emptyDir et hostPath soient en mesure de demander une certaine quantité d'espace en utilisant une spécification de ressource et de sélectionner un type de support à utiliser, pour les clusters qui ont plusieurs types de support.

Plugins de volume Out-of-Tree

Les plugins de volume Out-of-tree incluent l'interface CSI (Container Storage Interface) et FlexVolume. Ils permettent aux fournisseurs de stockage de créer des plugins de stockage personnalisés sans les ajouter au dépôt Kubernetes.

Avant l'introduction de l'interface CSI et FlexVolume, tous les plugins de volume (tels que les types de volume listés plus haut) étaient "in-tree", ce qui signifie qu'ils étaient construits, liés, compilés et livrés avec les binaires de base Kubernetes et étendent l'API Kubernetes de base. Cela signifiait que l'ajout d'un nouveau système de stockage à Kubernetes (un plugin de volume) requérait de vérifier le code dans le dépôt de base de Kubernetes.

CSI et FlexVolume permettent à des plugins de volume d'être développés indépendamment de la base de code Kubernetes et déployés (installés) sur des clusters Kubernetes en tant qu'extensions.

Pour les fournisseurs de stockage qui cherchent à créer un plugin de volume "out-of-tree", se référer à cette FAQ.

CSI

L'interface Container Storage Interface (CSI) définit une interface standard pour les systèmes d'orchestration de conteneurs (comme Kubernetes) pour exposer des systèmes de stockage arbitraires aux charges de travail de leurs conteneurs.

Pour plus d'informations, lire la proposition de conception CSI.

Le support CSI a été introduit en alpha à partir de Kubernetes v1.9, a évolué en beta dans Kubernetes v1.10 et est en disponibilité générale (GA) depuis Kubernetes v1.13.

Une fois qu'un pilote de volume CSI compatible est déployé dans un cluster Kubernetes, les utilisateurs peuvent utiliser le type de volume csi pour attacher, monter, etc.., les volumes exposés par le pilote CSI.

Le type de volume csi ne supporte pas de référence directe depuis un Pod et ne peut être référencé seulement dans un Pod que par un objet PersistentVolumeClaim.

Les champs suivants sont disponibles aux administrateurs de stockage pour configurer un volume persistant CSI :

  • driver: Une valeur texte qui spécifie le nom du pilote de volume à utiliser. Cette valeur doit correspondre à la valeur retournée dans le GetPluginInfoResponse par le pilote CSI tel que défini dans la spec CSI. Elle est utilisée par Kubernetes pour identifier le pilote CSI à appeler et par les composants du pilote CSI pour identifier quels objets PV appartiennent au pilote CSI.
  • volumeHandle: Une valeur texte qui identifie le volume de manière unique. Cette valeur doit correspondre à la valeur retournée dans le champ volume.id de CreateVolumeResponse par le pilote CSI tel que défini dans la spec CSI. La valeur est passée en tant que volume_id sur tous les appels au pilote de volume CSI lorsque le volume est référencé.
  • readOnly: Une valeur booléenne optionnelle indiquant si le volume doit être "ControllerPublished" (attaché) en lecture seule. La valeur par défaut est "false". Cette valeur est passées au pilote CSI via le champ readonly dans le ControllerPublishVolumeRequest.
  • fsType: Si le VolumeMode du PV est Filesystem, alors ce champ peut être utilisé pour spécifier le système de fichiers qui devrait être utilisé pour monter le volume. Si le volume n'a pas été formaté et que le formatage est supporté, cette valeur sera utilisée pour formater le volume. Cette valeur est passée au pilote CSI driver via le champ VolumeCapability de ControllerPublishVolumeRequest, NodeStageVolumeRequest, et NodePublishVolumeRequest.
  • volumeAttributes: Un tableau associatif (map) string vers string qui spécifie les propriétés statiques d'un volume. Ce tableau associatif doit correspondre à celui retourné dans le champ volume.attributes du CreateVolumeResponse par le pilote CSI tel que défini dans la spec CSI. Le tableau associatif est passé au pilote CSI via le champ volume_attributes dans la ControllerPublishVolumeRequest, NodeStageV olumeRequest, et NodePublishVolumeRequest.
  • controllerPublishSecretRef: Une référence de l'objet de type secret contenant des informations sensibles à passer au driver CSI pour compléter les appels CSI ControllerPublishVolume et ControllerUnpublishVolume. Ce champ est optionnel et peut être vide si aucun secret n'est requis. Si l'objet secret contient plus qu'un secret, tous les secrets sont passés.
  • nodeStageSecretRef: Une référence à l'objet de type secret contenant des informations sensibles à passer au pilote CSI pour compléter l'appel CSI NodeStageVolume. Ce champ est optionnel et peut être vide si aucun secret n'est requis. Si l'objet secret contient plus qu'un secret, tous les secrets sont passés.
  • nodePublishSecretRef: Une référence vers l'objet de type secret contenant des informations sensibles à passer au pilote CSI pour compléter l'appel CSI NodePublishVolume. Ce champ est optionnel et peut être vide si aucun secret n'est requis. Si l'objet secret contient plus qu'un secret, tous les secrets sont passés.

Support de volume bloc brut CSI

FEATURE STATE: Kubernetes v1.14 [beta]

À partir de la version 1.11, CSI a introduit le support des volumes bloc bruts, qui s'appuient sur la fonctionnalité de volume bloc brut introduite dans une version précédente de Kubernetes. Cette fonctionnalité va permettre aux fournisseurs avec des pilotes CSI externes d'implémenter le support pour les volumes bloc bruts dans les charges de travail Kubernetes.

Le support volume bloc CSI est une "feature-gate", mais est activée par défaut. Les deux "feature gates" qui doivent être activées pour cette fonctionnalité sont BlockVolume et CSIBlockVolume.

Apprenez comment configurer votre PV/PVC avec le support de volume bloc brut.

Volumes CSI éphémères

FEATURE STATE: Kubernetes v1.15 [alpha]

Cette fonctionnalité permet aux volumes CSI d'être embarqués directement dans la spécification du Pod au lieu de celle d'un PersistentVolume. Les Volumes spécifiés de cette manière sont éphémères et ne persistent pas lorsque le Pod redémarre.

Exemple :

kind: Pod
apiVersion: v1
metadata:
  name: my-csi-app
spec:
  containers:
    - name: my-frontend
      image: busybox
      volumeMounts:
      - mountPath: "/data"
        name: my-csi-inline-vol
      command: [ "sleep", "1000000" ]
  volumes:
    - name: my-csi-inline-vol
      csi:
        driver: inline.storage.kubernetes.io
        volumeAttributes:
              foo: bar

Cette fonctionnalité requiert l'activation de la "feature gate" CSIInlineVolume :

--feature-gates=CSIInlineVolume=true

Les volumes éphémères CSI sont seulement supportés par un sous-ensemble des pilotes CSI. La liste des pilotes CSI est disponible ici.

Ressources pour développeur

Pour plus d'informations sur la manière de développer un pilote CSI, se référer à la documentation kubernetes-csi

Migration de pilotes CSI depuis des plugins "in-tree"

FEATURE STATE: Kubernetes v1.14 [alpha]

La fonctionnalité de migration CSI, lorsque activée, dirige les opérations sur les plugins "in-tree" existants vers les plugins CSI correspondants (qui sont sensés être installés et configurés). Cette fonctionnalité implémente la logique de translation nécessaire et les fixations nécessaires pour rerouter les opérations de manière transparente. En conséquence, les opérateurs n'ont pas à effectuer de changements de configuration aux classes de stockage (Storage Classes) existantes, PV ou PVC (référençant aux plugins "in-tree") lors de la transition vers un pilote CSI qui remplace un plugin "in-tree".

Dans l'état alpha, les opérations et fonctionnalités qui sont supportées incluent provisionnement/suppression, attachement/détachement, montage/démontage et le redimensionnement des volumes.

Les plugins "in-tree" qui supportent la migration CSI et qui ont un pilote CSI correspondant implémenté sont listés dans la section "Types de volumes" au-dessus.

FlexVolume

FlexVolume est une interface de plugin "out-of-tree" qui existe dans Kubernetes depuis la version 1.2 (avant CSI). Elle utilise un modèle basé sur exec pour s'interfacer avec les pilotes. Les binaires de pilote FlexVolume doivent être installés dans un chemin de volume de plugin prédéfini sur chaque nœud (et dans certains cas le nœud maître).

Les Pods interagissent avec les pilotes FlexVolume à travers le plugin "in-tree" flexvolume Plus de détails sont disponibles ici.

Propagation de montage

La propagation de montage permet à des volumes partagés montés par un conteneur à d'autres conteneurs dans un même Pod, ou même à d'autres Pods dans le même nœud.

La propagation de montage d'un volume est contrôlée par le champ mountPropagation dans Container.volumeMounts. Ses valeurs sont :

  • None - Ce montage de volume ne recevra aucun montage subséquent qui est monté à ce volume ou n'importe lequel de ses sous-dossiers par l'hôte. De la même manière, aucun montage créé par le conteneur ne sera visible sur l'hôte. C'est le mode par défaut.

    Ce mode équivaut à une propagation de montage private tel que décrit dans la documentation du noyau Linux

  • HostToContainer - Ce montage de volume recevra les montages subséquents qui sont montés sur ce volume ou n'importe lequel de ses sous-dossiers.

    En d'autres termes, si l'hôte monte quoi que ce soit dans le montage de volume, le conteneur va le voir monté à cet endroit.

    De manière similaire, si un Pod avec la propagation de montage Bidirectional vers le même volume y monte quoi que ce soit, le conteneur avec la propagation de montage HostToContainer le verra.

    Ce mode est équivalent à la propagation de montage rslave tel que décrit dans la documentation du noyau Linux

  • Bidirectional - Ce montage de volume se comporte de la même manière que le montage HostToContainer. De plus, tous les montages de volume créés par le conteneur seront propagés à l'hôte et à tous les conteneurs des autres Pods qui utilisent le même volume.

    Un cas d'utilisation typique pour ce mode est un Pod avec un FlexVolume ou un pilote CSI, ou un Pod qui nécessite de monter quelque chose sur l'hôte en utilisant un volume hostPath.

    Ce mode est équivalent à une propagation de montage rshared tel que décrit dans la documentation du noyau Linux

Configuration

Avant que la propagation de montage puisse fonctionner correctement sur certains déploiements (CoreOS, RedHat/Centos, Ubuntu) le partage de montage doit être correctement configuré dans Docker tel qu'illustré ci-dessous :

Modifiez le fichier de service systemd de votre Docker. Configurez votre MountFlags comme suit :

MountFlags=shared

Ou bien retirez MountFlags=slave si présent. Redémarrez ensuite le démon Docker :

sudo systemctl daemon-reload
sudo systemctl restart docker

A suivre

6.2 - Volumes persistants

Ce document décrit l'état actuel de PersistentVolumes dans Kubernetes. Une connaissance des volumes est suggérée.

Introduction

La gestion du stockage est un problème distinct de la gestion des instances de calcul. Le sous-système PersistentVolume fournit une API pour les utilisateurs et les administrateurs qui abstrait les détails de la façon dont le stockage est fourni et de la façon dont il est utilisé. Pour ce faire, nous introduisons deux nouvelles ressources API: PersistentVolume et PersistentVolumeClaim.

Un PersistentVolume (PV) est un élément de stockage dans le cluster qui a été provisionné par un administrateur ou provisionné dynamiquement à l'aide de Storage Classes. Il s'agit d'une ressource dans le cluster, tout comme un nœud est une ressource de cluster. Les PV sont des plugins de volume comme Volumes, mais ont un cycle de vie indépendant de tout pod individuel qui utilise le PV. Cet objet API capture les détails de l'implémentation du stockage, que ce soit NFS, iSCSI ou un système de stockage spécifique au fournisseur de cloud.

Un PersistentVolumeClaim (PVC) est une demande de stockage par un utilisateur. Il est similaire à un Pod. Les pods consomment des ressources de noeud et les PVC consomment des ressources PV. Les pods peuvent demander des niveaux spécifiques de ressources (CPU et mémoire). Les PVC peuvent demander une taille et des modes d'accès spécifiques (par exemple, ils peuvent être montés une fois en lecture/écriture ou plusieurs fois en lecture seule).

Alors que les PersistentVolumeClaims permettent à un utilisateur de consommer des ressources de stockage abstraites, il est courant que les utilisateurs aient besoin de PersistentVolumes avec des propriétés et des performances variables pour différents problèmes. Les administrateurs de cluster doivent être en mesure d'offrir une variété de PersistentVolumes qui diffèrent de bien des façons plus que la taille et les modes d'accès, sans exposer les utilisateurs aux détails de la façon dont ces volumes sont mis en œuvre. Pour ces besoins, il existe la ressource StorageClass.

Voir la procédure détaillée avec des exemples.

Cycle de vie d'un PV et d'un PVC

Les PV sont des ressources du cluster. Les PVC sont des demandes pour ces ressources et agissent également comme des contrôles de réclamation pour la ressource. L'interaction entre les PV et les PVC suit ce cycle de vie:

Provisionnement

Les PV peuvent être provisionnés de deux manières: statiquement ou dynamiquement.

Provisionnement statique

Un administrateur de cluster crée un certain nombre de PV. Ils contiennent les détails du stockage réel, qui est disponible pour une utilisation par les utilisateurs du cluster. Ils existent dans l'API Kubernetes et sont disponibles pour la consommation.

Provisionnement dynamique

Lorsqu'aucun des PV statiques créés par l'administrateur ne correspond au PersistentVolumeClaim d'un utilisateur, le cluster peut essayer de provisionner dynamiquement un volume spécialement pour le PVC. Ce provisionnement est basé sur les StorageClasses: le PVC doit demander une storage class et l'administrateur doit avoir créé et configuré cette classe pour que l'approvisionnement dynamique se produise. Les PVC qui demandent la classe "" désactive le provisionnement dynamique pour eux-mêmes.

Pour activer le provisionnement de stockage dynamique basé sur la classe de stockage, l'administrateur de cluster doit activer le DefaultStorageClass dans l'contrôleur d'admission sur le serveur API. Cela peut être fait, par exemple, en veillant à ce que DefaultStorageClass figure parmi la liste de valeurs séparées par des virgules pour l'option --enable-admission-plugins du composant serveur API. Pour plus d'informations sur les options de ligne de commande du serveur API, consultez la documentation kube-apiserver.

Liaison

Un utilisateur crée, ou dans le cas d'un provisionnement dynamique, a déjà créé, un PersistentVolumeClaim avec une quantité spécifique de stockage demandée et avec certains modes d'accès. Une boucle de contrôle dans le maître surveille les nouveaux PVC, trouve un PV correspondant (si possible) et les lie ensemble. Si un PV a été dynamiquement provisionné pour un nouveau PVC, la boucle liera toujours ce PV au PVC. Sinon, l'utilisateur obtiendra toujours au moins ce qu'il a demandé, mais le volume peut être supérieur à ce qui a été demandé. Une fois liées, les liaisons PersistentVolumeClaim sont exclusives, quelle que soit la façon dont elles ont été liées. Une liaison PVC-PV est une relation 1-à-1.

Les PVC resteront non liés indéfiniment s'il n'existe pas de volume correspondant. Le PVC sera lié à mesure que les volumes correspondants deviendront disponibles. Par exemple, un cluster provisionné avec de nombreux PV 50Gi ne correspondrait pas à un PVC demandant 100Gi. Le PVC peut être lié lorsqu'un PV 100Gi est ajouté au cluster.

Utilisation

Les Pods utilisent les PVC comme des volumes. Le cluster inspecte le PVC pour trouver le volume lié et monte ce volume pour un Pod. Pour les volumes qui prennent en charge plusieurs modes d'accès, l'utilisateur spécifie le mode souhaité lors de l'utilisation de leur PVC comme volume dans un Pod.

Une fois qu'un utilisateur a un PVC et que ce PVC est lié, le PV lié appartient à l'utilisateur aussi longtemps qu'il en a besoin. Les utilisateurs planifient des pods et accèdent à leurs PV revendiqués en incluant un persistentVolumeClaim dans le bloc de volumes de leur Pod Voir ci-dessous pour les détails de la syntaxe.

Protection de l'objet de stockage en cours d'utilisation

Le but de la fonction de protection des objets de stockage utilisés est de garantir que les revendications de volume persistantes (PVC) en cours d'utilisation par un Pod et les volumes persistants (PV) liés aux PVC ne sont pas supprimées du système, car cela peut entraîner des pertes de données.

Si un utilisateur supprime un PVC en cours d'utilisation par un pod, le PVC n'est pas supprimé immédiatement. L'élimination du PVC est différée jusqu'à ce que le PVC ne soit plus activement utilisé par les pods. De plus, si un administrateur supprime un PV lié à un PVC, le PV n'est pas supprimé immédiatement. L'élimination du PV est différée jusqu'à ce que le PV ne soit plus lié à un PVC.

Vous pouvez voir qu'un PVC est protégé lorsque son état est Terminating et la liste Finalizers inclus kubernetes.io/pvc-protection:

kubectl describe pvc hostpath
Name:          hostpath
Namespace:     default
StorageClass:  example-hostpath
Status:        Terminating
Volume:
Labels:        <none>
Annotations:   volume.beta.kubernetes.io/storage-class=example-hostpath
               volume.beta.kubernetes.io/storage-provisioner=example.com/hostpath
Finalizers:    [kubernetes.io/pvc-protection]
...

Vous pouvez voir qu'un PV est protégé lorsque son état est Terminating et la liste Finalizers inclus kubernetes.io/pv-protection aussi:

kubectl describe pv task-pv-volume
Name:            task-pv-volume
Labels:          type=local
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    standard
Status:          Available
Claim:
Reclaim Policy:  Delete
Access Modes:    RWO
Capacity:        1Gi
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/data
    HostPathType:
Events:            <none>

Récupération des volumes

Lorsqu'un utilisateur a terminé avec son volume, il peut supprimer les objets PVC de l'API qui permet la récupération de la ressource. La politique de récupération pour un PersistentVolume indique au cluster ce qu'il doit faire du volume une fois qu'il a été libéré de son PVC. Actuellement, les volumes peuvent être conservés, recyclés ou supprimés.

Volumes conservés

La politique de récupération Retain permet la récupération manuelle de la ressource. Lorsque le PersistentVolumeClaim est supprimé, le PersistentVolume existe toujours et le volume est considéré comme «libéré». Mais il n'est pas encore disponible pour une autre demande car les données du demandeur précédent restent sur le volume. Un administrateur peut récupérer manuellement le volume en procédant comme suit.

  1. Supprimer le PersistentVolume. L'actif de stockage associé dans une infrastructure externe (comme un volume AWS EBS, GCE PD, Azure Disk ou Cinder) existe toujours après la suppression du PV.
  2. Nettoyez manuellement les données sur l'actif de stockage associé en conséquence.
  3. Supprimez manuellement l'actif de stockage associé ou, si vous souhaitez réutiliser le même actif de stockage, créez un nouveau PersistentVolume avec la définition de l'actif de stockage.

Volumes supprimés

Pour les plug-ins de volume qui prennent en charge la stratégie de récupération Delete, la suppression supprime à la fois l'objet PersistentVolume de Kubernetes, ainsi que l'actif de stockage associé dans l'infrastructure externe, tel qu'un volume AWS EBS, GCE PD, Azure Disk ou Cinder. Les volumes qui ont été dynamiquement provisionnés héritent de la politique de récupération de leur StorageClass, qui par défaut est Delete. L'administrateur doit configurer la StorageClass selon les attentes des utilisateurs; sinon, le PV doit être édité ou corrigé après sa création. Voir Modifier la politique de récupération d'un PersistentVolume.

Volumes recyclés

Si elle est prise en charge par le plug-in de volume sous-jacent, la stratégie de récupération Recycle effectue un nettoyage de base (rm -rf /thevolume/*) sur le volume et le rend à nouveau disponible pour une nouvelle demande.

Cependant, un administrateur peut configurer un modèle de module de recyclage personnalisé à l'aide des arguments de ligne de commande du gestionnaire de contrôleur Kubernetes, comme décrit ici. Le modèle de pod de recycleur personnalisé doit contenir une définition de volumes, comme le montre l'exemple ci-dessous:

apiVersion: v1
kind: Pod
metadata:
  name: pv-recycler
  namespace: default
spec:
  restartPolicy: Never
  volumes:
  - name: vol
    hostPath:
      path: /any/path/it/will/be/replaced
  containers:
  - name: pv-recycler
    image: "k8s.gcr.io/busybox"
    command: ["/bin/sh", "-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/*  && test -z \"$(ls -A /scrub)\" || exit 1"]
    volumeMounts:
    - name: vol
      mountPath: /scrub

Cependant, le chemin particulier spécifié dans la partie volumes du template personnalisé de Pod est remplacée par le chemin particulier du volume qui est recyclé.

Redimensionnement des PVC

FEATURE STATE: Kubernetes v1.11 [beta]

La prise en charge du redimensionnement des PersistentVolumeClaims (PVCs) est désormais activée par défaut. Vous pouvez redimensionner les types de volumes suivants:

  • gcePersistentDisk
  • awsElasticBlockStore
  • Cinder
  • glusterfs
  • rbd
  • Azure File
  • Azure Disk
  • Portworx
  • FlexVolumes
  • CSI

Vous ne pouvez redimensionner un PVC que si le champ allowVolumeExpansion de sa classe de stockage est défini sur true.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gluster-vol-default
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://192.168.10.100:8080"
  restuser: ""
  secretNamespace: ""
  secretName: ""
allowVolumeExpansion: true

Pour demander un volume plus important pour un PVC, modifiez l'objet PVC et spécifiez une taille plus grande. Cela déclenche l'expansion du volume qui soutient le PersistentVolume sous-jacent. Un nouveau PersistentVolume n'est jamais créé pour satisfaire la demande. Au lieu de cela, un volume existant est redimensionné.

Redimensionnement de volume CSI

FEATURE STATE: Kubernetes v1.16 [beta]

La prise en charge du redimensionnement des volumes CSI est activée par défaut, mais elle nécessite également un pilote CSI spécifique pour prendre en charge le redimensionnement des volumes. Reportez-vous à la documentation du pilote CSI spécifique pour plus d'informations.

Redimensionner un volume contenant un système de fichiers

Vous ne pouvez redimensionner des volumes contenant un système de fichiers que si le système de fichiers est XFS, Ext3 ou Ext4.

Lorsqu'un volume contient un système de fichiers, le système de fichiers n'est redimensionné que lorsqu'un nouveau pod utilise le PersistentVolumeClaim en mode ReadWrite. L'extension du système de fichiers est effectuée au démarrage d'un pod ou lorsqu'un pod est en cours d'exécution et que le système de fichiers sous-jacent prend en charge le redimensionnement en ligne.

FlexVolumes autorise le redimensionnement si le pilote est défini avec la capacité requiresFSResize sur true. Le FlexVolume peut être redimensionné au redémarrage du pod.

Redimensionnement d'un PersistentVolumeClaim en cours d'utilisation

FEATURE STATE: Kubernetes v1.15 [beta]

Dans ce cas, vous n'avez pas besoin de supprimer et de recréer un pod ou un déploiement qui utilise un PVC existant. Tout PVC en cours d'utilisation devient automatiquement disponible pour son pod dès que son système de fichiers a été étendu. Cette fonctionnalité n'a aucun effet sur les PVC qui ne sont pas utilisés par un pod ou un déploiement. Vous devez créer un pod qui utilise le PVC avant que l'extension puisse se terminer.

Semblable à d'autres types de volume - les volumes FlexVolume peuvent également être étendus lorsqu'ils sont utilisés par un pod.

Types de volumes persistants

Les types PersistentVolume sont implémentés en tant que plugins. Kubernetes prend actuellement en charge les plugins suivants:

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • AzureFile
  • AzureDisk
  • CSI
  • FC (Fibre Channel)
  • FlexVolume
  • Flocker
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • CephFS
  • Cinder (OpenStack block storage)
  • Glusterfs
  • VsphereVolume
  • Quobyte Volumes
  • HostPath (Test de nœud unique uniquement -- le stockage local n'est en aucun cas pris en charge et NE FONCTIONNERA PAS dans un cluster à plusieurs nœuds)
  • Portworx Volumes
  • ScaleIO Volumes
  • StorageOS

Volumes persistants

Chaque PV contient une spécification et un état, qui sont les spécifications et l'état du volume.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /tmp
    server: 172.17.0.2

Capacité

Généralement, un PV aura une capacité de stockage spécifique. Ceci est réglé en utilisant l'attribut capacity des PV. Voir le Kubernetes modèle de ressource pour comprendre les unités attendues par capacity.

Actuellement, la taille du stockage est la seule ressource qui peut être définie ou demandée. Les futurs attributs peuvent inclure les IOPS, le débit, etc.

Mode volume

FEATURE STATE: Kubernetes v1.13 [beta]

Avant Kubernetes 1.9, tous les plug-ins de volume créaient un système de fichiers sur le volume persistant. Maintenant, vous pouvez définir la valeur de volumeMode sur block pour utiliser un périphérique de bloc brut, ou filesystem pour utiliser un système de fichiers. filesystem est la valeur par défaut si la valeur est omise. Il s'agit d'un paramètre API facultatif.

Modes d'accès

Un PersistentVolume peut être monté sur un hôte de n'importe quelle manière prise en charge par le fournisseur de ressources. Comme indiqué dans le tableau ci-dessous, les fournisseurs auront des capacités différentes et les modes d'accès de chaque PV sont définis sur les modes spécifiques pris en charge par ce volume particulier. Par exemple, NFS peut prendre en charge plusieurs clients en lecture/écriture, mais un PV NFS spécifique peut être exporté sur le serveur en lecture seule. Chaque PV dispose de son propre ensemble de modes d'accès décrivant les capacités spécifiques de ce PV.

Les modes d'accès sont:

  • ReadWriteOnce -- le volume peut être monté en lecture-écriture par un seul nœud
  • ReadOnlyMany -- le volume peut être monté en lecture seule par plusieurs nœuds
  • ReadWriteMany -- le volume peut être monté en lecture-écriture par de nombreux nœuds

Dans la CLI, les modes d'accès sont abrégés comme suit:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany

Important! Un volume ne peut être monté qu'en utilisant un seul mode d'accès à la fois, même s'il prend en charge plusieurs. Par exemple, un GCEPersistentDisk peut être monté en tant que ReadWriteOnce par un seul nœud ou ReadOnlyMany par plusieurs nœuds, mais pas en même temps.

Volume Plugin ReadWriteOnce ReadOnlyMany ReadWriteMany
AWSElasticBlockStore - -
AzureFile
AzureDisk - -
CephFS
Cinder - -
CSI dépend du pilote dépend du pilote dépend du pilote
FC -
FlexVolume dépend du pilote
Flocker - -
GCEPersistentDisk -
Glusterfs
HostPath - -
iSCSI -
Quobyte
NFS
RBD -
VsphereVolume - - (fonctionne lorsque les pods sont colocalisés)
PortworxVolume -
ScaleIO -
StorageOS - -

Classe

Un PV peut avoir une classe, qui est spécifiée en définissant l'attribut storageClassName sur le nom d'une StorageClass. Un PV d'une classe particulière ne peut être lié qu'à des PVC demandant cette classe. Un PV sans storageClassName n'a pas de classe et ne peut être lié qu'à des PVC qui ne demandent aucune classe particulière.

Dans le passé, l'annotation volume.beta.kubernetes.io/storage-class a été utilisé à la place de l'attribut storageClassName. Cette annotation fonctionne toujours; cependant, il deviendra complètement obsolète dans une future version de Kubernetes.

Politique de récupération

Les politiques de récupération actuelles sont:

  • Retain -- remise en état manuelle
  • Recycle -- effacement de base (rm -rf /thevolume/*)
  • Delete -- l'élément de stockage associé tel qu'AWS EBS, GCE PD, Azure Disk ou le volume OpenStack Cinder est supprimé

Actuellement, seuls NFS et HostPath prennent en charge le recyclage. Les volumes AWS EBS, GCE PD, Azure Disk et Cinder prennent en charge la suppression.

Options de montage

Un administrateur Kubernetes peut spécifier des options de montage supplémentaires pour quand un PersistentVolume est monté sur un nœud.

Les types de volume suivants prennent en charge les options de montage:

  • AWSElasticBlockStore
  • AzureDisk
  • AzureFile
  • CephFS
  • Cinder (OpenStack block storage)
  • GCEPersistentDisk
  • Glusterfs
  • NFS
  • Quobyte Volumes
  • RBD (Ceph Block Device)
  • StorageOS
  • VsphereVolume
  • iSCSI

Les options de montage ne sont pas validées, donc le montage échouera simplement si l'une n'est pas valide.

Dans le passé, l'annotation volume.beta.kubernetes.io/mount-options était utilisée à la place de l'attribut mountOptions. Cette annotation fonctionne toujours; cependant, elle deviendra complètement obsolète dans une future version de Kubernetes.

Affinité des nœuds

Un PV peut spécifier une affinité de nœud pour définir les contraintes qui limitent les nœuds à partir desquels ce volume est accessible. Les pods qui utilisent un PV seront uniquement planifiés sur les nœuds sélectionnés par l'affinité de nœud.

Phase

Un volume sera dans l'une des phases suivantes:

  • Available -- une ressource libre qui n'est pas encore liée à une demande
  • Bound -- le volume est lié à une demande
  • Released -- la demande a été supprimée, mais la ressource n'est pas encore récupérée par le cluster
  • Failed -- le volume n'a pas réussi sa récupération automatique

Le CLI affichera le nom du PVC lié au PV.

PersistentVolumeClaims

Chaque PVC contient une spécification et un état, qui sont les spécifications et l'état de la réclamation.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}

Modes d'accès

Les PVC utilisent les mêmes conventions que les volumes lorsque vous demandez un stockage avec des modes d'accès spécifiques.

Modes de volume

Les PVC utilisent la même convention que les volumes pour indiquer la consommation du volume en tant que système de fichiers ou périphérique de bloc.

Ressources

Les PVC, comme les pods, peuvent demander des quantités spécifiques d'une ressource. Dans ce cas, la demande concerne le stockage. Le même modèle de ressource s'applique aux volumes et aux PVC.

Sélecteur

Les PVC peuvent spécifier un sélecteur de labels pour filtrer davantage l'ensemble des volumes. Seuls les volumes dont les étiquettes correspondent au sélecteur peuvent être liés au PVC. Le sélecteur peut comprendre deux champs:

  • matchLabels - le volume doit avoir un label avec cette valeur
  • matchExpressions - une liste des exigences définies en spécifiant la clé, la liste des valeurs et l'opérateur qui relie la clé et les valeurs. Les opérateurs valides incluent In, NotIn, Exists et DoesNotExist.

Toutes les exigences, à la fois de matchLabels et de matchExpressions doivent toutes être satisfaites pour correspondre (application d'un opérateur booléen ET).

Classe

Un PVC peut demander une classe particulière en spécifiant le nom d'une StorageClass en utilisant l'attribut storageClassName. Seuls les PV de la classe demandée, ceux ayant le même storageClassName que le PVC, peuvent être liés au PVC.

Les PVC n'ont pas nécessairement à demander une classe. Un PVC avec son attribut storageClassName égal à "" est toujours interprété comme demandant un PV sans classe, il ne peut donc être lié qu'à des PV sans classe (pas d'annotation ou une annotation égal à ""). Un PVC sans storageClassName n'est pas tout à fait la même et est traité différemment par le cluster, selon que le DefaultStorageClass admission plugin est activé.

  • Si le plug-in d'admission est activé, l'administrateur peut spécifier une valeur par défaut StorageClass. Tous les PVC qui n'ont pas de storageClassName ne peuvent être liés qu'aux PV de cette valeur par défaut. La spécification d'une StorageClass par défaut se fait en définissant l'annotation storageclass.kubernetes.io/is-default-class égal à true dans un objet StorageClass. Si l'administrateur ne spécifie pas de valeur par défaut, le cluster répond à la création de PVC comme si le plug-in d'admission était désactivé. Si plusieurs valeurs par défaut sont spécifiées, le plugin d'admission interdit la création de tous les PVC.
  • Si le plugin d'admission est désactivé, il n'y a aucune notion de défaut StorageClass. Tous les PVC qui n'ont pas storageClassName peut être lié uniquement aux PV qui n'ont pas de classe. Dans ce cas, les PVC qui n'ont pas storageClassName sont traités de la même manière que les PVC qui ont leur storageClassName égal à "".

Selon la méthode d'installation, une StorageClass par défaut peut être déployée sur un cluster Kubernetes par le gestionnaire d'extensions pendant l'installation.

Lorsqu'un PVC spécifie un selector en plus de demander une StorageClass, les exigences sont ET ensemble: seul un PV de la classe demandée et avec les labels demandées peut être lié au PVC.

Dans le passé, l'annotation volume.beta.kubernetes.io/storage-class a été utilisé au lieu de l'attribut storageClassName. Cette annotation fonctionne toujours; cependant, elle ne sera pas pris en charge dans une future version de Kubernetes.

PVC sous forme de volumes

Les pods accèdent au stockage en utilisant le PVC comme volume. Les PVC et les pods qui les utilisent doivent exister dans le même namespace. Le cluster trouve le PVC dans le namespace où se trouve le pod et l'utilise pour obtenir le PersistentVolume visé par le PVC. Le volume est ensuite monté sur l'hôte et dans le pod.

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

Remarque au sujet des namespaces

Les liaisons PersistentVolumes sont exclusives, et comme les objets PersistentVolumeClaims sont des objets vivant dans un namespace donné, le montage de PVC avec les modes "Many" (ROX, RWX) n'est possible qu'au sein d'un même namespace.

Prise en charge du volume de bloc brut

FEATURE STATE: Kubernetes v1.13 [beta]

Les plug-ins de volume suivants prennent en charge les volumes de blocs bruts, y compris l'approvisionnement dynamique, le cas échéant:

  • AWSElasticBlockStore
  • AzureDisk
  • FC (Fibre Channel)
  • GCEPersistentDisk
  • iSCSI
  • Local volume
  • RBD (Ceph Block Device)
  • VsphereVolume (alpha)

Volumes persistants utilisant un volume de bloc brut

apiVersion: v1
kind: PersistentVolume
metadata:
  name: block-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  persistentVolumeReclaimPolicy: Retain
  fc:
    targetWWNs: ["50060e801049cfd1"]
    lun: 0
    readOnly: false

Revendication de volume persistant demandant un volume de bloc brut

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Block
  resources:
    requests:
      storage: 10Gi

Spécification de pod ajoutant le chemin du périphérique de bloc brut dans le conteneur

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-block-volume
spec:
  containers:
    - name: fc-container
      image: fedora:26
      command: ["/bin/sh", "-c"]
      args: [ "tail -f /dev/null" ]
      volumeDevices:
        - name: data
          devicePath: /dev/xvda
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: block-pvc

Lier des volumes bloc bruts

Si un utilisateur demande un volume de bloc brut en l'indiquant à l'aide du champ volumeMode dans la spécification PersistentVolumeClaim, les règles de liaison diffèrent légèrement des versions précédentes qui ne considéraient pas ce mode comme faisant partie de la spécification. Voici un tableau des combinaisons possibles que l'utilisateur et l'administrateur peuvent spécifier pour demander un périphérique de bloc brut. Le tableau indique si le volume sera lié ou non compte tenu des combinaisons: Matrice de liaison de volume pour les volumes provisionnés statiquement:

| PV volumeMode | PVC volumeMode | Result | |---------------|-:-:------------|--:------| | unspecified | unspecified | BIND | | unspecified | Block | NO BIND | | unspecified | Filesystem | BIND | | Block | unspecified | NO BIND | | Block | Block | BIND | | Block | Filesystem | NO BIND | | Filesystem | Filesystem | BIND | | Filesystem | Block | NO BIND | | Filesystem | unspecified | BIND |

Snapshot et restauration de volumes

FEATURE STATE: Kubernetes v1.12 [alpha]

La fonction de snapshot de volume a été ajoutée pour prendre en charge uniquement les plug-ins de volume CSI. Pour plus de détails, voir volume snapshots.

Pour activer la prise en charge de la restauration d'un volume à partir d'un snapshot de volume, activez la fonctionnalité VolumeSnapshotDataSource sur l'apiserver et le controller-manager.

Créer du PVC à partir d'un snapshot de volume

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: restore-pvc
spec:
  storageClassName: csi-hostpath-sc
  dataSource:
    name: new-snapshot-test
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Clonage de volume

FEATURE STATE: Kubernetes v1.16 [beta]

La fonctionnalité de clonage de volume a été ajoutée pour prendre en charge uniquement les plug-ins de volume CSI. Pour plus de détails, voir clonage de volume.

Pour activer la prise en charge du clonage d'un volume à partir d'une source de données PVC, activez la propriété VolumePVCDataSource sur l'apiserver et le controller-manager.

Créer un PVC à partir d'un PVC existant

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cloned-pvc
spec:
  storageClassName: my-csi-plugin
  dataSource:
    name: existing-src-pvc-name
    kind: PersistentVolumeClaim
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Écriture d'une configuration portable

Si vous écrivez des templates de configuration ou des exemples qui s'exécutent sur une large gamme de clusters et nécessitent un stockage persistant, il est recommandé d'utiliser le modèle suivant:

  • Incluez des objets PersistentVolumeClaim dans votre ensemble de config (aux côtés de Deployments, ConfigMaps, etc.).
  • N'incluez pas d'objets PersistentVolume dans la configuration, car l'utilisateur qui instancie la configuration peut ne pas être autorisé à créer des PersistentVolumes.
  • Donnez à l'utilisateur la possibilité de fournir un nom de classe de stockage lors de l'instanciation du template.
    • Si l'utilisateur fournit un nom de classe de stockage, mettez cette valeur dans le champ persistentVolumeClaim.storageClassName. Cela entraînera le PVC pour utiliser la bonne classe de stockage si le cluster a cette StorageClasses activé par l'administrateur.
    • Si l'utilisateur ne fournit pas de nom de classe de stockage, laissez le champ persistentVolumeClaim.storageClassName à zéro. Cela entraînera un PV à être automatiquement provisionné pour l'utilisateur avec la StorageClass par défaut dans le cluster. De nombreux environnements de cluster ont une StorageClass par défaut installée, où les administrateurs peuvent créer leur propre StorageClass par défaut.
  • Dans votre outillage, surveillez les PVCs qui ne sont pas liés après un certain temps et signalez-le à l'utilisateur, car cela peut indiquer que le cluster n'a pas de support de stockage dynamique (auquel cas l'utilisateur doit créer un PV correspondant) ou que le cluster n'a aucun système de stockage (auquel cas l'utilisateur ne peut pas déployer de configuration nécessitant des PVCs).

7 - Configuration

7.1 - Secrets

Les objets secret de Kubernetes vous permettent de stocker et de gérer des informations sensibles, telles que les mots de passe, les jetons OAuth et les clés ssh. Mettre ces informations dans un secret est plus sûr et plus flexible que de le mettre en dur dans la définition d'un Pod ou dans une container image. Voir Document de conception des secrets pour plus d'informations.

Présentation des secrets

Un secret est un objet qui contient une petite quantité de données sensibles telles qu'un mot de passe, un jeton ou une clé. De telles informations pourraient autrement être placées dans une spécification de pod ou dans une image; le placer dans un objet secret permet de mieux contrôler la façon dont il est utilisé et réduit le risque d'exposition accidentelle.

Les utilisateurs peuvent créer des secrets et le système crée également des secrets.

Pour utiliser un secret, un pod doit référencer le secret. Un secret peut être utilisé avec un pod de deux manières: sous forme de fichiers dans un volume monté sur un ou plusieurs de ses conteneurs, ou utilisé par kubelet lorsque vous récupérez des images pour le pod.

Secrets intégrés

Les comptes de service créent et attachent automatiquement des secrets avec les informations d'identification de l'API

Kubernetes crée automatiquement des secrets qui contiennent des informations d'identification pour accéder à l'API et il modifie automatiquement vos pods pour utiliser ce type de secret.

La création et l'utilisation automatiques des informations d'identification de l'API peuvent être désactivées ou remplacées si vous le souhaitez. Cependant, si tout ce que vous avez à faire est d'accéder en toute sécurité à l'apiserver, il s'agit de la méthode recommandée.

Voir la documentation des Compte de service pour plus d'informations sur le fonctionnement des comptes de service.

Créer vos propres secrets

Créer un secret avec kubectl create secret

Supposons que certains pods doivent accéder à une base de données. Le nom d'utilisateur et le mot de passe que les pods doivent utiliser se trouvent dans les fichiers ./username.txt et ./password.txt sur votre machine locale.

# Create files needed for rest of example.
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt

La commande kubectl create secret regroupe ces fichiers dans un secret et crée l'objet sur l'Apiserver.

kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret "db-user-pass" created

Vous pouvez vérifier que le secret a été créé comme ceci:

kubectl get secrets
NAME                  TYPE                                  DATA      AGE
db-user-pass          Opaque                                2         51s
kubectl describe secrets/db-user-pass
Name:            db-user-pass
Namespace:       default
Labels:          <none>
Annotations:     <none>

Type:            Opaque

Data
====
password.txt:    12 bytes
username.txt:    5 bytes

Voir décoder un secret pour voir le contenu d'un secret.

Création manuelle d'un secret

Vous pouvez également créer un secret dans un fichier d'abord, au format json ou yaml, puis créer cet objet. Le secret contient deux table de hachage: data et stringData. Le champ data est utilisé pour stocker des données arbitraires, encodées en base64. Le champ stringData est fourni pour plus de commodité et vous permet de fournir des données secrètes sous forme de chaînes non codées.

Par exemple, pour stocker deux chaînes dans un secret à l'aide du champ data, convertissez-les en base64 comme suit:

echo -n 'admin' | base64
YWRtaW4=
echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm

Écrivez un secret qui ressemble à ceci:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Maintenant, créez le secret en utilisant kubectl apply:

kubectl apply -f ./secret.yaml
secret "mysecret" created

Pour certains scénarios, vous pouvez utiliser le champ stringData à la place. Ce champ vous permet de mettre une chaîne non codée en base64 directement dans le secret, et la chaîne sera codée pour vous lorsque le secret sera créé ou mis à jour.

Un exemple pratique de cela pourrait être le suivant: vous déployez une application qui utilise un secret pour stocker un fichier de configuration. Vous souhaitez remplir des parties de ce fichier de configuration pendant votre processus de déploiement.

Si votre application utilise le fichier de configuration suivant:

apiUrl: "https://my.api.com/api/v1"
username: "user"
password: "password"

Vous pouvez stocker cela dans un secret en utilisant ce qui suit:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  config.yaml: |-
    apiUrl: "https://my.api.com/api/v1"
    username: {{username}}
    password: {{password}}    

Votre outil de déploiement pourrait alors remplacer les variables de modèle {{username}} et {{password}} avant d'exécuter kubectl apply.

stringData est un champ de commodité en écriture seule. Il n'est jamais affiché lors de la récupération des secrets. Par exemple, si vous exécutez la commande suivante:

kubectl get secret mysecret -o yaml

La sortie sera similaire à:

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-15T20:40:59Z
  name: mysecret
  namespace: default
  resourceVersion: "7225"
  uid: c280ad2e-e916-11e8-98f2-025000000001
type: Opaque
data:
  config.yaml: YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IHt7dXNlcm5hbWV9fQpwYXNzd29yZDoge3twYXNzd29yZH19

Si un champ est spécifié à la fois dans data et stringData, la valeur de stringData est utilisée. Par exemple, la définition de secret suivante:

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
stringData:
  username: administrateur

Donnera le secret suivant:

apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2018-11-15T20:46:46Z
  name: mysecret
  namespace: default
  resourceVersion: "7579"
  uid: 91460ecb-e917-11e8-98f2-025000000001
type: Opaque
data:
  username: YWRtaW5pc3RyYXRldXI=

YWRtaW5pc3RyYXRldXI= décode en administrateur.

Les clés de data et stringData doivent être composées de caractères alphanumériques, '-', '_' ou '.'.

Encoding Note: Les valeurs JSON et YAML sérialisées des données secrètes sont codées sous forme de chaînes base64. Les sauts de ligne ne sont pas valides dans ces chaînes et doivent être omis. Lors de l'utilisation de l'utilitaire base64 sur Darwin / macOS, les utilisateurs doivent éviter d'utiliser l'option -b pour diviser les longues lignes. Inversement, les utilisateurs Linux devraient ajouter l'option -w 0 aux commandes base64 ou le pipeline base64 | tr -d '\ n' si l'option -w n'est pas disponible.

Création d'un secret à partir du générateur

Kubectl prend en charge la gestion des objets à l'aide de Kustomize depuis 1.14. Avec cette nouvelle fonctionnalité, vous pouvez également créer un secret à partir de générateurs, puis l'appliquer pour créer l'objet sur l'Apiserver. Les générateurs doivent être spécifiés dans un kustomization.yaml à l'intérieur d'un répertoire.

Par exemple, pour générer un secret à partir des fichiers ./username.txt et ./password.txt

# Create a kustomization.yaml file with SecretGenerator
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: db-user-pass
  files:
  - username.txt
  - password.txt
EOF

Appliquez le répertoire de personnalisation pour créer l'objet secret.

$ kubectl apply -k .
secret/db-user-pass-96mffmfh4k created

Vous pouvez vérifier que le secret a été créé comme ceci:

$ kubectl get secrets
NAME                             TYPE                                  DATA      AGE
db-user-pass-96mffmfh4k          Opaque                                2         51s

$ kubectl describe secrets/db-user-pass-96mffmfh4k
Name:            db-user-pass
Namespace:       default
Labels:          <none>
Annotations:     <none>

Type:            Opaque

Data
====
password.txt:    12 bytes
username.txt:    5 bytes

Par exemple, pour générer un secret à partir des littéraux username=admin et password=secret, vous pouvez spécifier le générateur de secret dans kustomization.yaml comme:

# Create a kustomization.yaml file with SecretGenerator
$ cat <<EOF >./kustomization.yaml
secretGenerator:
- name: db-user-pass
  literals:
  - username=admin
  - password=secret
EOF

Appliquer le repertoire kustomization pour créer l'objet secret.

$ kubectl apply -k .
secret/db-user-pass-dddghtt9b5 created

Décoder un secret

Les secrets peuvent être récupérés via la command kubectl get secret. Par exemple, pour récupérer le secret créé dans la section précédente:

kubectl get secret mysecret -o yaml
apiVersion: v1
kind: Secret
metadata:
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Décodez le champ du mot de passe:

echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
1f2d1e2e67df

Modification d'un secret

Un secret existant peut être modifié avec la commande suivante:

kubectl edit secrets mysecret

Cela ouvrira l'éditeur configuré par défaut et permettra de mettre à jour les valeurs secrètes codées en base64 dans le champ data:

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: { ... }
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque

Utiliser les secrets

Les secrets peuvent être montés en tant que volumes de données ou être exposés en tant que variables d'environnement à utiliser par un conteneur dans un Pod. Ils peuvent également être utilisés par d'autres parties du système, sans être directement exposés aux Pods. Par exemple, ils peuvent détenir des informations d'identification que d'autres parties du système doivent utiliser pour interagir avec des systèmes externes en votre nom.

Utilisation de secrets comme fichiers d'un pod

Pour consommer un secret dans un volume dans un pod:

  1. Créez un secret ou utilisez-en un déjà existant. Plusieurs Pods peuvent référencer le même secret.
  2. Modifiez la définition de votre Pod pour ajouter un volume sous .spec.volumes[]. Nommez le volume et ayez un champ .spec.volumes[].secret.secretName égal au nom de l'objet secret.
  3. Ajouter un .spec.containers[].volumeMounts[] à chaque conteneur qui a besoin du secret. Spécifier .spec.containers[].volumeMounts[].readOnly = true et .spec.containers[].volumeMounts[].mountPath à un nom de répertoire inutilisé où vous souhaitez que les secrets apparaissent.
  4. Modifiez votre image et/ou votre ligne de commande pour que le programme recherche les fichiers dans ce répertoire. Chaque clé de la carte secrète data devient le nom de fichier sous mountPath.

Voici un exemple de pod qui monte un secret dans un volume:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

Chaque secret que vous souhaitez utiliser doit être mentionné dans .spec.volumes.

S'il y a plusieurs conteneurs dans le pod, alors chaque conteneur a besoin de son propre bloc volumeMounts, mais un seul .spec.volumes est nécessaire par secret.

Vous pouvez regrouper de nombreux fichiers en un seul secret ou utiliser de nombreux secrets, selon le cas.

Projection de clés secrètes vers des chemins spécifiques

Nous pouvons également contrôler les chemins dans le volume où les clés secrètes sont projetées. Vous pouvez utiliser le champ .spec.volumes []. Secret.items pour changer le chemin cible de chaque clé:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

Que se passera-t-il:

  • username est stocké dans le fichier /etc/foo/my-group/my-username au lieu de /etc/foo/username.
  • password n'est pas projeté

Si .spec.volumes[].secret.items est utilisé, seules les clés spécifiées dans items sont projetées. Pour consommer toutes les clés du secret, toutes doivent être répertoriées dans le champ items. Toutes les clés répertoriées doivent exister dans le secret correspondant. Sinon, le volume n'est pas créé.

Autorisations de fichiers secrets

Vous pouvez également spécifier les bits de mode d'autorisation des fichiers contenant les parties d'un secret. Si vous n'en spécifiez pas, 0644 est utilisé par défaut. Vous pouvez spécifier un mode par défaut pour tout le volume secret et remplacer par clé si nécessaire.

Par exemple, vous pouvez spécifier un mode par défaut comme celui-ci:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 256

Ensuite, le secret sera monté sur /etc/foo et tous les fichiers créés par le montage de volume secret auront la permission 0400.

Notez que la spécification JSON ne prend pas en charge la notation octale, utilisez donc la valeur 256 pour les autorisations 0400. Si vous utilisez yaml au lieu de json pour le pod, vous pouvez utiliser la notation octale pour spécifier les autorisations de manière plus naturelle.

Vous pouvez aussi utiliser un mapping, comme dans l'exemple précédent, et spécifier des autorisations différentes pour différents fichiers comme celui-ci:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 511

Dans ce cas, le fichier résultant /etc/foo/my-group/my-username aura la valeur d'autorisation de 0777. En raison des limitations JSON, vous devez spécifier le mode en notation décimale.

Notez que cette valeur d'autorisation peut être affichée en notation décimale si vous la lisez plus tard.

Consommer des valeurs secrètes à partir de volumes

À l'intérieur du conteneur qui monte un volume secret, les clés secrètes apparaissent sous forme de fichiers et les valeurs secrètes sont décodées en base 64 et stockées à l'intérieur de ces fichiers. C'est le résultat des commandes exécutées à l'intérieur du conteneur de l'exemple ci-dessus:

ls /etc/foo/
username
password
cat /etc/foo/username
admin
cat /etc/foo/password
1f2d1e2e67df

Le programme dans un conteneur est responsable de la lecture des secrets des fichiers.

Les secrets montés sont mis à jour automatiquement

Lorsqu'un secret déjà consommé dans un volume est mis à jour, les clés projetées sont finalement mises à jour également. Kubelet vérifie si le secret monté est récent à chaque synchronisation périodique. Cependant, il utilise son cache local pour obtenir la valeur actuelle du Secret. Le type de cache est configurable à l'aide de le champ ConfigMapAndSecretChangeDetectionStrategy dans la structure KubeletConfiguration. Il peut être soit propagé via watch (par défaut), basé sur ttl, ou simplement redirigé toutes les requêtes vers directement kube-apiserver. Par conséquent, le délai total entre le moment où le secret est mis à jour et le moment où de nouvelles clés sont projetées sur le pod peut être aussi long que la période de synchronisation du kubelet + le délai de propagation du cache, où le délai de propagation du cache dépend du type de cache choisi (cela équivaut au delai de propagation du watch, ttl du cache, ou bien zéro).

Utilisation de secrets comme variables d'environnement

Pour utiliser un secret dans une variable d'environnement dans un pod:

  1. Créez un secret ou utilisez-en un déjà existant. Plusieurs pods peuvent référencer le même secret.
  2. Modifiez la définition de votre pod dans chaque conteneur où vous souhaitez utiliser la valeur d'une clé secrète pour ajouter une variable d'environnement pour chaque clé secrète que vous souhaitez consommer. La variable d'environnement qui consomme la clé secrète doit remplir le nom et la clé du secret dans env[].valueFrom.secretKeyRef.
  3. Modifiez votre image et/ou votre ligne de commande pour que le programme recherche des valeurs dans les variables d'environnement spécifiées

Voici un exemple de pod qui utilise des secrets de variables d'environnement:

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never

Consommation de valeurs secrètes à partir de variables d'environnement

À l'intérieur d'un conteneur qui consomme un secret dans des variables d'environnement, les clés secrètes apparaissent comme des variables d'environnement normales contenant les valeurs décodées en base64 des données secrètes. C'est le résultat des commandes exécutées à l'intérieur du conteneur de l'exemple ci-dessus:

echo $SECRET_USERNAME
admin
echo $SECRET_PASSWORD
1f2d1e2e67df

Utilisation des imagePullSecrets

Un imagePullSecret est un moyen de transmettre un secret qui contient un mot de passe de registre d'images Docker (ou autre) au Kubelet afin qu'il puisse extraire une image privée au nom de votre Pod.

Spécification manuelle d'une imagePullSecret

L'utilisation de imagePullSecrets est décrite dans la documentation des images

Arranging for imagePullSecrets to be Automatically Attached

Vous pouvez créer manuellement un imagePullSecret et le référencer à partir d'un serviceAccount. Tous les pods créés avec ce serviceAccount ou cette valeur par défaut pour utiliser ce serviceAccount, verront leur champ imagePullSecret défini sur celui du compte de service. Voir Ajouter ImagePullSecrets à un compte de service pour une explication détaillée de ce processus.

Montage automatique de secrets créés manuellement

Les secrets créés manuellement (par exemple, un contenant un jeton pour accéder à un compte github) peuvent être automatiquement associés aux pods en fonction de leur compte de service. Voir Injection d'informations dans des pods à l'aide d'un PodPreset pour une explication détaillée de ce processus.

Details

Restrictions

Les sources de volume secrètes sont validées pour garantir que la référence d'objet spécifiée pointe réellement vers un objet de type Secret. Par conséquent, un secret doit être créé avant tous les pods qui en dépendent.

Les objets API secrets résident dans un namespace. Ils ne peuvent être référencés que par des pods dans le même espace de noms.

Les secrets individuels sont limités à 1 Mo de taille. C'est pour décourager la création de très grands secrets qui épuiseraient la mémoire de l'apiserver et du kubelet. Cependant, la création de nombreux petits secrets pourrait également épuiser la mémoire. Des limites plus complètes sur l'utilisation de la mémoire en raison de secrets sont une fonctionnalité prévue.

Kubelet prend uniquement en charge l'utilisation des secrets pour les pods qu'il obtient du serveur API. Cela inclut tous les pods créés à l'aide de kubectl, ou indirectement via un contrôleur de réplication. Il n'inclut pas les pods créés via les drapeaux kubelet --manifest-url, ou --config, ou son API REST (ce ne sont pas des moyens courants de créer des Pods).

Les secrets doivent être créés avant d'être consommés dans les pods en tant que variables d'environnement, sauf s'ils sont marqués comme facultatifs. Les références à des secrets qui n'existent pas empêcheront le pod de démarrer.

Les références via secretKeyRef à des clés qui n'existent pas dans un Secret nommé empêcheront le pod de démarrer.

Les secrets utilisés pour remplir les variables d'environnement via envFrom qui ont des clés considérées comme des noms de variables d'environnement non valides verront ces clés ignorées. Le pod sera autorisé à démarrer. Il y aura un événement dont la raison est InvalidVariableNames et le message contiendra la liste des clés invalides qui ont été ignorées. L'exemple montre un pod qui fait référence au / mysecret par défaut qui contient 2 clés invalides, 1badkey et 2alsobad.

kubectl get events
LASTSEEN   FIRSTSEEN   COUNT     NAME            KIND      SUBOBJECT                         TYPE      REASON
0s         0s          1         dapi-test-pod   Pod                                         Warning   InvalidEnvironmentVariableNames   kubelet, 127.0.0.1      Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.

Cycle de vie de l'intéraction Secret et Pod

Lorsqu'un pod est créé via l'API, il n'est pas vérifié s'il existe un secret référencé. Une fois qu'un pod est programmé, le kubelet tentera de récupérer la valeur secrète. Si le secret ne peut pas être récupéré parce qu'il n'existe pas ou en raison d'un manque temporaire de connexion au serveur API, kubelet réessayera périodiquement. Il rapportera un événement sur le pod expliquant la raison pour laquelle il n'a pas encore démarré. Une fois le secret récupéré, le kubelet créera et montera un volume le contenant. Aucun des conteneurs du pod ne démarre tant que tous les volumes du pod ne sont pas montés.

Cas d'utilisation

Cas d'utilisation: pod avec clés SSH

Créez un kustomization.yaml avec un SecretGenerator contenant quelques clés SSH:

kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
secret "ssh-key-secret" created

Nous pouvons maintenant créer un pod qui référence le secret avec la clé SSH et le consomme dans un volume:

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
  labels:
    name: secret-test
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh-key-secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

Lorsque la commande du conteneur s'exécute, les morceaux de la clé seront disponibles dans:

/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey

Le conteneur est alors libre d'utiliser les données secrètes pour établir une connexion SSH.

Cas d'utilisation: pods avec informations d'identification de prod/test

Faites un fichier kustomization.yaml avec un SecretGenerator.

Cet exemple illustre un Pod qui consomme un secret contenant des informations d'identification de prod et un autre Pod qui consomme un secret avec des informations d'identification d'environnement de test.

kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11
secret "prod-db-secret" created
kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests
secret "test-db-secret" created

Maintenant, faites les pods:

$ cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
EOF

Ajoutez les pods à la même kustomization.yaml

$ cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF

Appliquez tous ces objets sur l'Apiserver avec

kubectl apply -k .

Les deux conteneurs auront les fichiers suivants présents sur leurs systèmes de fichiers avec les valeurs pour l'environnement de chaque conteneur:

/etc/secret-volume/username
/etc/secret-volume/password

Notez comment les spécifications pour les deux pods ne diffèrent que dans un champ; cela facilite la création de pods avec différentes capacités à partir d'un template de pod commun.

Vous pouvez encore simplifier la spécification du pod de base en utilisant deux comptes de service: un appelé, disons, prod-user avec le secret prod-db-secret, et un appelé, test-user avec le secret test-db-secret. Ensuite, la spécification du pod peut être raccourcie, par exemple:

apiVersion: v1
kind: Pod
metadata:
  name: prod-db-client-pod
  labels:
    name: prod-db-client
spec:
  serviceAccount: prod-db-client
  containers:
  - name: db-client-container
    image: myClientImage

Cas d'utilisation: Dotfiles dans un volume secret

Afin de masquer des données (c'est-à-dire dans un fichier dont le nom commence par un point), il suffit de faire commencer cette clé par un point. Par exemple, lorsque le secret suivant est monté dans un volume:

apiVersion: v1
kind: Secret
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: k8s.gcr.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

Le secret-volume contiendra un seul fichier, appelé .secret-file, et le dotfile-test-container aura ce fichier présent au chemin /etc/secret-volume/.secret-file.

Cas d'utilisation: secret visible pour un conteneur dans un pod

Envisagez un programme qui doit gérer les requêtes HTTP, effectuer une logique métier complexe, puis signer certains messages avec un HMAC. Parce qu'il a une logique d'application complexe, il pourrait y avoir un exploit de lecture de fichier à distance inaperçu dans le serveur, qui pourrait exposer la clé privée à un attaquant.

Cela pourrait être divisé en deux processus dans deux conteneurs: un conteneur frontal qui gère l'interaction utilisateur et la logique métier, mais qui ne peut pas voir la clé privée; et un conteneur de signataire qui peut voir la clé privée, et répond aux demandes de signature simples du frontend (par exemple sur le réseau localhost).

Avec cette approche partitionnée, un attaquant doit maintenant inciter le serveur d'applications à faire quelque chose d'assez arbitraire, ce qui peut être plus difficile que de lui faire lire un fichier.

Les meilleures pratiques

Clients qui utilisent l'API secrets

Lors du déploiement d'applications qui interagissent avec l'API secrets, l'accès doit être limité à l'aide de politiques d'autorisation telles que RBAC.

Les secrets contiennent souvent des valeurs qui couvrent un spectre d'importance, dont beaucoup peuvent provoquer des escalades au sein de Kubernetes (par exemple, les jetons de compte de service) et vers les systèmes externes. Même si une application individuelle peut raisonner sur la puissance des secrets avec lesquels elle s'attend à interagir, d'autres applications dans le même namespace peuvent rendre ces hypothèses invalides.

Pour ces raisons, les requêtes watch et list pour les secrets dans un namespace sont des capacités extrêmement puissantes et doivent être évitées, puisque la liste des secrets permet aux clients d'inspecter les valeurs de tous les secrets qui se trouvent dans ce namespace. La capacité à effectuer un watch ou list des secrets dans un cluster doit être réservé uniquement aux composants les plus privilégiés au niveau du système.

Les applications qui ont besoin d'accéder à l'API secrets doivent effectuer des requêtes get sur les secrets dont elles ont besoin. Cela permet aux administrateurs de restreindre l'accès à tous les secrets tout en donnant accès en liste blanche aux instances individuelles dont l'application a besoin.

Pour des performances améliorées sur une boucle get, les clients peuvent concevoir des ressources qui font référence à un secret puis watch la ressource, demandant à nouveau le secret lorsque la ressource change. De plus, un "bulk watch" API laisse les clients watch des ressources individuelles ont également été proposées et seront probablement disponibles dans les prochaines versions de Kubernetes.

Propriétés de sécurité

Protections

Étant donné que les objets secrets peuvent être créés indépendamment des Pods qui les utilisent, il y a moins de risques que le secret soit exposé pendant la création, la visualisation et la modification des Pods. Le système peut également prendre des précautions supplémentaires avec les objets secrets, comme éviter de les écrire sur le disque lorsque cela est possible.

Un secret n'est envoyé à un nœud que si un module sur ce nœud l'exige. Kubelet stocke le secret dans un tmpfs afin que le secret ne soit pas écrit sur le stockage sur disque. Une fois que le pod qui dépend du secret est supprimé, kubelet supprimera également sa copie locale des données secrètes.

Il peut y avoir des secrets pour plusieurs pods sur le même nœud. Cependant, seuls les secrets qu'un pod demande sont potentiellement visibles dans ses conteneurs. Par conséquent, un pod n'a pas accès aux secrets d'un autre pod.

Il peut y avoir plusieurs conteneurs dans un pod. Cependant, chaque conteneur d'un pod doit demander le volume secret dans ses volumesMounts pour qu'il soit visible dans le conteneur. Cela peut être utilisé pour construire des partitions de sécurité au niveau du pod.

Sur la plupart des distributions gérées par le projet Kubernetes, la communication entre l'utilisateur vers l'apiserver et entre l'apiserver et les kubelets est protégée par SSL/TLS. Les secrets sont protégés lorsqu'ils sont transmis sur ces canaux.

FEATURE STATE: Kubernetes v1.13 [beta]

Vous pouvez activer le chiffrement au repos pour les données secrètes, afin que les secrets ne soient pas stockés en clair dans etcd.

Risques

  • Dans le serveur API, les données secrètes sont stockées dans etcd; par conséquent:
    • Les administrateurs doivent activer le chiffrement au repos pour les données du cluster (nécessite la version 1.13 ou ultérieure)
    • Les administrateurs devraient limiter l'accès à etcd aux utilisateurs administrateurs
    • Les administrateurs peuvent vouloir effacer/détruire les disques utilisés par etcd lorsqu'ils ne sont plus utilisés
    • Si vous exécutez etcd dans un cluster, les administrateurs doivent s'assurer d'utiliser SSL/TLS pour la communication peer-to-peer etcd.
  • Si vous configurez le secret via un fichier manifeste (JSON ou YAML) qui a les données secrètes codées en base64, partager ce fichier ou l'archiver dans un dépot de source signifie que le secret est compromis. L'encodage Base64 n'est pas une méthode de chiffrement, il est considéré comme identique au texte brut.
  • Les applications doivent toujours protéger la valeur du secret après l'avoir lu dans le volume, comme ne pas le mettre accidentellement dans un journal ou le transmettre à une partie non fiable.
  • Un utilisateur qui peut créer un pod qui utilise un secret peut également voir la valeur de ce secret. Même si la stratégie apiserver ne permet pas à cet utilisateur de lire l'objet secret, l'utilisateur peut créer un pod qui expose le secret.
  • Actuellement, toute personne disposant des droit root sur n'importe quel nœud peut lire n'importe quel secret depuis l'apiserver, en usurpant l'identité du kubelet. Il est prévu de n'envoyer des secrets qu'aux nœuds qui en ont réellement besoin, pour limiter l'impact d'un exploit root sur un seul nœud.

A suivre

8 - Sécurité

9 - Politiques

10 - Administration d'un cluster

Administration cluster Kubernetes

10.1 - Vue d'ensemble de l'administration d'un cluster

Administration cluster Kubernetes

La vue d'ensemble de l'administration d'un cluster est destinée à toute personne créant ou administrant un cluster Kubernetes. Il suppose une certaine familiarité avec les concepts de Kubernetes.

Planifier le déploiement d'un cluster

Voir le guide: choisir la bonne solution pour des exemples de planification, de mise en place et de configuration de clusters Kubernetes. Les solutions répertoriées dans cet article s'appellent des distributions.

Avant de choisir un guide, voici quelques considérations:

  • Voulez-vous simplement essayer Kubernetes sur votre machine ou voulez-vous créer un cluster haute disponibilité à plusieurs nœuds? Choisissez les distributions les mieux adaptées à vos besoins.
  • Si vous recherchez la haute disponibilité, apprenez à configurer des clusters multi zones.
  • Utiliserez-vous un cluster Kubernetes hébergé, comme Google Kubernetes Engine, ou hébergerez-vous votre propre cluster?
  • Votre cluster sera-t-il on-premises, ou sur un cloud (IaaS)? Kubernetes ne prend pas directement en charge les clusters hybrides. Cependant, vous pouvez configurer plusieurs clusters.
  • Si vous configurez Kubernetes on-premises, choisissez le modèle réseau qui vous convient le mieux.
  • Voulez-vous faire tourner Kubernetes sur du bare metal ou sur des machines virtuelles (VMs)?
  • Voulez-vous simplement faire tourner un cluster, ou vous attendez-vous à faire du développement actif sur le code du projet Kubernetes? Dans ce dernier cas, choisissez une distribution activement développée. Certaines distributions n’utilisent que des versions binaires, mais offrent une plus grande variété de choix.
  • Familiarisez-vous avec les composants nécessaires pour faire tourner un cluster.

A noter: Toutes les distributions ne sont pas activement maintenues. Choisissez des distributions qui ont été testées avec une version récente de Kubernetes.

Gérer un cluster

  • Gérer un cluster décrit plusieurs rubriques relatives au cycle de vie d’un cluster: création d’un nouveau cluster, mise à niveau des nœuds maître et des workers de votre cluster, maintenance des nœuds (mises à niveau du noyau, par exemple) et mise à niveau de la version de l’API Kubernetes d’un cluster en cours d’exécution.

  • Apprenez comment gérer les nœuds.

  • Apprenez à configurer et gérer les quotas de ressources pour les clusters partagés.

Sécuriser un cluster

Sécuriser la Kubelet

Services de cluster optionnels

10.2 - Certificats

Certifications cluster Kubernetes

Lorsque vous utilisez l'authentification par certificats client, vous pouvez générer des certificats manuellement grâce à easyrsa, openssl ou cfssl.

easyrsa

easyrsa peut générer manuellement des certificats pour votre cluster.

  1. Téléchargez, décompressez et initialisez la version corrigée de easyrsa3.

    curl -LO https://storage.googleapis.com/kubernetes-release/easy-rsa/easy-rsa.tar.gz
    tar xzf easy-rsa.tar.gz
    cd easy-rsa-master/easyrsa3
    ./easyrsa init-pki
    
  2. Générez une CA. (--batch pour le mode automatique. --req-cn CN par défaut à utiliser)

    ./easyrsa --batch "--req-cn=${MASTER_IP}@`date +%s`" build-ca nopass
    
  3. Générer un certificat de serveur et une clé. L' argument --subject-alt-name définit les adresses IP et noms DNS possibles par lesquels l'API serveur peut être atteind. La MASTER_CLUSTER_IP est généralement la première adresse IP du CIDR des services qui est spécifié en tant qu'argument --service-cluster-ip-range pour l'API Server et le composant controller manager. L'argument --days est utilisé pour définir le nombre de jours après lesquels le certificat expire. L’exemple ci-dessous suppose également que vous utilisez cluster.local par défaut comme nom de domaine DNS.

    ./easyrsa --subject-alt-name="IP:${MASTER_IP},"\
    "IP:${MASTER_CLUSTER_IP},"\
    "DNS:kubernetes,"\
    "DNS:kubernetes.default,"\
    "DNS:kubernetes.default.svc,"\
    "DNS:kubernetes.default.svc.cluster,"\
    "DNS:kubernetes.default.svc.cluster.local" \
    --days=10000 \
    build-server-full server nopass
    
  4. Copiez pki/ca.crt, pki/issued/server.crt, et pki/private/server.key dans votre répertoire.

  5. Personnalisez et ajoutez les lignes suivantes aux paramètres de démarrage de l'API Server:

    --client-ca-file=/yourdirectory/ca.crt
    --tls-cert-file=/yourdirectory/server.crt
    --tls-private-key-file=/yourdirectory/server.key
    

openssl

openssl peut générer manuellement des certificats pour votre cluster.

  1. Générez ca.key en 2048bit:

    openssl genrsa -out ca.key 2048
    
  2. A partir de la clé ca.key générez ca.crt (utilisez -days pour définir la durée du certificat):

    openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt
    
  3. Générez server.key en 2048bit:

    openssl genrsa -out server.key 2048
    
  4. Créez un fichier de configuration pour générer une demande de signature de certificat (CSR). Assurez-vous de remplacer les valeurs marquées par des "< >" (par exemple, <MASTER_IP>) avec des valeurs réelles avant de l'enregistrer dans un fichier (par exemple, csr.conf). Notez que la valeur de MASTER_CLUSTER_IP est celle du service Cluster IP pour l' API Server comme décrit dans la sous-section précédente. L’exemple ci-dessous suppose également que vous utilisez cluster.local par défaut comme nom de domaine DNS.

    [ req ]
    default_bits = 2048
    prompt = no
    default_md = sha256
    req_extensions = req_ext
    distinguished_name = dn
    
    [ dn ]
    C = <country>
    ST = <state>
    L = <city>
    O = <organization>
    OU = <organization unit>
    CN = <MASTER_IP>
    
    [ req_ext ]
    subjectAltName = @alt_names
    
    [ alt_names ]
    DNS.1 = kubernetes
    DNS.2 = kubernetes.default
    DNS.3 = kubernetes.default.svc
    DNS.4 = kubernetes.default.svc.cluster
    DNS.5 = kubernetes.default.svc.cluster.local
    IP.1 = <MASTER_IP>
    IP.2 = <MASTER_CLUSTER_IP>
    
    [ v3_ext ]
    authorityKeyIdentifier=keyid,issuer:always
    basicConstraints=CA:FALSE
    keyUsage=keyEncipherment,dataEncipherment
    extendedKeyUsage=serverAuth,clientAuth
    subjectAltName=@alt_names
    
  5. Générez la demande de signature de certificat basée sur le fichier de configuration:

    openssl req -new -key server.key -out server.csr -config csr.conf
    
  6. Générez le certificat de serveur en utilisant ca.key, ca.crt et server.csr:

    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out server.crt -days 10000 \
    -extensions v3_ext -extfile csr.conf
    
  7. Vérifiez le certificat:

    openssl x509  -noout -text -in ./server.crt
    

Enfin, ajoutez les mêmes paramètres aux paramètres de démarrage de l'API Server.

cfssl

cfssl est un autre outil pour la génération de certificat.

  1. Téléchargez, décompressez et préparez les outils de ligne de commande comme indiqué ci-dessous. Notez que vous devrez peut-être adapter les exemples de commandes en fonction du matériel, de l'architecture et de la version de cfssl que vous utilisez.

    curl -L https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -o cfssl
    chmod +x cfssl
    curl -L https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -o cfssljson
    chmod +x cfssljson
    curl -L https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -o cfssl-certinfo
    chmod +x cfssl-certinfo
    
  2. Créez un répertoire pour contenir les artefacts et initialiser cfssl:

    mkdir cert
    cd cert
    ../cfssl print-defaults config > config.json
    ../cfssl print-defaults csr > csr.json
    
  3. Créez un fichier JSON pour générer le fichier d'autorité de certification, par exemple, ca-config.json:

    {
      "signing": {
        "default": {
          "expiry": "8760h"
        },
        "profiles": {
          "kubernetes": {
            "usages": [
              "signing",
              "key encipherment",
              "server auth",
              "client auth"
            ],
            "expiry": "8760h"
          }
        }
      }
    }
    
  4. Créez un fichier JSON pour la demande de signature de certificat de l'autorité de certification, par exemple, ca-csr.json. Assurez-vous de remplacer les valeurs marquées par des "< >" par les vraies valeurs que vous voulez utiliser.

    {
      "CN": "kubernetes",
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names":[{
        "C": "<country>",
        "ST": "<state>",
        "L": "<city>",
        "O": "<organization>",
        "OU": "<organization unit>"
      }]
    }
    
  5. Générez la clé de CA (ca-key.pem) et le certificat (ca.pem):

    ../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca
    
  6. Créer un fichier JSON pour générer des clés et des certificats pour l'API Server, par exemple, server-csr.json. Assurez-vous de remplacer les valeurs entre "< >" par les vraies valeurs que vous voulez utiliser. MASTER_CLUSTER_IP est le service Cluster IP de l'API Server, comme décrit dans la sous-section précédente. L’exemple ci-dessous suppose également que vous utilisez cluster.local par défaut comme nom de domaine DNS.

    {
      "CN": "kubernetes",
      "hosts": [
        "127.0.0.1",
        "<MASTER_IP>",
        "<MASTER_CLUSTER_IP>",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
      ],
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [{
        "C": "<country>",
        "ST": "<state>",
        "L": "<city>",
        "O": "<organization>",
        "OU": "<organization unit>"
      }]
    }
    
  7. Générez la clé et le certificat pour l'API Server, qui sont par défaut sauvegardés respectivement dans les fichiers server-key.pem et server.pem:

    ../cfssl gencert -ca=ca.pem -ca-key=ca-key.pem \
    --config=ca-config.json -profile=kubernetes \
    server-csr.json | ../cfssljson -bare server
    

Distribuer un certificat auto-signé

Un client peut refuser de reconnaître un certificat auto-signé comme valide. Pour un déploiement hors production ou pour un déploiement exécuté derrière un pare-feu d'entreprise, vous pouvez distribuer un certificat auto-signé à tous les clients et actualiser la liste locale pour les certificats valides.

Sur chaque client, effectuez les opérations suivantes:

$ sudo cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....
done.

API pour les certificats

Vous pouvez utiliser l’API certificates.k8s.io pour faire créer des Certificats x509 à utiliser pour l'authentification, comme documenté ici.

10.3 - Architecture de Journalisation d'évènements (logging)

La journalisation des évènements systèmes et d'applications peut aider à comprendre ce qui se passe dans un cluster. Les journaux sont particulièrement utiles pour débogguer les problèmes et surveiller l'activité du cluster. La plupart des applications modernes ont un mécanisme de journalisation d'évènements, et la plupart des environnements d'exécution de conteneurs ont été conçus pour supporter la journalisation des évènements. La méthode de journalisation la plus facile et la plus répandue pour des applications conteneurisées est d'écrire dans les flux de sortie standard et d'erreur (stdout et stderr).

Malgré cela, la fonctionnalité de journalisation fournie nativement par l'environnement d'exécution de conteneurs n'est pas suffisante comme solution complète de journalisation. Quand un conteneur crashe, quand un Pod est expulsé ou quand un nœud disparaît, il est utile de pouvoir accéder au journal d'événements de l'application. C'est pourquoi les journaux doivent avoir leur propre espace de stockage et un cycle de vie indépendamment des nœuds, Pods ou conteneurs. Ce concept est appelé journalisation des évènements au niveau du cluster (cluster-level-logging). Un backend dédié pour stocker, analyser et faire des requêtes est alors nécessaire. Kubernetes n'offre pas nativement de solution de stockage pour les journaux mais il est possible d'intégrer de nombreuses solutions de journalisation d'évènements dans un cluster Kubernetes.

L'architecture de journalisation des évènements au niveau du cluster est décrite en considérant qu'un backend de journalisation est présent à l'intérieur ou à l'extérieur du cluster. Même sans avoir l'intention de journaliser les évènements au niveau du cluster, il est intéressant de savoir comment les journaux sont conservés et gérés au niveau d'un nœud.

Journalisation simple d'évènements dans Kubernetes

Dans cette section, on va utiliser un exemple simple de journalisation d'évènements avec le flux de sortie standard. Cette démonstration utilise un manifeste pour un Pod avec un seul conteneur qui écrit du texte sur le flux de sortie standard toutes les secondes.

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

Pour lancer ce Pod, utilisez la commande suivante :

kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml

Le résultat est :

pod/counter created

Pour récupérer les événements du conteneur d'un pod, utilisez la commande kubectl logs de la manière suivante :

kubectl logs counter

Le résultat est :

0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...

Utilisez kubectl logs pour récupérer les évènements de l'instanciation précédente d'un Pod en utilisant l'option --previous quand par exemple le conteneur a crashé.

Si le Pod a plusieurs conteneurs, il faut spécifier le nom du conteneur dont on veut récupérer le journal d'évènement. Dans notre exemple le conteneur s'appelle count donc vous pouvez utiliser kubectl logs counter count. Plus de détails dans la [documentation de kubectl logs] (/docs/reference/generated/kubectl/kubectl-commands#logs)

Journalisation d'évènements au niveau du nœud

Journalisation d'évènements au niveau dunœud

Tout ce qu'une application conteneurisée écrit sur stdout ou stderr est pris en compte et redirigé par l'environnement d'exécution des conteneurs. Par exemple, Docker redirige ces deux flux vers un driver de journalisation (EN) qui est configuré dans Kubernetes pour écrire dans un fichier au format json.

Par défaut quand un conteneur redémarre, le kubelet ne conserve le journal que du dernier conteneur terminé. Quand un Pod est expulsé d'un nœud, tous ses conteneurs sont aussi expulsés avec leurs journaux d'évènements.

Quand on utilise la journalisation d'évènements au niveau du nœud, il faut prendre garde à mettre en place une politique de rotation des journaux adéquate afin qu'ils n'utilisent pas tout l'espace de stockage du nœud. Kubernetes n'a pas en charge la rotation des journaux, c'est à l'outil de déploiement de le prendre en compte.

Par exemple, dans les clusters Kubernetes déployés avec le script kube-up.sh logrotate est configuré pour s'exécuter toutes les heures. Il est aussi possible de configurer l'environnement d'exécution des conteneurs pour que la rotation des journaux s'exécute automatiquement, e.g. en utilisant le paramètre log-opt de Docker. Dans le script kube-up.sh, c'est cette méthode qui est utilisée pour des images COS sur GCP et sinon c'est la première méthode dans tous les autres cas. Quelle que soit la méthode choisie par kube-up.sh la rotation est configurée par défaut quand la taille d'un journal atteint 10 Mo.

Ce script montre de manière détaillée comment kube-up.sh met en place la journalisation d'évènements pour des images COS sur GCP.

Quand kubectl logs s'exécute comme dans le premier exemple de journalisation simple le kubelet du nœud gère la requête et lit directement depuis le fichier de journal et retourne son contenu dans la réponse.

Journalisation des évènements des composants système

Il y a deux types de composants système selon qu'ils s'exécutent dans un conteneur ou pas.

Par exemple :

  • Le scheduler Kubernetes et kube-proxy s'exécutent dans un conteneur.
  • Kubelet et l'environnement d'exécution de conteneurs, comme par exemple Docker, ne s'exécutent pas dans un conteneur.

Sur les systèmes avec systemd, kubelet et l'environnement d'exécution de conteneurs écrivent dans journald. Si systemd n'est pas présent, ils écrivent dans un fichier .log dans le répertoire /var/log.

Les composants système qui s'exécutent dans un conteneur écrivent toujours dans le répertoire /var/log, en contournant le mécanisme de journalisation par défaut. Ils utilisent la bibliothèque de journalisation klog. Les conventions pour la sévérité des évènements pour ces composants se trouvent dans cette [documentation sur les conventions de journalisation des évènements dans kubernetes] (https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md).

De la même manière que les journaux des conteneurs, les journaux des composants systèmes doivent avoir une politique de rotation. Dans un cluster créé avec le script kube-up.sh, les journaux ont une rotation journalière ou quand leur taille atteint 100 Mo.

Architecture de journalisation des évènements au niveau du cluster

Kubernetes ne fournit pas de solution native pour la journalisation des évènements au niveau du cluster. Mais il y a différentes approches qui peuvent être considérées :

  • Utiliser un agent de journalisation au niveau du nœud sur chacun des nœuds.
  • Inclure un conteneur side-car pour journaliser les évènements du Pod applicatif.
  • Envoyer les évènements directement a un backend depuis l'application.

Utiliser un agent de journalisation au niveau du nœud

Utiliser un agent de journalisation au niveau dunœud

Vous pouvez implémenter une journalisation au niveau du cluster en incluant un agent de journalisation au niveau du nœud sur chacun des nœuds. L'agent de journalisation est un outil dédié qui met à disposition ou envoie les journaux à un backend. Communément l'agent de journalisation est un conteneur qui a accès au répertoire qui contient les journaux des conteneurs applicatifs sur ce nœud.

Comme l'agent de journalisation doit s'exécuter sur chacun des nœuds, on utilise soit un DaemonSet, soit un manifeste de Pod, soit un processus dédié natif sur le nœud. Ces deux dernières options sont obsolètes et fortement découragées.

Utiliser un agent de journalisation au niveau du nœud est l'approche la plus commune et recommandée pour un cluster Kubernetes parce qu'un seul agent par nœud est créé et qu'aucune modification dans l'application n'est nécessaire. Mais cette approche ne fonctionne correctement que pour les flux standards de sortie et d'erreurs des applications.

Kubernetes ne définit pas d'agent de journalisation, mais deux agents de journalisation optionnels sont fournis avec la version de Kubernetes : Stackdriver (EN) pour utiliser sur Google Cloud Platform, et Elasticsearch (EN). Les deux utilisent fluentd avec une configuration spécifique comme agent sur le nœud. Les liens précédents fournissent plus d'informations et les instructions pour les utiliser et configurer.

Inclure un conteneur side-car pour journaliser les évènements du Pod applicatif

Vous pouvez utiliser un conteneur side-car d'une des manières suivantes :

  • Le conteneur side-car diffuse les journaux de l'application sur son propre stdout.
  • Le conteneur side-car exécute un agent de journalisation qui est configuré pour récupérer les journaux du conteneur applicatif.

Conteneur side-car diffusant (Streaming sidecar container)

Conteneur side-cardiffusant

Comme le conteneur side-car diffuse les journaux sur ses propres flux stdout et stderr, on peut bénéficier du kubelet et de l'agent de journalisation qui s'exécute déjà sur chaque nœud. Les conteneurs side-car lisent les journaux depuis un fichier, un socket ou bien journald. Chaque conteneur side-car écrit son journal sur son propre flux stdout ou stderr.

Cette méthode permet de séparer les flux de journaux de différentes parties de votre application même si elles ne supportent pas d'écrire sur stdout ou stderr. La logique de rediriger les journaux est minime et le surcoût est non significatif. De plus comme les flux standards stdout et stderr sont gérés par kubelet, les outils natifs comme kubectl logs peuvent être utilisés.

Regardez l'exemple qui suit.

Un Pod exécute un unique conteneur et ce conteneur écrit dans deux fichiers de journaux différents en utilisant deux format différents. Voici le manifeste du Pod :

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

Il serait très désordonné d'avoir des évènements avec des formats différents dans le même journal en redirigeant les évènements dans le flux de sortie stdout d'un seul conteneur. Il est plutôt souhaitable d'utiliser deux conteneurs side-car, un pour chaque type de journaux. Chaque conteneur side-car suit un des fichiers et renvoie les évènements sur son propre stdout.

Ci-dessous se trouve le manifeste pour un Pod avec deux conteneurs side-car.

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-1
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

Quand ce Pod s'exécute, chaque journal peut être diffusé séparément en utilisant les commandes suivantes :

kubectl logs counter count-log-1
0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...
kubectl logs counter count-log-2
Mon Jan  1 00:00:00 UTC 2001 INFO 0
Mon Jan  1 00:00:01 UTC 2001 INFO 1
Mon Jan  1 00:00:02 UTC 2001 INFO 2
...

L'agent au niveau du nœud installé dans le cluster récupère les deux flux de journaux sans aucune configuration supplémentaire. Il est possible de configurer l'agent pour qu'il analyse syntaxiquement les évènements en fonction du conteneur source.

Notez que bien que la consommation en CPU et mémoire soit faible ( de l'ordre de quelques milicores pour la CPU et quelques mégaoctets pour la mémoire), ecrire les évènements dans un fichier et les envoyer ensuite dans stdout peut doubler l'espace disque utilisé. Quand une application écrit dans un seul fichier de journal, il est préférable de configurer /dev/stdout comme destination plutôt que d'implémenter un conteneur side-car diffusant.

Les conteneurs side-car peuvent être utilisés pour faire la rotation des journaux quand l'application n'en est pas capable elle-même. Un exemple serait un petit conteneur side-car qui effectuerait cette rotation périodiquement. Toutefois, il est recommandé d'utiliser stdout et stderr directement et de laisser la rotation et les politiques de rétentions au kubelet.

Conteneur side-car avec un agent de journalisation

Conteneur side-car avec un agent dejournalisation

Quand un agent de journalisation au niveau du nœud n'est pas assez flexible pour votre utilisation, vous pouvez créer un conteneur side-car avec un agent de journalisation séparé que vous avez configuré spécialement pour qu'il s'exécute avec votre application.

Comme exemple, vous pouvez utiliser Stackdriver où fluentd est l'agent de journalisation. Ci-dessous se trouvent deux configurations qui implémentent cette méthode.

Le premier fichier contient un ConfigMap pour configurer fluentd.

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    <source>
      type tail
      format none
      path /var/log/1.log
      pos_file /var/log/1.log.pos
      tag count.format1
    </source>

    <source>
      type tail
      format none
      path /var/log/2.log
      pos_file /var/log/2.log.pos
      tag count.format2
    </source>

    <match **>
      type google_cloud
    </match>    

Le second fichier est un manifeste pour un Pod avec un conteneur side-car qui exécute fluentd. Le Pod monte un volume où fluentd peut récupérer sa configuration.

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-agent
    image: k8s.gcr.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

Apres quelques minutes, les évènements apparaîtront dans l'interface de Stackdriver.

Ce n'est qu'un exemple et vous pouvez remplacer fluentd par n'importe quel agent de journalisation qui lit depuis n'importe quelle source de votre application.

Envoyer les évènements directement depuis l'application.

Envoyer les évènements directement a un backend depuisl'application.

Vous pouvez implémenter la journalisation au niveau cluster en mettant à disposition ou en envoyant les journaux directement depuis chaque application; Toutefois l'implémentation de ce mécanisme de journalisation est hors du cadre de Kubernetes.

11 - Extensions Kubernetes

11.1 - Extensions de l'API Kubernetes

11.2 - Extensions compute, stockage et réseau