Seidor
StatefulSets

19 mars 2024

StatefulSets, gestion optimisée des applications avec état

Qu'est-ce qu'un StatefulSet ?

Nous pouvons le définir comme l'objet API de charge de travail et de gestion des applications avec état, également connues sous le nom de "stateful applications". Il gère le déploiement et le dimensionnement d'un ensemble de pods, offrant ainsi des garanties sur l'ordre et l'unicité des pods qu'il gère.

À l'instar des déploiements, un StatefulSet gère des pods basés sur des spécifications de conteneur identiques entre eux, mais à la différence des déploiements, un StatefulSet maintient une identité fixe pour chacun de ses pods. C'est-à-dire que ces pods, bien qu'ils soient créés à partir des mêmes spécifications, ne sont pas interchangeables entre eux, ils possèdent un identifiant persistant qui se maintient lors de toute reprogrammation ((re)scheduling).

Si nous souhaitons utiliser des volumes de stockage pour obtenir une persistance dans notre charge de travail, les StatefulSets peuvent faire partie d'une bonne solution. Bien que ces pods appartenant à un StatefulSet soient susceptibles de tomber en panne, leurs identifiants persistants nous faciliteront la connexion des volumes existants avec les nouveaux pods qui remplaceront les pods défaillants.

Quand utiliser un StatefulSet ?

Les StatefulSets sont particulièrement utiles dans les cas ou exigences suivants.

  • Identifiants réseau uniques et stables.
  • Stockage stable et persistant.
  • Déploiements et mises à l'échelle ordonnés et sans impact.
  • Mises à jour automatiques, continues et ordonnées.

Lorsque nous nous référons à des applications qui ne nécessitent pas d'identifiant persistant, de déploiement ordonné, de suppressions ou de mises à l'échelle.
Dans ce cas, nous devons déployer notre application à l'aide d'objets de charge de travail qui fournissent un ensemble de répliques sans état. Par exemple, un ReplicaSet ou un Deployment serait plus efficace dans ce cas.

Limitations à prendre en compte

  • Le stockage pour un pod donné doit être provisionné via un “PersistentVolume” ou volume persistant en fonction de la classe de stockage demandée, ou bien, préalablement par l'administrateur.
  • Supprimer et/ou réduire un StatefulSet ne supprimera pas les volumes associés à celui-ci. Étant donné que nous essayons toujours de garantir la sécurité et la persistance des données, généralement plus précieuses qu'une suppression automatique de toutes les ressources associées au StatefulSet.
  • Actuellement, les StatefulSets nécessitent un service “Headless” ou sans tête qui doit être responsable de l'identité réseau des pods déployés. Dans ce cas, nous devons créer ce service de notre côté.
  • Les StatefulSets n'offrent aucune garantie en termes de terminaison des pods en raison de la suppression du StatefulSet. Pour garantir une terminaison ordonnée et sans impact des pods, nous devons utiliser la possibilité de mettre à l'échelle le StatefulSet à 0 avant de le supprimer.
  • Lorsque nous travaillons avec des mises à jour continues (rolling updates) sur les politiques de gestion des pods par défaut (OrderedReady), il est possible de recevoir un état d'interruption ou de rupture nécessitant une intervention manuelle pour sa réparation.

Composants d'un StatefulSet Kubernetes

Analysons l'exemple suivant d'un StatefulSet.

L'exemple suivant a été extrait de la documentation officielle de Kubernetes et ne révèle donc aucun type de configuration ou de données sensibles.

statefulset

Observations :

  • Ligne 1-13 : Le service "headless" ou sans tête, appelé "nginx" et utilisé pour contrôler le domaine du réseau. Nous pouvons ouvrir un port spécifique et lui attribuer un alias.
  • Ligne 15-49 : Le StatefulSet appelé “web” a une “spec” (spécification) à la ligne 24 qui nous indique le nombre de répliques du conteneur “nginx” qui seront lancées dans des pods uniques.
  • Ligne 41-49 : Le “volumeClaimTemplate” nous facilitera le stockage stable (persistant) en utilisant un “PersistentVolume” approvisionné par le “PersistentVolume Provisioner”.

