Guides

Guide Kubernetes

Services sans état sur Kubernetes : concevoir, déployer et monter en charge en toute sécurité

Un service sans état peut être remplacé à tout moment sans perdre le contexte durable nécessaire à l'exécution correcte du travail. Concevez d'abord cette propriété avant d'ajouter des réplicas.

Kubernetes rend le remplacement normal : les pods sont redémarrés, replanifiés, mis à niveau et mis à l'échelle. Un service qui dépend de la mémoire, du disque local ou de l'identité réseau d'un pod finira par échouer de façons difficiles à reproduire. Une conception sans état transforme ces événements de cycle de vie en opérations courantes.

Sans état ne signifie pas qu'un service ne possède aucun état. Cela signifie que l'état métier durable réside dans un système externe adapté et que toute instance peut traiter une requête à l'aide de cet état. Un pod peut mettre des données en cache ou détenir une connexion en cours, mais l'exactitude ne doit pas dépendre de la survie de ces données transitoires.

Identifier l'état avant de le déplacer

Listez tout élément qu'un processus conserve au-delà d'une requête unique : sessions de connexion, progression d'upload, propriété des jobs, compteurs de limitation de débit, clés d'idempotence, fichiers générés, données de routage WebSocket et entrées de cache. Pour chaque élément, déterminez s'il est durable, reconstructible ou jetable.

  • Stockez les enregistrements durables dans une base de données, un stockage d'objets ou une file avec un modèle explicite de rétention et de cohérence.
  • Stockez l'état éphémère partagé, tel qu'une session distribuée ou un bail, dans un stockage partagé spécialisé lorsqu'il est réellement nécessaire.
  • Conservez les caches reconstructibles en local uniquement si un cache froid ne nuit pas à l'exactitude ni ne surcharge l'origine.
  • Considérez les systèmes de fichiers locaux aux pods comme temporaires, sauf si une charge de travail utilise délibérément un volume persistant et ses contraintes opérationnelles.

Évitez d'utiliser des sessions persistantes pour masquer un état. L'affinité peut améliorer la localité du cache, mais elle ne protège pas contre le redémarrage d'un pod, un rollout ou une défaillance de zone. Si une action utilisateur doit atteindre la même instance pour réussir, le système a une contrainte de disponibilité et de mise à l'échelle qui doit être explicitée.

Construire un service sans état déployable

Commencez par une image immuable et un contrat d'exécution réduit et explicite. Le processus doit se lier au port configuré, exposer des endpoints de santé, lire la configuration de l'environnement de déploiement et émettre les logs vers la sortie standard ou un collecteur. Il ne doit pas attendre un nœud, une IP publique ou un répertoire applicatif inscriptible particulier.

Utilisez un Deployment pour les services HTTP ou gRPC indépendamment remplaçables. Attribuez-lui des demandes de ressources fondées sur une demande stable mesurée et des limites choisies pour empêcher une charge de travail de nuire aux autres. Une limite CPU peut provoquer du throttling et une forte latence de queue ; mesurez avant de l'appliquer partout. Les limites mémoire sont une protection essentielle, mais un redémarrage par manque de mémoire n'est pas un plan de capacité.

Probes de readiness, de liveness et de démarrage

Configurez les probes pour refléter des questions distinctes :

  • Démarrage : l'application a-t-elle terminé son initialisation bornée ?
  • Readiness : cette instance peut-elle accepter le trafic qui lui est attribué maintenant ?
  • Liveness : le processus est-il irrémédiablement bloqué et mérite-t-il un redémarrage ?

Gardez les endpoints de probe peu coûteux et indépendants de l'authentification utilisateur. Ne leur faites pas exécuter de requêtes coûteuses ni appeler chaque dépendance aval optionnelle. Lors d'une panne, une vérification de liveness sensible aux dépendances peut provoquer une tempête de redémarrages ; la readiness peut retirer une instance du trafic lorsqu'elle ne peut pas servir en sécurité, tandis que les circuit breakers et les solutions de repli protègent les appels aux dépendances.

Un Pod Disruption Budget n'est pas une garantie de disponibilité

Un Pod Disruption Budget limite les perturbations volontaires, sous réserve des conditions du cluster. Il n'empêche pas les crashs applicatifs, les pénuries de capacité, les défaillances de nœuds ou un mauvais rollout. Concevez les réplicas, la répartition topologique et l'arrêt progressif pour les modes de défaillance auxquels vous devez résister.

Drainez correctement les requêtes et le travail

Kubernetes envoie SIGTERM avant de retirer un conteneur après son délai de grâce de terminaison. L'application doit coopérer. À la terminaison, faites immédiatement échouer la readiness, cessez d'accepter de nouvelles connexions et laissez aux requêtes en cours un temps borné pour se terminer. Alignez le drainage du load balancer, le comportement pre-stop, le délai d'arrêt de l'application et terminationGracePeriodSeconds afin qu'ils ne se contredisent pas.

