Diagnostiquer le réseau d'un cluster
Pourquoi
La leçon 12 du cours précédent donnait une méthode pour les pannes de charges de travail : pods, Deployments, sondes, Services. Le cours Le modèle TCP/IP donnait la méthode réseau d'une machine : couche par couche, en lisant ce que disent les messages. Un cluster superpose les deux : un paquet y traverse des espaces de noms réseau, un plugin CNI, des règles de traduction d'adresses, un DNS interne, des politiques, parfois un tunnel et une passerelle. Autant de couches où une panne peut se loger, et autant de façons de se tromper en cherchant au mauvais endroit.
Cette leçon propose un ordre de questions où chaque réponse élimine une couche entière, des outils d'observation, et six pannes types résolues sur Signalements, chacune présentée comme un runbook : ce qu'on constate, ce qu'on interroge, la cause, la correction.
Note
Les six scénarios sont des cas d'école construits à partir des mécanismes décrits dans les leçons 1 à 9 et dans la documentation des projets. Ils ne racontent pas des incidents survenus chez un client. Les sorties de commandes sont décrites, pas reproduites : elles dépendent de votre cluster.
Les concepts
Les six questions, dans l'ordre
Pour une connexion qui échoue entre un client et un serveur du cluster, on pose les questions suivantes, de l'amont vers l'aval. Chacune se vérifie par une commande précise.
- Le nom se résout-il ? (DNS, leçon 5) Si le client utilise un nom, l'échec peut venir de la résolution. On élimine cette couche en testant par adresse IP.
- Le Service a-t-il des points d'accès ? (Service et EndpointSlices, leçon 4) Si le Service n'a pas de pod prêt derrière, aucune couche réseau n'est en cause : c'est une panne de sélecteur ou de disponibilité, déjà vue au cours précédent.
- Une politique réseau bloque-t-elle ? (leçon 8) Sans réponse du tout, par délai d'attente, c'est le signe d'un paquet abandonné.
- Le CNI et les routes acheminent-ils le paquet ? (leçons 2 et 3) Pod à pod sur le même nœud, puis sur un autre nœud : on localise la frontière.
- La taille des paquets pose-t-elle problème ? (MTU) Les petits échanges passent, les gros se bloquent.
- Le suivi de connexion est-il saturé ? (conntrack) Des connexions échouent par intermittence sous charge.
Ce qui rend cet ordre utile : les symptômes se distinguent. Un refus immédiat (Connection refused) veut dire qu'une machine a répondu « personne n'écoute » : le chemin réseau fonctionne, regardez l'application et ses ports. Un délai d'attente veut dire qu'un paquet s'est perdu : politique, route, MTU ou pare-feu. Une erreur de résolution veut dire DNS. Une erreur de certificat veut dire TLS. Ne cherchez pas dans la couche réseau ce qu'un message vous place dans une autre.
Le pod de diagnostic
Les images d'applications bien construites (légères, sans shell, voir distroless) ne contiennent aucun outil réseau. On apporte les outils dans un pod de diagnostic. L'image nicolaka/netshoot, d'après son dépôt, regroupe dans une seule image dig, curl, tcpdump, iperf3, nmap, ss, mtr, tracepath... Trois façons de l'utiliser :
- Un pod à part, dans un namespace d'essai : il voit le cluster comme un client ordinaire, avec ses politiques réseau propres. Bon pour vérifier ce qu'un pod quelconque peut atteindre.
- Un conteneur éphémère dans le pod à examiner (
kubectl debug) : il partage l'espace de noms réseau du pod, donc voit ses interfaces, ses routes et son DNS exacts. Bon pour comprendre ce que voit ce pod. - Un pod de nœud (
kubectl debug node/…) : un pod privilégié qui s'exécute dans les espaces de noms réseau, de processus et d'IPC de l'hôte, avec le système de fichiers du nœud sous/host. Bon pour les captures de paquets et les tables de routage, au prix d'un accès très étendu.
$ kubectl run diag -n test-reseau --rm -it --restart=Never --image=nicolaka/netshoot -- sh
$ kubectl debug -it pod/<pod> -n signalements --image=nicolaka/netshoot --profile=restricted --target=api
$ kubectl debug node/<nœud> -it --image=nicolaka/netshoot --profile=sysadmin
L'option --profile règle le contexte de sécurité du conteneur : restricted respecte les règles d'un namespace restreint (mais ne donne pas tcpdump, qui exige la capacité NET_RAW), netadmin ajoute NET_ADMIN et NET_RAW, sysadmin donne un accès privilégié. Dans le namespace signalements, au profil de sécurité restricted, utilisez --profile=restricted ; pour une capture de paquets, passez par le nœud plutôt que d'affaiblir le namespace.
Les outils d'observation
- Hubble (avec Cilium) : observe les flux entre pods, avec le verdict de chaque décision de politique.
hubble observe --namespace signalements --verdict DROPPEDliste les paquets abandonnés ;--port 53filtre sur le DNS ;--followsuit en direct. C'est le seul moyen direct de voir qui a été refusé et par quelle politique. cilium connectivity test: déploie dans un namespace dédié des pods de test et une batterie de scénarios (pod à pod, Service, politiques, DNS, sortie). À lancer après une installation ou une mise à jour du CNI, sur un cluster d'essai : il crée des charges de travail et des politiques.tcpdumpsur le nœud : voir les paquets sur les interfaces réelles (la carte du nœud, les interfacesvethdes pods, un tunnel VXLAN ou Geneve). Il montre ce que le noyau a vu, pas ce que l'application croit avoir envoyé.nsenter: exécuter une commande dans l'espace de noms réseau d'un conteneur depuis le nœud, quand le conteneur n'a aucun outil.
En pratique
Une convention : les cinq premières étapes servent de trame, appliquée avant d'ouvrir un scénario. Elles décrivent les commandes, jamais une sortie inventée.
1. Poser les faits
Depuis un pod de diagnostic dans un namespace d'essai :
$ curl -sv -m 5 http://signalements.signalements.svc.cluster.local/sante
$ curl -sv -m 5 http://<IP-du-Service>/sante
$ curl -sv -m 5 http://<IP-d-un-pod>:8000/sante
Trois essais de plus en plus bas : par le nom du Service, par son adresse virtuelle, par l'adresse d'un pod. Le premier qui réussit borne la panne. Si le nom échoue et l'adresse du Service réussit : DNS. Si l'adresse du Service échoue et celle du pod réussit : Service, EndpointSlices ou kube-proxy. Si l'adresse du pod échoue : politique, CNI, MTU, ou l'application elle-même.
2. Le DNS
$ cat /etc/resolv.conf
$ dig signalements.signalements.svc.cluster.local +search +showsearch
$ dig @<IP-de-CoreDNS> api.scaleway.com +stats
Le premier fichier montre les domaines de recherche et l'option ndots. Le deuxième suit la résolution en indiquant les noms essayés. Le troisième interroge CoreDNS directement et affiche son temps de réponse (Query time).
3. Le Service et ses points d'accès
$ kubectl get service,endpointslices -n signalements -l app.kubernetes.io/name=signalements
Un Service dont l'EndpointSlice est vide ou ready: false n'a pas de problème réseau (cours précédent).
4. Les politiques
$ kubectl get networkpolicy -n signalements
$ hubble observe --namespace signalements --verdict DROPPED --last 50
Sans Hubble, on liste les politiques qui sélectionnent le pod et on relit leurs règles à la lumière de la leçon 8 : lequel des quatre points (egress de la source, ingress de la destination, DNS, namespace de la passerelle) manque ?
5. Le chemin, la MTU, le suivi de connexion
Sur le nœud (kubectl debug node/…) :
$ ip route
$ ip -d link show
$ conntrack -S
La table de routage montre les routes vers les plages de pods des autres nœuds. ip -d link donne la MTU de chaque interface. conntrack -S (si l'outil est présent dans l'image) affiche des compteurs, dont les insertions qui échouent (insert_failed) et les paquets abandonnés (drop). Un compteur qui augmente en même temps que la panne est un indice très fort.
Scénario 1 : le DNS lent (ndots)
Symptôme. Signalements appelle un service externe, api.scaleway.com par exemple, et ses requêtes durent de façon irrégulière plusieurs centaines de millisecondes de plus qu'attendu. L'application fonctionne ; elle est simplement lente.
Démarche. Dans un pod du namespace, cat /etc/resolv.conf montre trois domaines de recherche (signalements.svc.cluster.local, svc.cluster.local, cluster.local) et options ndots:5. La règle de ndots, expliquée à la leçon 5 : un nom qui contient moins de 5 points est d'abord essayé avec chacun des domaines de recherche avant d'être essayé tel quel. api.scaleway.com a deux points. Le résolveur essaie donc api.scaleway.com.signalements.svc.cluster.local, puis …svc.cluster.local, puis …cluster.local, qui reçoivent chacun une réponse « n'existe pas », avant la vraie requête, et cela pour les enregistrements A et AAAA. Avec dig +search +showsearch, on voit la suite des noms essayés. Une capture sur le nœud (tcpdump -ni any port 53) montre quatre à huit requêtes pour un seul appel.
Cause. Le comportement par défaut du DNS d'un pod, multiplié par le nombre d'appels sortants.
Correction, du plus localisé au plus général :
- écrire le nom entièrement qualifié, avec un point final :
api.scaleway.com.; - abaisser
ndotspour le pod, pardnsConfig:
spec:
dnsConfig:
options:
- name: ndots
value: "2"Avec ndots: 2, api.scaleway.com (deux points) est essayé tel quel d'abord ; mais un nom de Service interne à un seul point, du type signalements.signalements, restera résolu par recherche. Vérifiez donc les noms internes utilisés par l'application avant de baisser la valeur.
- réduire la charge sur CoreDNS avec un cache local par nœud (leçon 5, NodeLocal DNSCache).
Scénario 2 : la NetworkPolicy qui bloque le DNS
Symptôme. Après l'ajout d'un refus par défaut dans le namespace (leçon 8), l'API ne démarre plus correctement : /sante est en échec, les journaux parlent de résolution de nom impossible pour postgresql.
Démarche. On élimine d'abord le Service : kubectl get endpointslices montre la base prête. Depuis un conteneur éphémère dans un pod de l'API (profil restricted), nslookup postgresql échoue par délai d'attente, pas par NXDOMAIN : la requête n'a pas obtenu de réponse, ce qui exclut un nom inexistant. Un test par adresse IP vers la base (nc -zv -w 3 <IP> 5432) échoue lui aussi, car le refus par défaut n'ouvre ni l'un ni l'autre. Avec Hubble, hubble observe --namespace signalements --verdict DROPPED --port 53 montre les paquets UDP de l'API vers les pods de CoreDNS, abandonnés, dans le sens de la sortie.
Cause. Le refus par défaut s'applique aussi à l'egress : le DNS, sans règle explicite, est refusé comme le reste.
Correction. La politique autorise-dns de la section 3 de la leçon 8 : egress vers les pods k8s-app: kube-dns de kube-system, ports 53 en UDP et en TCP. On revérifie en retestant nslookup, puis la connexion à la base, qui exige encore la règle api-vers-postgresql.
Scénario 3 : la MTU et l'encapsulation
Symptôme. /sante répond, les petites requêtes aussi. Mais un export de signalements d'une centaine de kilo-octets reste bloqué : le client attend, puis expire. Le problème ne touche que les pods qui parlent à d'autres nœuds.
Démarche. Le DNS et le Service sont sains (les petites requêtes passent), et il n'y a pas de refus de politique (rien dans Hubble). Les symptômes « petit passe, gros bloque » sont ceux d'un problème de MTU, expliqué dans la leçon ICMP et MTU du cours TCP/IP : la poignée de main TCP et les petites réponses tiennent dans des paquets courts, les gros transferts remplissent les paquets jusqu'à la MTU du pod. Si le réseau entre nœuds a une MTU plus faible que celle du pod (parce que le tunnel d'encapsulation, VXLAN ou Geneve, ajoute des octets d'en-tête), ces paquets ne passent pas, et si les messages ICMP « fragmentation nécessaire » sont filtrés, l'émetteur ne l'apprend jamais.
On le démontre depuis le pod de diagnostic, par des paquets qui interdisent la fragmentation :
$ ping -M do -s 1472 <IP-d-un-pod-sur-un-autre-nœud>
$ ping -M do -s 1372 <IP-d-un-pod-sur-un-autre-nœud>
$ ip link show eth0
$ tracepath <IP-d-un-pod-sur-un-autre-nœud>
-M do interdit la fragmentation, -s fixe la taille de données (28 octets d'en-têtes s'y ajoutent). Si 1472 échoue et qu'une taille plus petite passe, la MTU effective du chemin est plus petite que celle de l'interface. tracepath la découvre et l'affiche. Sur le nœud, ip -d link show donne la MTU de la carte physique et celle des interfaces du CNI : la MTU du pod doit être inférieure à celle de la carte du nœud, de la taille de l'encapsulation (environ 50 octets pour VXLAN sur IPv4, davantage avec le chiffrement transparent).
Cause. Une MTU de pod mal ajustée au réseau sous-jacent, ou un filtrage de l'ICMP qui empêche la découverte de la MTU du chemin (RFC 1191).
Correction. Régler la MTU du CNI (avec Cilium, par la valeur mtu du chart Helm, ou en laissant l'agent la détecter) ; ou, en secours, fixer le MSS des connexions TCP pour qu'elles n'émettent pas de paquets trop gros. Autoriser les messages ICMP de type « fragmentation nécessaire » sur le réseau est aussi une correction à la racine.
Scénario 4 : externalTrafficPolicy Local et nœuds sans pod
Symptôme. Après une modification du Service de Traefik, une partie des connexions entrantes échoue ou expire, et le répartiteur de charge Scaleway signale certains nœuds en échec de vérification de santé. L'application ne change pas.
Démarche. Le champ spec.externalTrafficPolicy d'un Service LoadBalancer vaut Cluster (le défaut) ou Local. Avec Local, d'après la documentation de Kubernetes, le trafic n'est envoyé qu'aux pods du nœud qui reçoit la connexion, ce qui conserve l'adresse IP source du client mais fait échouer les connexions reçues par un nœud qui n'héberge pas de pod. Le répartiteur interroge pour cela un port de vérification de santé du Service (healthCheckNodePort), qui répond « sain » seulement sur les nœuds qui ont un pod local. On regarde :
$ kubectl get service traefik -n traefik -o jsonpath='{.spec.externalTrafficPolicy}{"\n"}'
$ kubectl get pods -n traefik -o wide
Si Traefik n'a qu'un ou deux pods, la première commande donne Local et la seconde montre sur quels nœuds ils sont : tous les autres nœuds sont, correctement, déclarés non sains. Les connexions envoyées à ces nœuds expirent tant que le répartiteur ne les a pas retirés.
Cause. Pas une panne : le comportement prévu de Local. Il devient un problème quand le nombre de pods est inférieur au nombre de nœuds, ou quand un pod est déplacé : entre l'arrêt de l'ancien et le démarrage du nouveau, aucun nœud n'est sain, ou les vérifications ne l'ont pas encore constaté.
Correction, selon le besoin. Si l'adresse IP du client n'importe pas, remettre Cluster. Si elle importe, garder Local mais exécuter Traefik avec assez de répliques réparties sur les nœuds qui reçoivent le trafic (ou en DaemonSet), avec des vérifications de santé du répartiteur qui retirent vite un nœud ; le protocole PROXY est une autre voie (cours Kapsule).
Scénario 5 : la Gateway non programmée faute de ReferenceGrant
Symptôme. L'équipe plateforme crée un listener HTTPS dans la Gateway entree du namespace passerelle, avec un certificat stocké dans un Secret du namespace signalements. Le site n'est pas servi en HTTPS : Traefik ne présente pas le certificat attendu, et kubectl get gateway n'affiche pas la Gateway comme programmée pour ce listener.
Démarche. Gateway API écrit la cause dans le statut, il suffit de la lire (leçon 6) :
$ kubectl get gateway entree -n passerelle -o jsonpath='{range .status.listeners[*]}{.name}{"\t"}{range .conditions[*]}{.type}={.status}({.reason}) {end}{"\n"}{end}'
Pour le listener HTTPS, on lit ResolvedRefs=False avec la raison RefNotPermitted, ce qui veut dire : la référence existe, mais rien ne l'autorise. Le listener n'est alors pas programmé, d'après la spécification (le détail des conditions varie un peu selon le contrôleur). On confirme en cherchant une ReferenceGrant dans signalements : kubectl get referencegrant -n signalements. Il n'y en a pas.
Cause. Une Gateway ne peut référencer un Secret d'un autre namespace que si le propriétaire du Secret l'a autorisé par une ReferenceGrant (leçon 6).
Correction.
apiVersion: gateway.networking.k8s.io/v1
kind: ReferenceGrant
metadata:
name: certificats-pour-la-passerelle
namespace: signalements # le namespace du Secret
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: passerelle
to:
- group: ""
kind: Secret
name: signalements-tlsLe name limite l'autorisation à ce Secret : à préférer à une autorisation de tous les Secrets du namespace, car la clé privée d'un site est la donnée la plus sensible qu'une passerelle manipule. Après création, la condition passe à True sans autre action. Mieux : laisser cert-manager créer le Certificate dans le namespace de la Gateway (leçon 7), et éviter la référence entre namespaces.
Scénario 6 : le certificat bloqué en défi HTTP-01
Symptôme. Le Certificate signalements-tls reste à READY: False depuis plusieurs minutes, sans erreur évidente.
Démarche. On descend la chaîne d'objets de cert-manager, de l'amont vers l'aval :
$ kubectl describe certificate signalements-tls -n passerelle
$ kubectl get order,challenge -n passerelle
$ kubectl describe challenge -n passerelle
$ kubectl get httproute -n passerelle
$ kubectl describe httproute -n passerelle
Le Certificate indique qu'il attend l'émission ; l'Order est pending ; le Challenge est pending, avec un message sur la présentation ou l'auto-vérification du défi. Trois causes classiques :
- la HTTPRoute temporaire du solveur n'est pas acceptée. Dans son statut,
Accepted=Falseavec la raisonNotAllowedByListeners: le listener HTTP ne l'accepte pas, parce que le namespace du Certificate (icipasserelle) n'a pas l'étiquette queallowedRoutesexige ; - la route est acceptée, mais le nom de domaine ne résout pas vers l'adresse de la Gateway : l'auto-vérification de cert-manager (une requête vers l'URL du jeton) échoue, et le Challenge le dit.
dig signalements.apps.example.comdepuis un pod, puiscurl -v http://signalements.apps.example.com/.well-known/acme-challenge/testdepuis l'extérieur, le montrent ; - le port 80 n'est pas joignable (groupe de sécurité, pare-feu) ou une redirection globale vers HTTPS intercepte la requête avant la route du défi.
Cause. Ici, la première : le listener apps n'accepte que les namespaces portant acces-passerelle: apps, et le namespace passerelle ne l'a pas.
Correction.
$ kubectl label namespace passerelle acces-passerelle=apps
La route temporaire passe à Accepted=True, le Challenge à valid, et le Certificate à READY: True. Ne supprimez pas le Certificate pour « repartir de zéro » : chaque nouvelle émission consomme les limites de débit de Let's Encrypt, et le travail doit se faire avec l'émetteur de staging.
Sous le capot
Pourquoi un ordre. Les symptômes se ressemblent : une politique et une route absente donnent le même délai d'attente, une MTU trop grande ressemble à un DNS lent. L'ordre part de ce qui se teste sans ambiguïté et à coût nul (le nom contre l'adresse, les EndpointSlices) vers ce qui demande des captures, et chaque étape ne conclut que par un fait, jamais par une absence de fait.
Où regarder un paquet. Une capture au pod montre ce que l'application envoie ; une capture à la carte du nœud montre ce qui sort, encapsulé. Si le paquet apparaît sur la veth du pod mais pas sur la carte, il est abandonné dans le nœud (politique, routage) ; s'il apparaît sur la carte et pas sur l'autre nœud, il l'est sur le réseau.
Pourquoi nsenter. Les pods ne sont pas des machines virtuelles : leurs espaces de noms sont des objets du noyau, que n'importe quel processus du nœud peut rejoindre s'il en a le droit. Depuis un pod de nœud (qui partage les PID de l'hôte), on trouve le processus du conteneur et on entre dans son seul espace de noms réseau :
$ ps aux | grep gunicorn
$ nsenter -t <PID> -n ip addr
$ nsenter -t <PID> -n tcpdump -ni eth0 port 8000
-t désigne le processus dont on emprunte l'espace, -n le réseau. On garde les outils du nœud (netshoot) mais on voit le réseau du conteneur : c'est le moyen de capturer dans un pod sans aucun outil dedans, et sans le modifier.
Le conntrack. Chaque connexion suivie occupe une entrée dans la table du noyau (leçon 3). Quand la table est pleine, le noyau abandonne les nouveaux paquets et l'écrit dans son journal (nf_conntrack: table full, dropping packet). La taille se règle par net.netfilter.nf_conntrack_max ; un fort trafic de connexions courtes (appels HTTP sans connexion persistante) la remplit. Avec Cilium en remplacement de kube-proxy, le suivi de connexion se fait en partie dans eBPF, avec ses propres tables (leçon 4).
Pièges courants
Diagnostiquer depuis le mauvais endroit. Un test par kubectl port-forward traverse l'API de Kubernetes et ne dit rien du réseau du cluster ; un test depuis un autre namespace subit d'autres politiques. Dites toujours d'où vous testez, et changez une seule chose à la fois.
Conclure d'une absence. « Rien dans Hubble » ne prouve pas l'absence de refus si Hubble ne voit pas ce nœud ; « ping échoue » ne prouve rien si l'ICMP est filtré (le test TCP, nc -zv, est plus parlant).
Capturer sans filtre. tcpdump sans filtre sur la carte d'un nœud charge le nœud et capture les données d'autres clients : filtrez par hôte et port, limitez la durée et le nombre de paquets (-c).
Sécurité
- Les outils de diagnostic sont des accès.
kubectl debugsur un nœud donne un shell privilégié avec le système de fichiers de l'hôte, donc les clés et les Secrets de tous les pods du nœud. Le droit (pods/ephemeralcontainers, création de pods privilégiés) se réserve à un petit groupe, se journalise, et s'accorde pour une durée limitée. - Les captures contiennent des données. Un
tcpdumpde trafic HTTP en clair contient des jetons, des cookies et des données de clients. Stockez les fichiers.pcapcomme des données sensibles, effacez-les après usage, et ne les joignez pas à un ticket. - Une image de diagnostic publique est du code non maîtrisé. Épinglez
nicolaka/netshootpar digest, ou mieux, construisez votre propre image d'outils, analysée et hébergée dans votre registre. Elle tourne avec des droits élevés. cilium connectivity testcrée des politiques et des pods dans le cluster : lancez-le dans un environnement où cela est accepté.
En production
- Un runbook par scénario. Les six scénarios de cette leçon sont des modèles : symptôme, commandes, cause, correction, et le lien vers la leçon qui explique le mécanisme. Chez Lyneko, ils vivent avec les manifestes de la plateforme.
- Avoir Hubble avant d'en avoir besoin. L'observabilité des flux se met en place avant la panne : les flux refusés d'il y a une heure ne se retrouvent pas après coup sans elle. Avec Cilium sur Kapsule, activer Hubble et ses métriques coûte peu.
- Alerter sur les symptômes réseau : erreurs 5xx à la passerelle, latence de CoreDNS, nœuds non sains du répartiteur, certificats proches de l'expiration, Gateways et routes dont une condition passe à
False. - Après chaque incident, ajouter à l'intégration continue le test qui l'aurait révélé : une politique trop stricte se détecte par un test de non-régression, une route refusée par une vérification du statut après le déploiement.
Exercices
Exercice 1 : lire le symptôme
Pour chaque message, dites quelle couche vous interrogez en premier : (a) curl: (6) Could not resolve host: signalements.signalements.svc ; (b) curl: (7) Failed to connect ... Connection refused ; (c) curl: (28) Connection timed out after 5001 milliseconds ; (d) curl: (60) SSL certificate problem: unable to get local issuer certificate.
Solution
(a) Le DNS : la résolution échoue, avant toute connexion. Vérifiez le nom (signalements.signalements.svc est un nom sans le suffixe de cluster complet, qui peut résoudre ou non selon search), CoreDNS et les politiques d'egress. (b) Pas le réseau : une machine a répondu « personne n'écoute » ; regardez le port, le targetPort, et si le processus écoute (adresse 127.0.0.1 au lieu de 0.0.0.0 par exemple). (c) Un paquet perdu : politique réseau, route, MTU ou pare-feu ; on commence par Hubble et les politiques. (d) TLS : la chaîne de confiance, c'est-à-dire un certificat de staging, un intermédiaire absent ou une autorité inconnue du client ; la leçon 7.
Exercice 2 : qui est coupable ?
Depuis un pod d'essai : curl vers le nom du Service échoue par délai d'attente ; curl vers l'adresse du Service échoue aussi ; curl vers l'adresse d'un pod de l'API réussit. Quelle couche est en cause, et quelles deux vérifications faites-vous ?
Solution
Entre le pod d'essai et le pod de l'API, le réseau fonctionne (la politique et le CNI laissent passer le trafic direct vers le pod), mais l'adresse du Service ne marche pas : c'est la traduction d'adresse du Service, donc kube-proxy (ou son remplaçant), ou les EndpointSlices. Vérifications : kubectl get endpointslices -n signalements (le Service a-t-il des points d'accès prêts ?) puis l'état de kube-proxy ou de l'agent Cilium sur le nœud du pod d'essai (kubectl get pods -n kube-system -o wide, les journaux, et les règles installées sur ce nœud). Le DNS est hors de cause : le délai d'attente par nom provient de la même cause que par adresse, puisque le nom se résout vers l'adresse du Service.
Exercice 3 : écrire le runbook
Rédigez, en cinq lignes, le runbook du scénario « les petites requêtes passent, les gros transferts expirent entre pods de nœuds différents ».
Solution
Symptôme : /sante répond, un export volumineux expire, uniquement entre nœuds. Hypothèse : MTU. Commandes : depuis un pod de diagnostic, ping -M do -s 1472 <IP d'un pod d'un autre nœud> puis des tailles décroissantes ; tracepath ; sur le nœud, ip -d link show pour comparer la MTU de la carte et celle des interfaces du CNI. Cause probable : MTU des pods supérieure à la MTU du chemin moins l'encapsulation (VXLAN : 50 octets), ICMP « fragmentation nécessaire » filtré. Correction : régler la MTU du CNI, autoriser l'ICMP de type 3 code 4 (IPv4) ou « Packet Too Big » (IPv6) entre nœuds, plafonner le MSS en dernier recours ; vérifier ensuite avec le même ping.
Récapitulatif
- Une panne réseau se localise par six questions dans l'ordre : DNS, Service et EndpointSlices, politique, CNI et routes, MTU, conntrack. Chaque réponse élimine une couche.
- Le symptôme oriente : refus (l'application ou le port), délai d'attente (paquet perdu), erreur de résolution (DNS), erreur de certificat (TLS).
- Trois essais bornent la panne : par le nom, par l'adresse du Service, par l'adresse du pod.
- Trois façons d'embarquer des outils : pod à part, conteneur éphémère (profil adapté au namespace), pod de nœud (profil
sysadmin) ;nsenterpour entrer dans le réseau d'un conteneur depuis le nœud. - Hubble donne le verdict des politiques ;
tcpdumpsur lavethet sur la carte du nœud localise l'endroit où un paquet disparaît. - Six scénarios :
ndots(noms qualifiés oudnsConfig), DNS bloqué par un refus par défaut, MTU et encapsulation,externalTrafficPolicy: Local, ReferenceGrant absente, défi HTTP-01 refusé parallowedRoutes. - Les outils de diagnostic sont des accès privilégiés et leurs captures des données sensibles.
Pour aller plus loin
- Le cours Le modèle TCP/IP, pour la méthode réseau sur une machine, et Kubernetes : les fondamentaux pour les pannes de charges de travail.
- Les pages Debug Services et Debugging DNS Resolution de la documentation de Kubernetes.
- La documentation de Hubble et de
cilium connectivity test, et la page de dépannage ACME de cert-manager. - Les leçons Le chemin d'un paquet et kube-proxy et ses alternatives, pour savoir où regarder un paquet.
- Le cours Kapsule : Kubernetes managé chez Scaleway, pour les vérifications de santé du répartiteur et la MTU du réseau privé.
- Le cours Sécurité de Kubernetes, pour le RBAC sur les sous-ressources de débogage.
Sources
- Kubernetes, Debug Services
- Kubernetes, Debugging DNS Resolution
- Kubernetes, Debug Running Pods (kubectl debug, profils, nœuds)
- Kubernetes, Create an External Load Balancer (externalTrafficPolicy)
- Cilium, Hubble et cilium connectivity test
- cert-manager, Troubleshooting ACME
- nicolaka/netshoot, README
- RFC 1191, Path MTU Discovery