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:
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.
Pour resources, réglez requests sur ce dont le conteneur a besoin en charge normale. Le scheduler s'en sert pour placer les pods, et le HPA calcule l'utilisation en pourcentage de ces requests. Je ne mets pas de limite CPU. Le CPU est compressible, donc une limite CPU ne fait que throttler le conteneur alors que le node a des cycles libres, et la request CPU lui garantit déjà sa part en cas de contention. Pour la mémoire, c'est autre chose. Un conteneur qui dépasse sa limite mémoire est OOMKillé, donc fixez une limite mémoire en gardant de la marge au-dessus de la request.
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. Les métadonnées du Deployment lui-même ne comptent pas. C'est le bug de copier-coller que je croise le plus souvent. Quelqu'un renomme le Deployment, oublie de mettre à jour les labels du template de pod, et le Service se retrouve avec zéro endpoint sans la moindre erreur. Quand on me dit "le Service ne fonctionne pas", la première commande que je lance est kubectl get endpointslices -l kubernetes.io/service-name=checkout-api. Elle remplace kubectl get endpoints, qui affiche un avertissement de dépréciation depuis Kubernetes 1.33. D'expérience, un slice sans endpoint vient presque toujours d'un selector qui ne correspond pas.
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: 80ingressClassName: nginx et les annotations nginx.ingress.kubernetes.io/* ciblent le contrôleur communautaire ingress-nginx, qui a été retiré en mars 2026 et ne reçoit plus ni nouvelles versions ni correctifs de sécurité. L'API Ingress elle-même fonctionne toujours avec d'autres contrôleurs, mais sur un nouveau cluster je passerais directement à Gateway API (ci-dessous). Si vous faites encore tourner ingress-nginx, planifiez la migration pour ne pas garder un proxy d'entrée non patché devant la production.
L'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.
L'équivalent avec Gateway API
Gateway API succède à Ingress. L'opérateur du cluster gère la Gateway (listeners, TLS), et chaque équipe y rattache sa propre HTTPRoute. Voici le même routage, avec un certificat toujours émis par cert-manager :
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
gatewayClassName: gke-l7-global-external-managed # utilisez la GatewayClass de votre implémentation
listeners:
- name: https
hostname: api.example.com
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: checkout-api-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: checkout-api
spec:
parentRefs:
- name: public
sectionName: https
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: checkout-api
port: 80Vérifiez deux choses avant de copier ceci. D'abord, la gatewayClassName dépend de votre contrôleur. kubectl get gatewayclass liste celles qui sont installées, et celle ci-dessus correspond au load balancer externe managé de GKE. Ensuite, cert-manager ne surveille les Gateways qu'une fois son support de Gateway API activé (config.gatewayAPI.enabled: true dans les values Helm des versions actuelles).
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 sous 2 pods prêts. Les vidages de nodes, les scale-downs de l'autoscaler et kubectl drain passent tous par cette API et respectent cette contrainte. Avec 4 replicas sains, deux pods peuvent quand même être évincés en même temps. Si vous voulez des évictions strictement une par une, utilisez maxUnavailable: 1 à la place de minAvailable. Ce réglage reste aussi correct quand le HPA fait varier le nombre de replicas. La plupart des équipes font l'impasse sur cet objet jusqu'au jour où une montée de version de node de routine fait tomber un service.
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
Tutoriels similaires
Pipelines GitOps avec ArgoCD & Helm
Une configuration GitOps multi-environnements avec Argo CD et Helm : app-of-apps, surcouches par environnement, auto-réparation.
Conteneuriser une app avec Docker Compose
Écrivez un Dockerfile multi-étapes minimaliste et un docker-compose.yml pour votre application et sa base de données.
Réduire le lead time de déploiement avec le GitOps
Remplacez les approbations manuelles par du GitOps avec Argo CD et un canary, pour livrer en petits lots sous contrôle.