Pour les workers asynchrones, utilisez des files avec accusé de réception et des handlers idempotents. Un worker peut être arrêté après avoir reçu un message mais avant d'avoir enregistré son résultat. Rendez les livraisons en double sûres à l'aide d'une clé d'idempotence durable ou du pattern transactional outbox. Ne prolongez le bail d'un message que tant que le worker est sain et routez le travail qui échoue à répétition vers un processus de dead-letter ayant un responsable.

Les connexions longue durée méritent un plan distinct. Les WebSockets, les réponses en streaming et les uploads importants ne peuvent pas être drainés comme un appel d'API de 50 ms. Définissez un âge maximal de connexion, communiquez le comportement de reconnexion aux clients et déployez progressivement. Pour une audience mondiale, réfléchissez à l'emplacement de la terminaison des connexions et de l'état applicatif ; une couche de connexion en périphérie peut réduire la latence aller-retour, mais le protocole d'origine nécessite toujours une reconnexion sûre et une récupération d'état.

Monter en charge sans déplacer le goulot d'étranglement

Le Horizontal Pod Autoscaling peut ajouter des réplicas selon le CPU, la mémoire ou des métriques personnalisées. Choisissez un signal lié à la capacité utile. La profondeur ou l'âge de file, les requêtes actives ou la concurrence peuvent être plus significatifs que le CPU pour un service limité par les E/S. Validez la boucle complète : fraîcheur des métriques, délai de montée en charge, capacité des nœuds, durée de pull d'image, préchauffage de l'application et limites de connexions aval.

Définissez une limite de concurrence dans l'application ou la couche proxy. Sinon, davantage de trafic peut créer un nombre non borné de goroutines, threads, promises ou appels de base de données dans chaque réplica. La contre-pression est une fonctionnalité d'exactitude : mettez le travail en file, écartez le travail de faible priorité, renvoyez une réponse réessayable ou servez un résultat mis en cache ou dégradé avant que le service n'atteigne la défaillance.

La planification de capacité doit inclure les ressources partagées. Si chaque nouveau pod ouvre vingt connexions à la base de données, un autoscaler qui passe de dix à cent pods demande deux mille connexions. Limitez les pools par pod, utilisez un proxy conscient de la base de données lorsque c'est pertinent et alertez sur le temps d'attente du pool, pas uniquement sur le CPU des pods.

Rendre la latence visible de la périphérie à la dépendance

Mesurez la latence vue par l'utilisateur ainsi que les métriques du cluster. Un pod sain peut tout de même produire une expérience lente à cause du DNS, de TLS, des cache misses, du routage régional, d'une chaîne d'API sérielle ou d'une base de données surchargée. Propagez le contexte de trace W3C depuis l'ingress ou la périphérie vers les clients de service et de dépendance, puis attachez la route, la version de release, la région et le résultat à la télémétrie.

Suivez la latence p50, p95 et p99, le taux d'erreur, la saturation, l'âge de file, l'attente du pool de connexions et le taux de cache hit. Corrélez-les avec les événements de rollout et d'autoscaling. Les recommandations OpenTelemetry pour Kubernetes constituent un bon point de départ pour collecter des signaux sans faire des logs l'unique source de vérité.

Pour le trafic public pouvant être mis en cache, utilisez des règles Cache-Control et de clé de cache correctes en périphérie. Explicitez les routes authentifiées, personnalisées et de mutation. La mise en cache en périphérie réduit la charge à l'origine uniquement lorsque l'invalidation, les variations et les sémantiques d'autorisation sont correctes ; elle ne doit jamais mettre une réponse en cache simplement parce que l'origine était lente.

Tester le contrat de remplacement

Avant de qualifier un service de sans état, exécutez des tests contrôlés :

  1. Supprimez un pod pendant un trafic proche du réel et vérifiez que les requêtes sont drainées ou réessayées en sécurité.
  2. Déployez une nouvelle version tout en conservant des requêtes longues et du travail en file.
  3. Augmentez et réduisez les réplicas en observant les connexions de base de données et la latence de queue.
  4. Simulez un délai d'attente de dépendance et vérifiez les échéances, les circuit breakers et le comportement de repli.
  5. Retirez un nœud ou une zone dans un environnement hors production et confirmez les hypothèses de topologie et de capacité.

Testez également un démarrage à froid. Une image qui met des minutes à être téléchargée ou initialisée ne peut pas répondre rapidement à un pic de trafic, même si l'autoscaler choisit le bon nombre de réplicas.

Références faisant autorité

Concevez un chemin de livraison qui se dégrade de manière prévisible

Optimi peut vous aider à évaluer l'architecture de périphérie, d'origine, de cache et de routage du trafic autour de vos services hébergés sur Kubernetes.

Évaluer la résilience de la livraison