Kubernetes : Deployments, Services et Ingress
Ceci couvre les réglages du Deployment, du Service et de l'Ingress, la stratégie de rollout, les probes readiness/liveness, et un PodDisruptionBudget, qui déterminent si une mauvaise release passe inaperçue ou provoque une panne.
Le Deployment : replicas, ressources et stratégie de rollout
Le Deployment est le contrôleur qui maintient N copies de votre pod en fonctionnement et gère la transition d'une version à l'autre. Ce que les gens négligent le plus souvent, ce sont resources et strategy.
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
labels:
app: checkout-api
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
spec:
containers:
- name: checkout-api
image: registry.example.com/checkout-api:1.8.2
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"maxUnavailable: 0 combiné à maxSurge: 1 signifie que Kubernetes crée toujours un pod supplémentaire avant de retirer un ancien, donc vous ne descendez jamais en dessous de votre capacité complète, au prix d'un rollout légèrement plus lent et d'un bref moment où vous tournez avec N+1 pods, un compromis que je fais systématiquement pour tout service exposé aux utilisateurs. Le réglage inverse (maxUnavailable: 1, maxSurge: 0) supprime un pod avant que son remplaçant soit prêt, ce qui transforme régulièrement un "déploiement de routine" en incident de capacité sur un service qui tournait déjà proche de ses limites.
Sur resources : réglez requests sur ce dont le conteneur a réellement besoin en charge normale : c'est cette valeur que le scheduler utilise pour placer les pods, et sur laquelle reposent les calculs du PodDisruptionBudget et de l'autoscaling. Gardez les limits CPU proches des requests ; une limite généreuse déplace juste le throttling causé par un voisin bruyant ailleurs. La mémoire est différente : dépasser la limite provoque un OOMKill du conteneur, donc laissez-vous une marge à cet endroit.
Readiness vs. liveness probes : deux questions différentes
Confondre les deux cause plus d'incidents en production que tout le reste sur cette liste : les deux probes se ressemblent en YAML mais ont des effets opposés en cas d'échec.
- La readiness probe : "ce pod doit-il recevoir du trafic maintenant ?" Son échec retire le pod de la liste des endpoints du Service ; il continue de tourner, simplement hors rotation.
- La liveness probe : "ce processus est-il encore suffisamment vivant, ou faut-il le tuer et le redémarrer ?" Son échec tue le conteneur.
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3Ne pointez jamais votre liveness probe vers un endpoint qui vérifie des dépendances externes (base de données, cache, API tierce) : si votre base de données a une minute de latence, une liveness probe qui teste la connectivité DB va tuer tous les pods en même temps, transformant un incident passager en panne complète. La readiness peut vérifier la santé des dépendances externes ; la liveness ne devrait vérifier que si son propre processus répond.
Réglez la fenêtre failureThreshold × periodSeconds trop courte sur une liveness probe, et elle redémarrera un pod simplement lent sous charge, pas réellement défaillant, annulant les requêtes en cours. Si toute la flotte subit le même pic de charge, vous obtenez un crash loop auto-infligé : le "remède" à la lenteur consiste à tuer en boucle les pods qui essaient justement de rattraper le retard. Dans le doute, donnez à la liveness une fenêtre plus longue et plus tolérante qu'à la readiness.
Le Service : comment le trafic trouve réellement un pod
Les pods sont éphémères et changent d'IP en permanence ; un Service leur donne une IP virtuelle stable et un nom DNS, à l'aide d'un label selector qui détermine l'appartenance.
apiVersion: v1
kind: Service
metadata:
name: checkout-api
spec:
type: ClusterIP
selector:
app: checkout-api
ports:
- port: 80
targetPort: 8080
protocol: TCPLe selector ici (app: checkout-api) doit correspondre aux labels du template de pod dans le Deployment, pas aux métadonnées du Deployment lui-même. C'est le bug de copier-coller le plus fréquent que je rencontre : quelqu'un renomme le Deployment, oublie de mettre à jour les labels du template de pod, et le Service se retrouve silencieusement avec zéro endpoint. kubectl get endpoints checkout-api est la première commande que je lance dès qu'on me dit "le Service ne fonctionne pas" : une liste vide signifie un mismatch de selector neuf fois sur dix.
L'Ingress : terminaison TLS et routage
Un ClusterIP ne route le trafic qu'à l'intérieur du cluster. Un Ingress fait le lien entre un nom d'hôte externe et ce Service interne et, associé à cert-manager, gère la terminaison TLS sans que vous ayez à manipuler un certificat à la main.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: checkout-api
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: checkout-api-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: checkout-api
port:
number: 80L'annotation cert-manager.io/cluster-issuer déclenche la demande et le renouvellement automatiques du certificat par cert-manager, qui le stocke dans le Secret checkout-api-tls nommé sous tls.secretName, l'expiration du certificat cesse ainsi d'être quelque chose qu'un humain doit suivre.
Le chemin complet d'une requête, y compris pendant un rolling update :
PodDisruptionBudget : survivre aux vidages de nodes
Tout ce qui précède vous protège pendant un rollout que vous maîtrisez, pas pendant un vidage de node : un cluster autoscaler qui réduit la voilure, un node mis en maintenance, une VM sous-jacente remplacée. Sans PodDisruptionBudget, Kubernetes est libre d'évincer tous les pods de votre Deployment présents sur un même node d'un coup, y compris jusqu'à zéro replica.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: checkout-api-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: checkout-apiminAvailable: 2 indique à l'API d'éviction de ne jamais descendre volontairement ce Deployment en dessous de 2 pods prêts. Les vidages de nodes, les scale-downs de l'autoscaler et kubectl drain respectent tous cette contrainte, évinçant les pods un par un plutôt que tous en même temps. C'est un petit objet, et c'est celui que la plupart des équipes oublient, jusqu'au jour où une montée de version de node de routine en met un à terre.
Deployment, Service et Ingress ne sont pas compliqués pris isolément. Les équipes se font piéger en les déployant une fois, en copiant le YAML depuis un tutoriel, et en ne revenant jamais ajouter les probes, la stratégie de rollout ou le PDB avant que le service ne porte du vrai trafic. Réglez-les correctement dès le départ, et un rollout devient ennuyeux. En production, ennuyeux vaut mieux qu'intéressant.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez