Pods, Deployments, Services, sondes et ordonnancement : les questions Kubernetes posées en entretien DevOps et SRE.
Cliquez sur une question pour dérouler la réponse attendue.
Un Pod est l’unité qui tourne. On le laisse à un contrôleur, parce qu’un Pod orphelin ne revient pas si le nœud disparaît.
Lire la réponse détailléeLe ReplicaSet a un seul métier : tenir un nombre de Pods dont les labels matchent son sélecteur. Un Pod meurt, il en crée un autre. C’est tout.
Le Deployment possède des ReplicaSets. C’est lui qui sait faire une mise à jour, garder un historique de révisions, et revenir en arrière. Quand vous changez l’image, le Deployment crée un nouveau ReplicaSet, fait monter ses Pods, et fait descendre l’ancien. Pendant quelques instants, les deux ReplicaSets existent : c’est le rolling update.
Vous ne créez presque jamais un ReplicaSet à la main, pour la même raison que vous ne créez pas un Pod à la main : il lui manque la couche qui parle de versions. kubectl create deployment et un manifeste kind: Deployment suffisent ; le ReplicaSet apparaît tout seul, nommé d’après un hash du template.
Que vous savez lire kubectl get rs pendant un déploiement. Deux ReplicaSets, l’un dont le DESIRED monte, l’autre qui descend : la mise à jour est en cours. Un seul ReplicaSet à zéro et un autre au bon nombre : c’est fini. kubectl rollout undo ne « répare » pas un Pod : il réactive le ReplicaSet précédent.
Le piège à nommer : si vous touchez au sélecteur du Deployment sans toucher aux labels du template, Kubernetes refuse l’update. C’est voulu. Autrement, le Deployment orphelinerait des Pods qu’il ne reconnaîtrait plus, et un autre ReplicaSet pourrait les adopter. Le sélecteur est un contrat, pas un détail de YAML.
Les trois types répondent à la même question — qui a le droit d’atteindre ce Service — et pas à « comment on répartit le trafic ». Le répartiteur est le même ; change seulement l’endroit d’où on peut l’appeler.
ClusterIP (le défaut) : une adresse interne au cluster. Les autres Pods l’utilisent, via l’IP ou le DNS. C’est le type de presque tous les Services d’une application : API, base, cache. Rien n’est exposé hors du cluster.
NodePort : ClusterIP, plus un port statique ouvert sur chaque nœud. http://<ip-dun-noeud>:<nodePort> atteint le Service, même si aucun Pod ne tourne sur ce nœud. Utile en labo, ou derrière un équilibreur que vous gérez vous-même. En production cloud, c’est rarement l’objet que vous montrez au monde : le port est haut (30000-32767 par défaut), et vous publiez l’IP des nœuds.
LoadBalancer : NodePort, plus un équilibreur provisionné par le cloud (ELB, Azure LB, etc.). Une adresse externe arrive, le fournisseur la câble sur les nœuds. C’est le type « exposer un Service TCP/UDP au-dehors » quand vous n’avez pas d’Ingress, ou pour du trafic qui n’est pas du HTTP.
Un Ingress n’est pas un quatrième type de Service. C’est un objet HTTP devant des Services, le plus souvent ClusterIP. Beaucoup de candidats récitent les trois types puis proposent un LoadBalancer par microservice : c’est cher, et ce n’est pas l’architecture habituelle. Un LoadBalancer (ou un Ingress) à l’entrée, des ClusterIP partout derrière.
ExternalName et le Service headless (clusterIP: None) existent ; on les mentionne si on vous relance, pas pour noyer la réponse. Le headless sert surtout aux StatefulSets : le DNS renvoie les Pods, pas une IP virtuelle.
Parce que l’IP d’un Pod est un accident. Elle change à chaque recréation : rolling update, nœud perdu, sonde de vivacité qui tue le conteneur, simple kubectl delete pod. Noter 10.1.0.5 dans la config d’un voisin, c’est noter une adresse qui sera fausse. Ce n’est pas un bug, c’est le modèle : les Pods sont jetables.
Le Service met un nom et une IP virtuelle stables devant un ensemble mouvant. Le contrôleur d’Endpoints (aujourd’hui les EndpointSlices) suit les Pods dont les labels matchent le sélecteur, et qui sont ready. kube-proxy — ou le dataplane du CNI — traduit l’IP du Service vers un Pod vivant. CoreDNS publie mon-service.mon-namespace.svc.cluster.local. Vous couplez un nom, pas une machine.
Que vous reliez Service, sondes et labels. Un Service n’est pas une balise magique : si le sélecteur ne correspond à aucun Pod, le Service existe et répond vide. Si la readiness échoue, le Pod tourne mais sort des Endpoints : le trafic s’arrête, c’est voulu. Les deux pannes se lisent avec kubectl get endpoints / kubectl describe svc, pas en pingant l’IP du Pod.
La phrase à avoir : on n’adresse jamais un Pod de charge utile par son IP, hors debug. Même kubectl port-forward vers un Pod est un palliatif ; vers le Service, c’est déjà plus proche de la réalité.
La liveness redémarre, la readiness retire du Service, la startup protège le démarrage. Confondre les deux premières crée des pannes.
Lire la réponse détailléeDeux chiffres, deux métiers. Les mélanger est la réponse junior ; les séparer est celle qu’on attend.
La request est une réservation pour le scheduler. Kubernetes additionne les requests des Pods déjà placés sur un nœud, et refuse d’y mettre le vôtre si le reste ne suffit pas. C’est une promesse de capacité, pas une mesure d’usage. Sans request, le scheduler place « où ça passe » : le nœud a l’air vide, puis tout le monde se réveille.
La limit est un plafond au runtime, appliqué par le cgroup. Au-delà, le comportement dépend de la ressource :
OOMKilled, code 137). La mémoire n’est pas compressible.C’est pourquoi une limit CPU trop basse donne une appli lente et des sondes qui timeout, alors qu’une limit mémoire trop basse donne un redémarrage. On ne « règle » pas les deux avec le même réflexe.
Si vous posez une limit sans request, Kubernetes copie la limit dans la request. Si cela vaut pour le CPU et la mémoire de tous les conteneurs, le Pod devient Guaranteed ; sinon il reste Burstable. Si vous posez une request sans limit, le Pod peut exploser au-delà — et se faire évincer plus tôt en cas de pression. Les deux extrêmes se défendent ; l’absence totale des deux, rarement, hors Job jetable.
L’intervieweur veut aussi le lien avec le HPA : l’utilisation CPU est un ratio sur la request. Une request de 100m et une conso de 100m, c’est 100 %, même si la limit est à 1 CPU. Dimensionner la request « pour que ça passe » trop bas fait scaler pour de mauvaises raisons.
En une phrase : la request décide où le Pod atterrit et comment on le mesure ; la limit décide comment il meurt ou ralentit.
La classe QoS n’est pas un champ que vous écrivez. Kubernetes la déduit des requests et des limits, et s’en sert surtout quand le nœud manque de mémoire. Ce n’est pas un score de qualité de service réseau.
Guaranteed : chaque conteneur du Pod a une request et une limit, et elles sont égales, pour le CPU et pour la mémoire. C’est le plus protégé à l’éviction : on ne le tue pour pression mémoire que s’il dépasse sa propre request — ce qu’une limit égale rend normalement impossible.
Burstable : au moins une request (ou une limit) est posée, sans satisfaire Guaranteed. C’est le cas le plus fréquent. En pression, les Burstable sont triés par usage au-dessus de leur request : celui qui a le plus « emprunté » part en premier.
BestEffort : aucune request, aucune limit, nulle part. Placement facile, survie faible : premiers évacués quand le nœud étouffe.
Que vous ne récitez pas les trois noms pour ensuite dire « donc on met tout en Guaranteed ». Guaranteed coûte : vous réservez tout ce que vous plafonnez, donc vous remplissez le nœud plus vite, et vous perdez le burst. Un cache, un worker batch, un sidecar de debug peuvent rester Burstable ou même BestEffort. Un kube-apiserver, un etcd, le dataplane, non.
La relance : l’éviction n’est pas le throttling CPU. Une limite CPU Guaranteed bride, elle ne classe pas le Pod pour l’OOM du nœud. Et un Pod Guaranteed mal dimensionné — limit trop basse — meurt tout seul par OOMKilled sans que le nœud soit en pression. QoS protège contre le voisin, pas contre votre propre plafond.
Pending veut dire que tous les conteneurs ne tournent pas encore. On sépare d’abord FailedScheduling d’un pull d’image, puis on lit les Events.
Lire la réponse détailléeCrashLoopBackOff veut dire : le conteneur a démarré, puis il est sorti, et le kubelet relance avec un délai qui double. Ce n’est pas un échec de pull, ce n’est pas un Pending d’ordonnancement. Le process vit assez longtemps pour mourir.
On ne « augmente pas les retries ». On lit pourquoi il meurt.
kubectl logs <pod> -c <conteneur>
kubectl logs <pod> -c <conteneur> --previous
kubectl describe pod <pod>--previous est la commande que beaucoup oublient : le conteneur courant peut n’avoir encore rien écrit ; les logs utiles sont ceux de l’instance tuée. describe donne le code de sortie et le Last State.
command / args, migration qui échoue, dépendance absente. Les logs le disent.describe avant de toucher au code.describe montre Liveness probe failed puis Killing.Que vous séparez CrashLoopBackOff, ImagePullBackOff et Pending unschedulable. Les trois s’affichent « ça ne tourne pas » ; les trois se traitent ailleurs. Et que vous ne proposez pas restartPolicy: Never comme solution : sur un Deployment, le contrôleur recréera un Pod, vous n’aurez que perdu les logs.
Le scheduler filtre les nœuds capables (ressources, taints, volumes, ports), puis score ceux qui restent. nodeSelector et l’affinity interviennent dans le filtre — parfois dans le score.
nodeSelector est la forme courte : une carte de labels, toutes exigées. disk=ssd et tier=app : le nœud doit avoir les deux. Pas d’opérateur, pas de préférence. C’est lisible, et ça suffit pour un pool dédié.
nodeAffinity dit la même chose avec une grammaire. requiredDuringSchedulingIgnoredDuringExecution : si aucun nœud ne match, le Pod reste Pending — ignored during execution signifie qu’un changement de label après coup n’évince pas le Pod. preferredDuringSchedulingIgnoredDuringExecution : un poids, le scheduler préfère sans bloquer. Les opérateurs In, NotIn, Exists, Gt permettent « n’importe quelle zone sauf celle-ci » sans lister tous les nœuds.
podAffinity / podAntiAffinity parlent des autres Pods. Colocaliser un cache avec l’API (topologyKey: kubernetes.io/hostname), ou au contraire étaler les réplicas pour qu’un nœud perdu n’emporte pas tout. L’anti-affinité required sur le hostname avec trois réplicas et deux nœuds laisse un Pod Pending : c’est le piège classique.
Que vous choisissiez required seulement quand l’alternative est dangereuse (GPU, licence, disque local), et preferred pour la résilience. Et que vous nommiez le topologyKey : sans lui, l’anti-affinité ne veut rien dire. kubernetes.io/hostname, topology.kubernetes.io/zone ne produisent pas le même étalement.
nodeName existe : il force un nœud, en sautant le scheduler. C’est du dépannage, pas de l’architecture. On le mentionne pour montrer qu’on sait que ça court-circuite toutes les règles ci-dessus.
L’affinity attire. Le taint repousse. C’est la distinction que l’intervieweur attend en une phrase.
Un taint se pose sur le nœud : une clé, une valeur, un effet. Aucun Pod n’y atterrit, sauf s’il porte une toleration qui match. Les nœuds de contrôle-plane portent presque toujours le taint node-role.kubernetes.io/control-plane:NoSchedule : vos Deployments n’ont rien à y faire.
Trois effets :
NoSchedule : le scheduler refuse les nouveaux Pods. Ce qui tourne déjà reste.PreferNoSchedule : souple, le scheduler évite si possible.NoExecute : refuse les nouveaux et évince les Pods déjà là qui ne tolèrent pas — avec un tolerationSeconds possible.Pour réserver un pool : GPU, machines à licence, nœuds spot, nœuds d’ingress. Le taint empêche le reste du cluster de s’y installer « parce qu’il restait de la CPU ». L’affinity seule n’empêche pas un Pod sans sélecteur de s’y poser.
La toleration n’est pas une affinité : un Pod qui tolère le taint GPU peut atterrir sur un nœud GPU, il n’y est pas obligé. Si vous voulez les deux — « seulement ces nœuds, et ces nœuds seulement pour nous » — vous combinez taint + nodeSelector / nodeAffinity. Oublier la moitié, c’est soit polluer le pool, soit laisser le Pod Pending.
L’intervieweur relance souvent sur cordon / drain. kubectl drain pose un taint NoExecute temporaire et évince. Comprendre ça, c’est comprendre que vous avez déjà vu une mise à jour de nœuds.
Parce qu’une image est figée, et que c’est sa qualité. Ce qui a été scanné et promu en staging est ce qui tourne. Une couleur, une URL interne, un niveau de log n’ont rien à faire dans ce colis : les changer ne doit pas demander un docker build.
Le ConfigMap tient ces paires clé-valeur hors de l’image. Vous les injectez en variables d’environnement, ou en fichiers montés. Même image, trois namespaces, trois configs : dev, staging, prod ne se distinguent plus par un tag app:dev.
Une variable d’environnement est lue au démarrage. Modifier le ConfigMap ne relance pas les Pods, et ne change pas les process déjà nés. Un volume peut finir par refléter la nouvelle valeur — avec un délai, et seulement si l’appli relit le fichier. En pratique, on annote le Deployment avec un hash du ConfigMap, ou on fait un rollout, pour que la config et le process changent ensemble.
L’autre frontière : rien de sensible n’y entre. Un ConfigMap est un objet API que tout le monde dans le namespace peut souvent get. Mot de passe, jeton, clé privée : c’est un Secret — et encore, le Secret n’est pas une coffre-fort, c’est une autre question.
Le tutoriel ConfigMap : la configuration hors de l’image montre le cas où l’on reconstruit une image pour six caractères hexadécimaux, et pourquoi c’est le mauvais réflexe.
Le Secret isole les valeurs sensibles du manifeste et de l’image. Il n’est pas chiffré par défaut : il encode, il ne verrouille pas etcd.
Lire la réponse détailléeUn rolling update remplace les Pods un par un : les deux versions servent ensemble. La base doit rester compatible avec les deux.
Lire la réponse détailléeLe Service est un objet de couche 4 : une IP stable, un port, un sélecteur de Pods. Il ne lit pas l’hôte HTTP, ni le chemin, ni le certificat. ClusterIP, NodePort, LoadBalancer changent qui peut joindre cette IP, pas le métier du Service.
L’Ingress est un objet de couche 7. Il dit : api.exemple.com/v2 va vers ce Service, www.exemple.com vers cet autre, le TLS se termine ici. Il pointe vers des Services, il ne les remplace pas. Sans Service derrière, un Ingress n’a personne à qui parler.
kind: Ingress ne fait rien tout seul. Ce n’est pas un contrôleur du control plane. Un Ingress controller — nginx, HAProxy, Traefik, le ALB controller AWS — doit tourner dans le cluster (souvent en DaemonSet ou Deployment) et implémenter l’objet. Sans lui, l’Ingress est un manifeste que personne ne réconcilie. Beaucoup de « ça ne marche pas » commencent là.
L’architecture habituelle : un LoadBalancer (ou un NodePort derrière un équilibreur) vers le controller, des Ingress pour le routage HTTP, des ClusterIP pour les applis. Un LoadBalancer par microservice est l’anti-pattern inverse.
Gateway API est le successeur plus expressif ; on le mentionne si on vous demande « et aujourd’hui ? ». Pour l’entretien DevOps classique, Ingress vs Service et « qui exécute l’Ingress » suffisent. Le TCP brut, les bases, gRPC selon les controllers : ce n’est plus le même objet, et le forcer via des annotations propriétaires est une dette.
Un namespace est un périmètre de noms et d’objets. Deux Deployments api peuvent coexister s’ils sont dans equipe-a et equipe-b. Les DNS internes suivent : api.equipe-a.svc.cluster.local. C’est d’abord un tiroir, pas un hypervisor.
Ce qu’on y accroche ensuite, et qui justifie d’en créer :
kube-system.replicas: 200 ne vide pas le cluster.Ce n’est pas une frontière de sécurité dure. Les Pods partagent le kernel des nœuds, le réseau par défaut est ouvert, un Secret mal RBAC’é se lit depuis un autre namespace si le sujet a le verbe. Pour du multi-tenant hostile, on parle de clusters séparés, de sandboxes, de nœuds dédiés — pas d’un namespace tout seul.
default, kube-system, kube-public, kube-node-lease existent dès l’install. On n’y met pas l’application. kubectl config set-context --current --namespace=... évite le piège du « j’ai appliqué dans default sans le voir ».
Le HPA change le champ replicas d’un Deployment, ReplicaSet ou StatefulSet. Il compare une métrique à une cible, et écrit le nombre qu’il croit juste, entre minReplicas et maxReplicas. C’est tout. Il ne crée pas de nœud, il ne gonfle pas un Pod.
La métrique classique est le CPU (ou la mémoire) par rapport à la request. 80 % de cible, request à 200m, usage à 200m : le ratio est déjà à 100 %, le HPA scale. Sans request, le ratio n’a pas de dénominateur utilisable : le HPA ne peut pas faire son métier, ou il le fait n’importe comment. Les métriques custom et externes (file SQS, requêtes par seconde via Prometheus) existent ; on les cite, on ne les invente pas.
Il n’ajoute pas de capacité au cluster. Si tous les nœuds sont pleins, les nouveaux Pods restent Pending. Le Cluster Autoscaler ou Karpenter réagit à ça — des Pods unschedulable — pas au HPA directement. Les deux se chaînent : HPA crée des Pods, le CA crée des nœuds, ou l’inverse à la descente.
Il ne remplace pas non plus le VPA (taille d’un Pod) ni un KEDA trop spécifique. Et il oscille si la fenêtre de stabilisation est trop courte ou si la readiness est lente : les Pods montent, la métrique baisse, on redescend, on remonte. L’intervieweur aime entendre behavior, stabilizationWindowSeconds, pas seulement targetCPUUtilizationPercentage.
La phrase de clôture : le HPA scale des réplicas ready, sur une métrique dont vous maîtrisez le dénominateur. Sans request, sans sonde, sans marge de nœuds, c’est un moteur sans transmission.
Trois objets, un contrat. Vous n’avez pas à donner un cours CSI.
Le PersistentVolume est un morceau de disque vu par le cluster — NFS, EBS, disque Azure, LVM. Le PersistentVolumeClaim est la demande du Pod : « 10 Gi, ReadWriteOnce ». Le StorageClass dit comment fabriquer un PV quand un PVC arrive et qu’aucun volume statique ne match : provisioner, type de disque, zone, politique de reclaim.
En pratique moderne, vous écrivez un PVC qui cite un StorageClass (gp3, premium-ssd, retain-on-delete). Un contrôleur CSI crée le volume chez le cloud, le lie, le kubelet le monte. Le Pod n’a pas à connaître l’ID du volume.
Les modes d’accès. ReadWriteOnce : un nœud à la fois — le cas cloud le plus courant, et la raison pour laquelle deux réplicas d’un Deployment ne partagent pas ce disque. ReadWriteMany : plusieurs nœuds, NFS / EFS / un CSI qui le promet. Poser un Deployment à 3 réplicas sur un RWO, c’est deux Pods Pending et un ticket.
Le reclaim. Delete détruit le volume avec le PVC — parfait pour du jetable, catastrophique pour une base si on se trompe de manifeste. Retain laisse le PV orphelin : les données survivent, quelqu’un doit les récupérer à la main.
Pending. Un PVC Pending bloque le Pod. Causes fréquentes : StorageClass inexistant, zone différente du nœud, quota, provisioner down. On describe le PVC avant de relancer le Deployment.
On n’entre pas dans les sidecars CSI. L’idée suffit : le Pod réclame, la classe provisionne, le mode d’accès décide si ça se partage.
Un Deployment veut que ça reste allumé. Un Job veut que ça finisse. Migration, batch, indexation, un script qui doit réussir une fois : restartPolicy: OnFailure ou Never, completions, backoffLimit. Quand le Pod sort 0, le Job est Complete. Quand il épuise ses essais, Failed.
Le CronJob crée des Jobs selon un calendrier (0 2 * * *). Même objet, déclenché. concurrencyPolicy (Allow, Forbid, Replace) dit ce qui se passe si le run de 2 h n’est pas fini à 3 h — c’est la question que l’intervieweur pose après « c’est crontab ». startingDeadlineSeconds évite de rattraper six heures de jobs après une panne du control plane.
ttlSecondsAfterFinished : sans lui, les Jobs et leurs Pods s’accumulent, et kubectl get pods devient illisible. Et un Job n’est pas le bon outil pour un serveur HTTP : si vous le mettez en restartPolicy: Always, vous avez réinventé un ReplicaSet plus pauvre.
Le piège du CronJob : le scheduler du CronJob peut rater un créneau si le apiserver est indisponible. On ne lui confie pas « exactement une fois, à la seconde près, sinon double écriture comptable » sans idempotence côté métier.
Le StatefulSet donne un nom stable, un ordre et un disque par Pod. On l’utilise quand l’identité doit survivre au redémarrage.
Lire la réponse détailléeUn DaemonSet garantit un Pod par nœud (ou par nœud qui match un sélecteur) — pas « N réplicas quelque part ». Quand un nœud rejoint le cluster, le Pod apparaît. Quand le nœud part, le Pod part.
C’est l’objet des agents de nœud : collecteur de logs, agent de métriques, plugin réseau, scan de sécu, node-exporter, parfois l’Ingress controller. Ces processus doivent voir le host — souvent avec hostNetwork, hostPath, ou un accès au socket Docker / containerd. Un Deployment à anti-affinité required sur le hostname approche le résultat et se trompe dès qu’un nœud est cordoned, tainted, ou vient d’être ajouté : le DaemonSet, lui, est réconcilié sur l’inventaire des nœuds.
Vous excluez les nœuds qui ne doivent pas porter l’agent : taint + toleration, ou nodeSelector / affinity (contrôle-plane, nœuds Windows, pools GPU). Un DaemonSet sans toleration n’ira pas sur un nœud qui porte un taint ; un DaemonSet trop tolérant s’installera sur le control plane. Les deux erreurs se voient en prod.
La mise à jour (RollingUpdate avec maxUnavailable) redémarre l’agent nœud par nœud. Pour un CNI, c’est une fenêtre délicate : on ne traite pas un DaemonSet de dataplane comme un Deployment d’API. L’intervieweur veut entendre que vous savez pourquoi l’objet existe, pas seulement « un Pod partout ».
Par défaut, tout Pod parle à tout Pod. La NetworkPolicy est le pare-feu dans le cluster : qui peut entrer, qui peut sortir, pour quels ports. Ce n’est pas un Service, ce n’est pas un namespace.
L’idée qui compte : tant qu’aucune policy ne sélectionne le Pod, tout est permis. Dès qu’une policy le sélectionne pour l’ingress, seul le trafic explicitement autorisé (par celle-ci et les autres policies additives) entre. Même logique pour l’egress, si une policy d’egress le concerne. On passe donc d’un monde ouvert à un monde deny by default pour ce Pod, sans l’avoir écrit comme une phrase magique : c’est le fait d’être sélectionné qui ferme.
Que vous savez que le CNI doit l’implémenter. Calico, Cilium, Antrea : oui. Un plugin qui ignore l’objet : les manifests sont de la documentation. kubectl apply vert ne prouve pas un paquet filtré.
Que vous parlez de sélecteurs — Pods, namespaces, (parfois) CIDR — pas d’IP de Pods dures. Et que DNS doit rester joignable si vous restreignez l’egress : oublier kube-dns / CoreDNS, c’est casser la résolution et croire que « le réseau est down ».
On ne vous demande pas de dessiner un mesh. On demande : ** isolation de namespace, deny par défaut une fois la première règle posée, et le dataplane qui exécute vraiment.**
RBAC répond à qui a le droit de parler à l’API, pas à qui parle sur le réseau. Le kubelet, kubectl, un controller, un Pod qui lit un Secret : tous passent par le apiserver. NetworkPolicy ne voit rien de tout ça.
Quatre objets, une phrase chacun :
get pods, create deployments) dans un namespace.Le sujet est un utilisateur, un groupe, ou — le cas quotidien — un ServiceAccount monté dans un Pod. L’appli n’est pas « dans Kubernetes » : elle est un client de l’API, avec l’identité qu’on lui a donnée.
Le verbe bind et le verbe escalate. Pouvoir créer un RoleBinding vers cluster-admin, c’est être admin. Un compte de CI qui apply -f tout un dossier, y compris des Bindings, est un compte d’administration, même s’il s’appelle deployer.
ClusterRole + RoleBinding : on réutilise une grappe de verbes (par exemple view) dans un seul namespace. Ce n’est pas un oubli, c’est le motif correct pour « lecture seule sur l’équipe A ».
Le défaut à tuer : le ServiceAccount default du namespace, avec un binding trop large « pour que ça marche ». Chaque controller a son SA, le minimum de verbes, et automountServiceAccountToken: false quand le Pod n’appelle pas l’API.
L’intervieweur relance sur l’audit : qui a listé les Secrets la nuit dernière. RBAC sans logs d’API est une serrure dont on n’a jamais regardé la gâche.
Deux plans, un contrat. Le control plane décide et enregistre. Le kubelet exécute sur une machine.
Sur le control plane, à nommer sans réciter une brochure :
Le kubelet tourne sur chaque nœud. Il prend les Pods que le scheduler lui a assignés, parle au runtime (containerd, CRI-O), monte les volumes, lance les sondes, reporte le statut. Il n’invente pas l’état désiré : il le réalise. kube-proxy (ou le dataplane du CNI) programme les règles qui font d’un ClusterIP un vrai chemin.
« Le apiserver tombe. Que se passe-t-il pour les Pods déjà Running ? »
Ils continuent. Le kubelet a déjà le spec, le dataplane a déjà les règles. Les sondes tournent. Ce qui s’arrête : tout changement — scale, nouveau Pod, nouveau Service, token à renouveler, rollout. Un cluster sans API n’est pas éteint ; il est figé. C’est une réponse SRE, pas une réponse catalogue.
« Le kubelet d’un nœud meurt. »
Le control plane finit par marquer les Pods NotReady / Unknown, le Service les retire, le nœud devient NotReady. Les conteneurs peuvent encore tourner un moment sur la machine — sans maître local pour les soigner. Le scheduler, lui, ne place plus rien là.
On ne vous demande pas de dessiner Raft. On demande que vous sachiez qui a autorité sur quoi, et ce qui survit à la panne de l’autre.
etcd est la source de vérité du cluster. Sans quorum, l’API refuse les écritures : le cluster ne s’éteint pas, il cesse d’être gouvernable.
Lire la réponse détailléeLes deux s’appliquent au namespace. Ils ne font pas le même métier, et les poser tous les deux est souvent le bon réflexe.
ResourceQuota est un plafond. Somme des requests CPU, somme des limits mémoire, nombre de Pods, de PVC, de Services, parfois de GPU. Quand le compteur est plein, l’admission refuse le prochain objet. C’est ce qui empêche l’équipe voisine — ou un HPA mal borné — de manger le cluster.
LimitRange est un gabarit. Min, max, et surtout valeurs par défaut si le manifeste n’a rien mis. Un LimitRange qui injecte cpu: 100m / memory: 128Mi évite les Pods BestEffort involontaires. Il peut aussi rejeter un conteneur qui demande 64 Gi dans un namespace d’outils.
Quota sans LimitRange : quelqu’un crée des Pods sans request, le Quota sur requests.cpu ne les voit pas comme vous le croyez (selon les ressources comptées), et le scheduler les empile. LimitRange sans Quota : chaque Pod est bien formé, rien n’empêche d’en lancer deux cents.
Le symptôme : un Forbidden à l’apply, ou un Pending exceeded quota. On describe le ResourceQuota, on ne relance pas le Deployment en boucle. Et l’on se souvient que le HPA compte dans le Quota : maxReplicas trop haut et plafond trop bas, les Pods supplémentaires n’existeront jamais.
Par le DNS du cluster, presque toujours CoreDNS, exposé par le Service kube-dns dans kube-system. Chaque Pod reçoit un resolv.conf : nameserver = ClusterIP de ce Service, search = ns.svc.cluster.local, svc.cluster.local, cluster.local.
D’où les noms que vous citez :
api suffit ;api.paiement ;api.paiement.svc.cluster.local.CoreDNS traduit ça vers le ClusterIP du Service — ou, pour un Service headless, vers les IP des Pods. Ce n’est pas magique : si le Service n’a pas d’Endpoints, la résolution peut réussir (l’IP virtuelle existe) et le trafic tomber dans le vide. DNS et dataplane sont deux étages.
ndotsndots:5 est le défaut. Une requête postgres.prod.interne (moins de cinq points) passe d’abord par les suffixes de search : CoreDNS reçoit une série de noms qui n’existent pas, puis enfin le nom absolu. Latence, charge sur CoreDNS, timeouts d’applis pressées. Le remède : un FQDN avec un point final, ou un dnsConfig raisonnable — pas « scaler CoreDNS » en premier.
kubectl -n kube-system get pods -l k8s-app=kube-dns, les logs, un nslookup api depuis un Pod éphémère. Si CoreDNS est Pending ou CrashLoop, tout le cluster « n’a plus le réseau ». Si seul un namespace casse, c’est souvent une NetworkPolicy d’egress qui a oublié le port 53 vers kube-dns.
Le Service comme nom stable, côté usage, est développé dans pourquoi l’IP d’un Pod ne suffit pas.
On commence par describe et les Events, puis les logs, y compris le conteneur précédent. exec confirme une hypothèse, il ne commence pas l’enquête.
Lire la réponse détailléeLe nœud a été choisi. Le runtime n’arrive pas à obtenir l’image. Ce n’est pas un CrashLoop : le process n’a pas démarré. describe montre Failed to pull image et la raison du registry ; on la lit avant de retagger au hasard.
Les causes, dans l’ordre où elles se présentent :
latest que personne n’a poussé, digest d’une autre région. docker pull / crane depuis votre laptop ne prouve rien : le nœud n’a pas votre Docker Desktop.imagePullSecrets, ou secret dans le mauvais namespace, ou SA qui ne le référence pas. Le nœud reçoit 401.imagePullPolicy: Always force le pull à chaque démarrage : utile pour un tag mutable, fatal si le registry est down. IfNotPresent + tag latest : le nœud garde une vieille latest. En entretien, on dit : tags immuables (1.2.0 ou un digest), Always seulement si l’on assume le coût.
On ne relance pas le Pod pour « débloquer » un BackOff. On corrige l’image, le secret ou le réseau ; le kubelet réessaiera tout seul. La chaîne image → Pod est dans Docker et Kubernetes : qui fait quoi.
Kubernetes réconcilie sans cesse l’état déclaré et l’état observé. Un SRE explique ce que le contrôleur tente, pas seulement la commande qu’il a lancée.
Lire la réponse détaillée