Le nom de l'objet "StatefulSet" doit être un nom valide pour le sous-domaine spécifié dans le DNS.

  • Ligne 20-22 : Nous devons spécifier une étiquette, mais en tenant compte du fait qu'elle doit être la même que l'étiquette spécifiée dans le champ "app" à la ligne 29. Ne pas étiqueter correctement le champ du "Pod selector" entraînera une erreur de validation lors du processus de création du StatefulSet.
  • Ligne 25 : Nous pouvons spécifier “minReadySeconds” dans “specs” (spécifications) qui est un champ optionnel et détermine le nombre de secondes qui doivent s'écouler depuis la création d'un nouveau pod en cours d'exécution et en état prêt ou “ready” sans qu'aucun de ses conteneurs ne se casse pour être considéré en état disponible ou “available”. Cette option est particulièrement utile lorsque nous appliquons des mises à jour continues (rolling updates) pour vérifier l'avancement de cette mise à jour.

Identité du pod

Les pods qui appartiennent à un StatefulSet possèdent un identifiant unique composé d'un ordinal (numéro ordinal), d'une identité de réseau persistante et d'un stockage persistant également. L'identité ou l'identifiant est associé au pod, peu importe sur quel nœud il est programmé ((re)schedule).

Indice ordinal

Pour un StatefulSet avec n réplicas, chaque pod se verra attribuer un numéro entier ordinal de 0 à n-1, unique dans son ensemble.

Identifiant Persistant de Réseau

Chaque pod d'un StatefulSet dérive son nom du nom du StatefulSet et de l'ordinal du pod. Le modèle pour le nom de l'hôte (pod) construit est $(nom du statefulset)-$(ordinal).

Le StatefulSet peut utiliser un service "headless" ou sans tête pour contrôler le domaine de ses pods. Ce domaine a la nomenclature suivante $(nom du service).$(namespace).svc.cluster.local, où "cluster.local" est le domaine du cluster.

À mesure que chaque pod est créé, nous obtenons un sous-domaine DNS correspondant, sous la forme suivante : $(podname).$(domaine du service), où le domaine du service est défini par le champ “serviceName” dans le StatefulSet.

Selon la configuration du DNS dans votre cluster, il est possible que vous ne puissiez pas rechercher le nom DNS pour un pod nouvellement exécuté. Ce comportement se produit lorsque d'autres clients dans le cluster ont déjà envoyé des requêtes à ce nom d'hôte du pod avant qu'il ne soit créé. La mise en cache négative signifie que les résultats des recherches échouées précédentes sont mémorisés et réutilisés, même après avoir exécuté le pod.

Pour éviter que ce problème ne se produise et ne pas avoir quelques secondes de chute ou d'erreurs après avoir créé un pod, nous pouvons utiliser l'une des solutions décrites ci-dessous.

  • Lancer les requêtes directement via l'API de Kubernetes (par exemple, en utilisant un watch) au lieu de se fier aux recherches DNS.
  • Réduire le temps de mise en cache chez votre fournisseur DNS dans Kubernetes (normalement configurer le “configmap” pour CoreDNS, généralement avec une valeur de 30s).

"Rappelez-vous que, comme mentionné dans la section des limitations, nous sommes responsables de créer le service headless (sans tête) responsable de l'identité réseau des pods."

Voici quelques exemples de la façon dont le domaine du cluster, le nom du service et le nom du StatefulSet affectent le DNS des pods.

statefulset

Stockage persistant

Pour chaque entrée “VolumeClaimTemplate” définie dans un StatefulSet, chaque pod reçoit un “PersistentVolumeClaim”. Dans l'exemple de chaque “nginx” mentionné précédemment, chaque pod reçoit un seul PersistentVolume avec StorageClass “my-storage-class“ et un 1Gib de stockage provisionné.

Lorsqu'un pod est reprogrammé ((re)schedule) sur un nœud, ses "VolumeMounts" montent les "PersistentVolumes" associés à leurs "PersistentVolumesClaims" respectifs.

Étiquette de nom du pod (Etiqueta de nombre del pod)

Lorsque le “Controller” du StatefulSet crée un pod, il ajoute une étiquette, “statefulset.kubernetes.io/pod-name”, qui est définie sur le nom du pod. Cette même étiquette nous permet d'associer un service à un pod spécifique dans le StatefulSet.

Garanties de Déploiement et d'Évolutivité

Si nous avons un StatefulSet avec n répliques, lorsque les pods sont déployés, ils le font de manière séquentielle dans l'ordre suivant {0..N-1}.

Lorsque les pods sont en cours de suppression, ils le font de manière inverse, c'est-à-dire du {N-1..0}.

Avant qu'une opération de mise à l'échelle ne soit appliquée à un pod, tous ses prédécesseurs doivent être dans l'état "Running" et "Ready".

Avant qu'un pod ne soit terminé, tous ses successeurs doivent être complètement fermés.

Nous ne devons jamais spécifier une valeur de 0 dans “pod.Spec.TerminationGracePeriodSeconds”. Cette pratique est dangereuse et n'est jamais recommandée.

Stratégies de Mise à Jour

Le champ .spec.updateStrategy nous permet de configurer ou de désactiver les mises à jour continues (rolling updates) automatiques pour les conteneurs, les étiquettes, les ressources requests et limits ainsi que les annotations pour les pods dans un StatefulSet. Nous avons deux valeurs possibles.

OnDelete : Lorsque nous définissons le type de « updateStrategy » à « OnDelete », le contrôleur du StatefulSet ne mettra automatiquement pas à jour les pods qui appartiennent aux pods. Les utilisateurs devront supprimer manuellement les pods pour forcer le contrôleur à créer de nouveaux pods qui reflètent les modifications apportées au modèle du StatefulSet.

RollingUpdate : Cette valeur met en œuvre les mises à jour automatiques, par défaut, c'est la valeur définie pour la stratégie de mise à jour.

Mises à jour continues

Lorsque la stratégie de mises à jour (spec.updateStrategy.type) d'un StatefulSet est définie sur RollingUpdate, le contrôleur (controller) du StatefulSet supprimera et recréera chaque pod dans le StatefulSet. Cela se fait dans le même ordre que lorsque nous terminons les pods (du plus grand ordinal au plus petit). En mettant à jour chaque pod un par un.

Le controlplane de Kubernetes attend jusqu'à ce qu'un pod mis à jour soit en cours d'exécution et prêt avant de mettre à jour son prédécesseur. Le controlplane respecte la valeur que nous avons assignée à "spec.minReadySeconds" depuis que le pod change son état à "prêt" avant de continuer à mettre à jour le reste des pods.

Particiones

La stratégie de mise à jour "rolling update" nous permet de faire la mise à jour de manière partielle. Si nous spécifions une partition (spec.updaterStrategy.rollingUpdate.partition), tous les pods ayant un ordinal supérieur à la valeur de la partition seront mis à jour lorsque le modèle (spec.template) du StatefulSet sera modifié.

Les pods qui ont un ordinal inférieur à la valeur de la partition ne seront pas mis à jour et même s'ils sont supprimés et redéployés, ils le feront avec l'ancienne version du modèle du StatefulSet. Si la valeur de la partition est supérieure au nombre de répliques (spec.replicas), aucun pod ne sera mis à jour.

Recul forcé

Lorsque nous utilisons la stratégie de mise à jour par défaut “OrderedReady”, il est possible d'entrer dans un état d'échec nécessitant une intervention manuelle pour le réparer. Si nous mettons à jour le modèle d'un pod et que notre configuration ne passe jamais à l'état “running” ou “ready”, le StatefulSet cessera de se mettre à jour et restera en attente.

Cet état n'est pas suffisant pour pouvoir rétablir le modèle de pod à une bonne configuration. En raison d'un problème connu, le StatefulSet continuera d'attendre que l'état du pod défectueux passe à "ready", ce qui n'arrivera jamais, avant d'essayer de revenir à la configuration fonctionnelle.

Une fois le modèle rétabli, nous devons supprimer les pods du StatefulSet qui ont été déployés avec la configuration erronée. Une fois cela fait, le même StatefulSet commencera à recréer les pods en utilisant le modèle rétabli à la configuration correcte.

Répliques

Le champ optionnel “spec.replicas” spécifie le nombre souhaité de pods, la valeur par défaut est 1.

Si nous devons mettre à l'échelle automatiquement un déploiement, nous pouvons utiliser directement la commande kubectl ou bien modifier le fichier ".yml" et redéployer le StatefulSet. Avec la commande kubectl :

"kubectl scale statefulset statefulset –replicas=X"

Si nous avons un "HorizontalPodAutoscaler" ou une API similaire pour l'auto-scaling, nous ne devons pas spécifier ce champ car le plan de contrôle de Kubernetes agira pour nous.