Troubleshooting¶
Cette page est rangée par symptôme observable, jamais par composant. Un opérateur qui
débogue ne sait pas que sa panne vient de CoreDNS : il sait qu'il voit un 401, une page qui ne
charge pas, un pod qui reste 0/1. L'entrée doit se trouver à partir de ce qu'il a sous les
yeux.
Chaque entrée porte cinq blocs, dans cet ordre :
| Bloc | Ce qu'il donne |
|---|---|
| Symptôme | ce que l'on voit, et depuis où on le voit |
| La fausse piste | l'attribution erronée que ce symptôme provoque, et ce qui permet de l'écarter |
| Cause | le mécanisme réel |
| Correctif | les commandes, dans l'ordre |
| Réparé quand | l'effet qu'il faut constater pour conclure. Pas « la commande est passée » |
Les entrées ci-dessous décrivent des pannes qui se sont réellement produites sur l'instance de référence. La fausse piste n'est pas une précaution rhétorique : c'est le chemin qu'a effectivement pris le diagnostic la première fois, et celui que reprendra un opérateur qui voit la même chose.
Trouver son entrée¶
| Ce que vous voyez | Entrée |
|---|---|
| Aucun host ne répond, le navigateur refuse tout contournement | 1 |
| Tout marchait, puis plus rien depuis l'intérieur du cluster, après un redémarrage | 2 |
| Les APIs .NET répondent 401, les portails fonctionnent | 3 |
IDX10205 dans les journaux, 401 après une connexion réussie |
4 |
Un ExternalSecret en SecretSyncedError, ou un pod qui ne trouve pas son Secret |
5 |
| Les images des articles ne s'affichent pas, et pas sur tous les postes | 6 |
| La section des taux de change est vide, et le redevient après chaque redémarrage | 7 |
| Les portails appellent encore l'ancien domaine après un renommage | 8 |
| Les images du hub mettent des dizaines de secondes à s'afficher | 9 |
| Le nombre de pods explose, l'ordonnancement se bloque | 10 |
| Un host renvoie 404 par intermittence alors que le pod répond | 11 |
kubectl devient injoignable pendant que les applications continuent de servir |
12 |
kubectl top ne rend rien, les HPA restent en <unknown> |
13 |
Avant de dérouler une entrée, poser la question qui trie la moitié d'entre elles : le symptôme se voit-il depuis un poste, depuis l'intérieur du cluster, depuis internet, ou depuis les trois ? Un défaut visible depuis un poste mais pas depuis un pod, ou l'inverse, n'a pas la même famille de causes.
1. Aucun host ne répond, et le navigateur n'offre aucun contournement¶
Symptôme. Tous les hosts, publics comme internes, sont injoignables en HTTPS. Le navigateur
affiche une erreur de sécurité du type Did Not Connect: Potential Security Issue - this website
requires a secure connection, sans bouton pour passer outre. Le comportement est identique
sur tous les postes et dans tous les navigateurs. En ligne de commande, curl sort en erreur
TLS ou rend le code 000.
La fausse piste. Chercher du côté du DNS ou de Traefik. Le nom résout, le port 443 accepte
la connexion, Traefik sert : il sert simplement son certificat auto-signé interne. Puis tenter
de « continuer malgré tout » dans le navigateur, ce qui n'existe pas si ${TV_DOMAIN} est sur
la liste HSTS preload embarquée dans les navigateurs, ce qui est le cas de tout domaine en
.app. La panne n'est donc pas une dégradation avec contournement, c'est une coupure totale.
Cause. Le certificat wildcard n'est pas Ready. Le TLSStore default de Traefik n'a rien
à présenter et retombe sur le certificat auto-signé. En amont, deux causes couvrent la quasi
totalité des cas :
- le Secret
cloudflare-dns-tokenest absent du namespacecert-manager, ou le jeton n'a pas les portéesZone:DNS:EditetZone:Zone:Readsur la zone du client ; - les deux arguments DNS-01 manquent sur le Deployment cert-manager. Ils ne sont pas dans les
manifests amont : un
kubectl applydu Deployment amont les efface en silence. Sans eux, cert-manager vérifie la propagation du challenge en interrogeant CoreDNS, qui renvoie tout*.${TV_DOMAIN}vers Traefik. Le message dans le challenge est alorsCould not determine the zone for "_acme-challenge.${TV_DOMAIN}.": ... SERVFAIL.
Correctif.
kubectl get certificate -A
kubectl describe certificate wildcard-tradeval-app -n traefik-ingress
kubectl get secret cloudflare-dns-token -n cert-manager
kubectl get deploy cert-manager -n cert-manager \
-o jsonpath='{.spec.template.spec.containers[0].args}' | tr ',' '\n'
La sortie de la dernière commande doit porter --dns01-recursive-nameservers-only et
--dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53. Si elles manquent, les remettre par le
même patch qu'à l'étape 3 du Pas-à-pas :
kubectl patch deploy cert-manager -n cert-manager --type=json -p='[
{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--dns01-recursive-nameservers-only"},
{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--dns01-recursive-nameservers=1.1.1.1:53,8.8.8.8:53"}
]'
Ce patch ajoute les deux arguments à la fin de la liste : le relancer sur un Deployment qui les porte déjà les inscrit deux fois. Relire la liste d'arguments avant de patcher, avec la commande ci-dessus. Puis forcer une nouvelle tentative, sans attendre le report exponentiel de cert-manager :
kubectl -n traefik-ingress delete certificaterequest \
-l cert-manager.io/certificate-name=wildcard-tradeval-app
Ces noms d'objets ne portent pas le domaine du client
wildcard-tradeval-app et son Secret wildcard-tradeval-app-tls sont des noms de
ressources Kubernetes, pas des noms dérivés de ${TV_DOMAIN} : le paramétrage de la fiche
client ne les réécrit pas. Sur une instance dont le domaine est tout autre, le certificat
porte quand même ces noms-là. Ne pas chercher un objet nommé d'après le domaine du client,
il n'existe pas.
Réparé quand. Trois constats, dans cet ordre :
kubectl get certificate wildcard-tradeval-app -n traefik-ingress
kubectl get secret wildcard-tradeval-app-tls -n traefik-ingress
openssl s_client -connect ${TV_VIP}:443 -servername hub.${TV_DOMAIN} </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -enddate -ext subjectAltName
READY=True, le Secret existe, et surtout la troisième commande rend un émetteur Let's Encrypt,
une date d'expiration à plus de trente jours et un SAN *.${TV_DOMAIN}. Tant qu'elle rend
l'émetteur par défaut de Traefik, rien n'est réparé, quoi que dise l'objet Certificate.
Le navigateur qui a échoué une fois garde la panne en mémoire
Un navigateur qui a rencontré l'erreur a mémorisé le HSTS pour ce host. Il continuera de refuser la connexion après la réparation. Faire « Oublier ce site » sous Firefox, ou tester depuis une fenêtre privée, ou depuis un navigateur qui n'a jamais ouvert l'URL. Sans cela, on conclut à tort que le correctif n'a rien donné, et on repart chercher ailleurs.
2. La résolution interne de *.${TV_DOMAIN} a disparu après un redémarrage¶
Symptôme. Tout fonctionnait. Après un redémarrage de control-plane, une mise à niveau Talos
ou un talosctl apply-config, les services .NET repartent en 401, tandis que les portails
continuent de servir des pages. Depuis un pod, getent hosts auth.${TV_DOMAIN} ne rend rien, ou
rend une adresse publique au lieu de ${TV_TRAEFIK_CLUSTERIP}.
La fausse piste. Croire à un problème de certificat ou de Keycloak. Rien ne dit « DNS » : le service .NET journalise un échec d'authentification, pas un échec de résolution. Et la plateforme reste parfaitement joignable depuis un poste pendant toute la panne, puisqu'un poste interroge le résolveur du LAN, pas CoreDNS. La panne semble donc ne toucher que certains services, et seulement de l'intérieur.
Cause. Le bloc rewrite stop ... answer auto, qui renvoie *.${TV_DOMAIN} vers le Service
ingress, a disparu du Corefile. Jusqu'au 2026-09-10 la cause habituelle était Talos, qui
possédait la ConfigMap par son annotation config.k8s.io/owning-inventory et la réécrivait.
Ce n'est plus le cas : Talos ne pose plus CoreDNS. Chercher donc du côté d'un kubectl apply
malheureux ou d'une régression de core/coredns/coredns-stack.yaml.
Correctif.
kubectl get configmap coredns -n kube-system -o yaml | grep -A4 'rewrite stop'
kubectl apply -f core/coredns/coredns-configmap.yaml
kubectl rollout restart deployment/coredns -n kube-system
Réparé quand. Depuis un pod glibc, et pas depuis busybox, dont l'applet getent
n'existe pas :
kubectl run dns-check --rm -i --restart=Never --image=debian:stable-slim \
-- getent hosts auth.${TV_DOMAIN}
kubectl run dns-check --rm -i --restart=Never --image=debian:stable-slim \
-- getent hosts kubernetes.default.svc.cluster.local
La première doit rendre une adresse A égale à ${TV_TRAEFIK_CLUSTERIP}. La seconde est le
contrôle de non-régression : un Corefile mal formé casse toute la résolution du cluster, et ce
second appel est le seul qui le montre. Les pods reprennent d'eux-mêmes dès que la résolution
répond ; si des 401 persistent au-delà de quelques minutes sur un service .NET, le redémarrer,
son client OIDC gardant en cache le document de découverte.
Cette panne ne revient plus après une opération Talos, depuis le 2026-09-10
Elle se rejouait à chaque reboot de control-plane, montée de version ou
talosctl apply-config, parce que Talos possédait la ConfigMap. Ce n'est plus le cas :
cluster.coreDNS.disabled: true est dans talos/controlplane.yaml et dans la
configuration des trois nœuds, et core/coredns/coredns-stack.yaml porte les six objets.
Réappliquer la configuration machine du dépôt est donc sans danger. Contrôle qui le prouve :
talosctl get manifests ne rend plus aucune entrée coredns.
La réparation ci-dessus reste valable pour les autres causes, un kubectl apply malheureux
ou une régression du fichier versionné, à ceci près qu'elle se joue désormais sur
coredns-stack.yaml. Deux règles Prometheus du groupe dns signalent la disparition du
bloc rewrite en cinq minutes.
3. Les services .NET répondent 401 sur tous les appels, les portails fonctionnent¶
Symptôme. Tous les appels authentifiés vers les APIs .NET échouent en 401. Les portails
Next.js fonctionnent, les utilisateurs se connectent sur Keycloak sans problème. Keycloak est
sain : 1/1, aucun redémarrage.
La fausse piste. Soupçonner Keycloak, ou la configuration OIDC des APIs. Deux choses
alimentent l'erreur. D'abord, /api/auth/profile renvoie 401 sans jeton, ce qui est le
comportement normal : le symptôme ressemble à un défaut de configuration d'API. Ensuite, chaque
endpoint testé isolément répond 200. Vérifier chacun séparément ne prouve rien ici, et fait
perdre du temps.
Cause. CoreDNS rend une chaîne CNAME au lieu d'un enregistrement A. La glibc, bibliothèque C des conteneurs .NET, refuse de la suivre : l'appel TLS et OIDC ne part jamais. La musl, utilisée par les images Alpine et Node, est moins stricte et résout quand même. D'où une panne qui semble ne toucher que certains services, et c'est le fait que les portails Node fonctionnent qui la désigne.
Un second mécanisme a existé et produisait le même 401 : la confiance dans un CA interne,
distribué aux charges par trust-manager sous forme de ConfigMaps montées via SSL_CERT_FILE.
Il a été retiré le 2026-09-10 avec le CA lui-même, *.${TV_DOMAIN} étant signé par Let's
Encrypt, présent dans le magasin par défaut de toutes les images. Un cluster installé depuis
ce dépôt n'a ni CA interne, ni trust-manager, ni ces ConfigMaps ; sur un cluster plus ancien,
kubectl get bundles qui répond encore quelque chose signale un reste à retirer, pas une
cause à traiter.
Correctif. Vérifier la résolution depuis un pod .NET, pas depuis le poste :
Si getent ne rend pas ${TV_TRAEFIK_CLUSTERIP}, c'est le DNS : traiter
l'entrée 2.
Réparé quand. Les journaux du service .NET ne servent à rien ici : ils journalisent un échec d'authentification, jamais sa cause. Les deux constats utiles sont :
getent hosts auth.${TV_DOMAIN}depuis un pod .NET rend${TV_TRAEFIK_CLUSTERIP};- un parcours réel : une connexion suivie d'un appel d'API depuis un portail rend autre chose qu'un 401.
Un 401 qui persiste alors que ces deux points sont vrais n'est plus ce défaut. Passer à l'entrée 4.
4. Tous les appels authentifiés échouent avec IDX10205¶
Symptôme. IDX10205 dans les journaux des services .NET, et 401 sur tous les appels d'API
juste après une connexion réussie. L'utilisateur se connecte, obtient un jeton, et ce jeton est
refusé partout.
La fausse piste. Croire à un Keycloak en panne. Il est parfaitement sain : 1/1, aucun
redémarrage, et la seule ligne d'erreur dans ses journaux est une déconnexion client
(ClosedChannelException) sans rapport. Le document de découverte répond 200 quand on l'appelle
depuis le poste, et il répond 200 quand on l'appelle depuis le cluster. Les deux réponses ne
sont simplement pas d'accord entre elles, et aucun test qui les prend séparément ne peut le
montrer.
Cause. KC_HOSTNAME porte la forme courte (auth.${TV_DOMAIN}) au lieu de la forme URL
complète. Keycloak accepte les deux, et elles ne se comportent pas pareil :
Forme de KC_HOSTNAME |
Comportement |
|---|---|
auth.${TV_DOMAIN} |
le nom d'hôte est figé, le schéma est déduit de chaque requête |
https://auth.${TV_DOMAIN} |
schéma, hôte et port figés, issuer invariant |
Avec KC_PROXY_HEADERS=xforwarded, la déduction s'appuie sur X-Forwarded-Proto. Traefik
l'ajoute, un appel direct au Service keycloak ne l'a pas. Le même serveur annonce donc
https://... sur le chemin public et http://... sur le chemin interne. Les jetons remis aux
navigateurs portent iss en https ; les APIs, qui valident contre le document interne,
attendent http ; le iss ne correspond pas et tout est rejeté.
Seconde cause, et c'est celle qu'un opérateur rencontrera réellement : après un renommage de
domaine, les clés Vault qui portent l'issuer (AUTH_KEYCLOAK_ISSUER, NEXTAUTH_URL,
NEXTAUTH_URL_INTERNAL, l'authority OIDC des services .NET) n'ont pas été modifiées en même
temps que KC_HOSTNAME. Ces clés changent ensemble ou pas du tout.
La forme courte n'est pas atteignable sur une installation neuve
apps/keycloak/keycloak.yaml fixe déjà KC_HOSTNAME à la forme URL
complète, et render-config.sh n'y substitue que le domaine. La première cause ci-dessus
est donc l'histoire du défaut, pas son chemin d'arrivée le plus probable : elle ne
redeviendra atteignable que si quelqu'un raccourcit cette valeur à la main. Sur une
installation conforme, vérifier d'abord les clés Vault de la seconde cause. La mécanique
de la première reste à connaître, parce que c'est elle qui explique pourquoi comparer les
deux chemins est le seul test qui conclut.
Correctif. Le test qui tranche compare les deux chemins d'accès au même endpoint :
curl -sS https://auth.${TV_DOMAIN}/realms/${TV_KC_REALM}/.well-known/openid-configuration \
| grep -o '"issuer":"[^"]*"'
kubectl -n auth port-forward svc/keycloak 8080:80 &
curl -sS http://127.0.0.1:8080/realms/${TV_KC_REALM}/.well-known/openid-configuration \
| grep -o '"issuer":"[^"]*"'
Le port-forward n'ajoute aucun en-tête X-Forwarded-Proto : c'est exactement le chemin que
prennent les APIs. Si les deux sorties diffèrent, corriger KC_HOSTNAME en forme URL complète
dans apps/keycloak/keycloak.yaml, réappliquer, puis redémarrer les services
.NET :
Ce redémarrage n'est pas optionnel : le client OIDC de .NET met en cache le document de découverte, avec un rafraîchissement par défaut de l'ordre de douze à vingt-quatre heures. Sans lui, les APIs continuent de valider contre l'ancien issuer longtemps après le correctif, et le symptôme paraît intact.
Réparé quand. Les deux commandes ci-dessus rendent exactement la même chaîne,
https://auth.${TV_DOMAIN}/realms/${TV_KC_REALM}, et un parcours réel de connexion suivi d'un
appel d'API rend autre chose qu'un 401.
La recette de conformité ne détecte pas ce défaut
Le contrôle 6 de Validation compare l'issuer annoncé sur le chemin public à celui attendu. Sur ce défaut précis, le chemin public annonce justement la bonne valeur : la recette passe alors que les APIs, qui interrogent le chemin interne, reçoivent l'autre. La comparaison des deux chemins est le seul test qui conclut.
5. Un ExternalSecret reste en SecretSyncedError¶
Symptôme. kubectl get externalsecret -A montre un ou plusieurs objets autrement qu'en
SecretSynced. En aval, les pods qui consomment le Secret concerné restent en
CreateContainerConfigError s'il n'existe pas du tout, ou démarrent normalement avec des
valeurs périmées s'il existe encore.
La fausse piste. Deux, également coûteuses. La première est de conclure qu'External Secrets
Operator est cassé, alors qu'un seul chemin Vault l'est : le nombre d'objets en erreur est le
diagnostic, lire la liste avant de toucher quoi que ce soit. La seconde est de corriger le
Secret Kubernetes à la main, par kubectl edit ou kubectl create secret : creationPolicy:
Owner le réécrit à la synchronisation suivante, une minute plus tard, sans rien signaler. Le
seul point d'écriture est Vault.
Cause. Elle se lit dans la forme de la sortie :
Ce que montre kubectl get externalsecret -A |
Cause | Où confirmer |
|---|---|---|
| Tous en erreur | le ClusterSecretStore n'est pas prêt, ou Vault s'est rescellé après un redémarrage |
kubectl get clustersecretstore vault-store doit valoir READY=True ; kubectl exec -n vault vault-0 -- vault status doit rendre Sealed: false |
| Un ou deux objets nommés | le chemin Vault est absent ou mal orthographié | kubectl describe externalsecret <nom> -n <ns> nomme le chemin ; vault kv get -mount=secrets prod/<service> dit s'il existe |
| Aucun objet retourné du tout | ESO n'est pas installé, ses CRD manquent | |
Tous SecretSynced, mais un service se comporte mal |
un data: énuméré au lieu de dataFrom: extract : l'oubli d'une clé est silencieux, le pod démarre et n'échoue qu'à l'usage |
comparer les clés du Secret produit à celles du chemin Vault |
Correctif. Selon la cause : desceller Vault (page Exploitation), créer
le chemin Vault manquant avec vault kv patch, ou réécrire l'ExternalSecret sous la forme
obligatoire (dataFrom: - extract:, creationPolicy: Owner, deletionPolicy: Retain). Dans
tous les cas, terminer par un redémarrage des Deployments concernés : les variables
d'environnement sont figées au démarrage du conteneur, et un Secret corrigé n'atteint pas un
pod déjà en cours.
vault kv put sur un chemin partagé casse un service sans erreur
put remplace l'intégralité du contenu du chemin : toute clé absente de la commande
disparaît, donc disparaît du Secret à la synchronisation suivante. L'ExternalSecret
reste SecretSynced, puisque le chemin existe toujours : il a seulement maigri.
deletionPolicy: Retain ne protège pas de ce cas. Les pods en cours continuent de tourner
sur leurs variables figées, et la panne apparaît au redémarrage suivant, découplée de la
commande qui l'a causée. Sur tout chemin prod/<service>, c'est vault kv patch.
Réparé quand.
kubectl get externalsecret -A
kubectl get secret <nom> -n <ns> -o jsonpath='{.data}' | tr ',' '\n' | cut -d'"' -f2
kubectl get pods -n <zone> -l app=<nom>
Tous les objets en SecretSynced, et le Secret cible porte bien tous les noms de clés
attendus, et le pod qui le consomme est 1/1 après redémarrage. Un SecretSynced seul ne
prouve pas que le Secret porte les bonnes clés : c'est le décompte des noms de clés qui le
prouve. La commande ci-dessus n'affiche que des noms, jamais des valeurs : ne jamais la
remplacer par un -o yaml.
6. Les images des articles ne s'affichent pas, 403 selon le poste¶
Symptôme. Les images des actualités s'affichent sur un poste et pas sur un autre. La page
elle-même est servie normalement. Dans les journaux du content-service :
You are not allowed to perform this action., plusieurs dizaines de fois par jour.
La fausse piste. Partir sur le poste, parce que la différence entre les postes est réelle et
saute aux yeux. Elle est une conséquence, pas la cause. Le réflexe qui remet dans le bon sens :
un symptôme identique sur hub et sur portal désigne la source, c'est-à-dire les données ou
le content-service ; un symptôme différent selon le poste ou le navigateur désigne la
résolution de *.${TV_DOMAIN} sur le poste. Ici les deux se combinent, et c'est la source qu'il
faut corriger.
Cause. SERVER_URL (ou PAYLOAD_PUBLIC_SERVER_URL) est définie sur le content-service.
Payload renvoie alors des URLs de média absolues. La balise <img> du navigateur appelle le
content-service en direct, sans clé API ni cookie, et l'accès aux médias exige une autorisation :
une lecture anonyme reçoit un 403. Absente, Payload émet des URLs relatives, que le navigateur
résout depuis l'origine du portail ; elles arrivent donc sur le proxy /api/media de hub et de
portal, qui ajoute la clé API côté serveur.
Correctif.
kubectl exec -n api deploy/trade-content-service-svc -- \
env | grep -E 'SERVER_URL|PAYLOAD_PUBLIC'
vault kv patch -mount=secrets prod/content-service SERVER_URL=
kubectl rollout restart deployment/trade-content-service-svc -n api
La variable doit rester vide, jamais renseignée. patch et non put : ce chemin porte
toutes les autres clés du service.
Réparé quand.
- Sur le nouveau pod,
kubectl exec ... env | grep -E 'SERVER_URL|PAYLOAD_PUBLIC'ne rend rien, ou rend une valeur vide. L'environnement d'un pod déjà démarré est figé : mesurer sur le pod d'avant ne prouve rien. - Dans l'inspecteur réseau du navigateur, sur une page qui porte des images, les requêtes média
partent vers
https://hub.${TV_DOMAIN}/api/media/...et non plus vershttps://content.${TV_DOMAIN}/..., et elles rendent 200. - Le compteur de 403 cesse d'augmenter. C'est vérifiable, et c'est précisément ce qu'il faut chercher dans les journaux :
kubectl logs -n api deploy/trade-content-service-svc --since=10m \
| grep -c 'not allowed to perform this action'
Attendu : 0.
SERVER_URL sert aussi ailleurs
Elle alimente les liens des courriels, la prévisualisation et certains liens absolus de la console d'administration Payload. Vérifier ces trois usages après le retrait. Si l'un casse, le correctif est applicatif, côté portails, jamais le retour de la variable : la remettre ramène les 403 à l'identique.
7. La section des taux de change est vide, et le redevient à chaque redémarrage¶
Symptôme. La section des taux de change est vide sur le hub et sur le portal, en même
temps. En base, tous les taux portent exchangeStatus = "expired" alors que leur validTo est
dans le futur, donc qu'ils sont encore valides. Signature caractéristique : expiredBy vaut
NULL, c'est-à-dire une écriture système et non une action humaine. Et le symptôme revient après
chaque redémarrage de pod, y compris après une réparation des données.
La fausse piste. Deux. La première est de réparer les données et de considérer l'affaire
close : tant que le correctif applicatif n'est pas déployé, chaque redémarrage recorrompt les
taux, et une réparation faite avant ce déploiement est effacée au prochain redémarrage. La
seconde est de chercher dans les journaux : le catch du seed masque l'erreur d'unicité sous un
message du genre « already exists, skipping ». Rien n'apparaît dans les journaux. C'est ce
silence qui rend la panne coûteuse.
Cause. PAYLOAD_AUTO_SEED est définie. Le seed complet s'exécute alors à chaque démarrage de
pod, sans garde ni verrou. L'enchaînement : le seed recrée un taux avec le même validFrom pour
chaque devise ; un hook expire le taux existant qui chevauche, dans une transaction séparée qui
valide immédiatement ; le create échoue ensuite sur l'index unique et est annulé, mais
l'expiration est déjà validée. Il reste une ligne expirée sans remplaçante, répété sur toutes
les devises. Un HPA qui démarre plusieurs pods amplifie la corruption, chaque pod lançant son
propre seed.
Correctif.
L'état sûr est l'absence de la clé, pas une valeur vide. Or vault kv patch ne sait
qu'ajouter ou remplacer une clé, jamais en retirer une : le retrait passe par une réécriture
délibérée du chemin, à partir de son contenu relu juste avant.
kubectl exec -n api deploy/trade-content-service-svc -- env | grep PAYLOAD_AUTO_SEED
vault kv get -format=json -mount=secrets prod/content-service | jq '.data.data' > cs.json
# retirer la ligne PAYLOAD_AUTO_SEED de cs.json, et rien d'autre
vault kv put -mount=secrets prod/content-service @cs.json
vault kv get -mount=secrets prod/content-service
rm -f cs.json
kubectl rollout restart deployment/trade-content-service-svc -n api
C'est le seul put légitime sur un chemin partagé, et il ne l'est que parce que le fichier
porte toutes les autres clés, relues du chemin lui-même une commande plus tôt. Le vault kv get
qui suit le put n'est pas décoratif : il constate que les autres clés sont toujours là. Le
fichier intermédiaire contient des valeurs de secret en clair : le supprimer, et ne jamais
l'écrire dans le clone git.
Puis réparer les données, et seulement les lignes expirées par le système. expired_by_id IS
NULL les distingue d'une expiration décidée par un administrateur, par exemple le retrait d'un
taux erroné, qu'il ne faut surtout pas remettre en service. Passer d'abord la lecture seule,
puis le même WHERE en UPDATE, dans une transaction :
SELECT id, currency_id, valid_from, valid_to FROM exchange_rates
WHERE exchange_status = 'expired' AND expired_by_id IS NULL
AND valid_from <= NOW() AND valid_to >= NOW()
AND NOT EXISTS (
SELECT 1 FROM exchange_rates e2
WHERE e2.currency_id = exchange_rates.currency_id
AND e2.administration_id = exchange_rates.administration_id
AND e2.exchange_status = 'active' AND e2.id <> exchange_rates.id
AND e2.valid_from <= NOW() AND e2.valid_to >= NOW());
Réparé quand. La ligne Running auto-seed... n'apparaît plus au démarrage. C'est le seul
signal positif que les journaux donnent sur ce défaut :
Attendu : aucune sortie. Puis le contrôle décisif, le seul qui prouve le correctif et non la réparation : redémarrer le pod une seconde fois et vérifier que la section reste peuplée sur les deux portails. Une réparation de données suivie d'un redémarrage sans corruption est la preuve ; la réparation seule ne prouve rien.
8. Les portails appellent encore l'ancien domaine après un renommage¶
Symptôme. Après un changement de ${TV_DOMAIN}, la fiche client rejouée et les manifests
réappliqués, les pages des portails se chargent, mais les appels émis par le navigateur
partent vers l'ancien domaine : requêtes en échec dans la console du navigateur, connexion qui
redirige vers l'ancien host auth. Côté Kubernetes, rien : les pods sont Running, Ready,
sans redémarrage, sans erreur dans les journaux.
La fausse piste. Redéployer, faire un kubectl rollout restart, modifier une variable
d'environnement sur le Deployment, patcher la valeur dans Vault. Aucune de ces actions n'a le
moindre effet, et l'absence d'effet fait chercher ailleurs alors que la cause est identifiée dès
la première tentative.
Cause. Les variables NEXT_PUBLIC_* sont résolues au moment où l'image est construite,
jamais au démarrage du conteneur. Rien au runtime ne peut les changer. Corollaire : un
kubectl rollout undo ne répare pas non plus, il restaure une image qui porte elle aussi
l'ancien domaine.
Correctif. Reconstruire les trois images de portail avec les bonnes valeurs, les publier sur
${TV_REGISTRY} sous les noms et tags attendus, puis redéployer. C'est le seul correctif.
Vérifier dans le même mouvement les valeurs qui, elles, vivent au runtime et demandent seulement
un redémarrage : AUTH_KEYCLOAK_ISSUER, NEXTAUTH_URL, NEXTAUTH_URL_INTERNAL, l'authority
OIDC des services .NET, les *_API_BASE_URL, CONTENT_SERVICE_URL, ALLOWED_ORIGINS. Et hors
du cluster : les Valid Redirect URIs et Web Origins du realm Keycloak, ainsi que la
résolution DNS des postes.
Réparé quand. Deux constats, dont aucun ne passe par l'état des pods :
kubectl get deploy hub-portail -n web \
-o jsonpath='{.spec.template.spec.containers[0].image}'; echo
Le tag lu doit être différent de celui d'avant. La plupart des images applicatives portent
latest : avec un tag identique, rien ne prouve que le nouveau contenu a été tiré, et le pod
peut servir l'ancienne image indéfiniment. Utiliser un tag distinct par version, ou forcer
imagePullPolicy: Always.
Ensuite, dans le navigateur, page rechargée sans cache : l'inspecteur réseau ne montre aucune
requête vers l'ancien domaine, et un parcours de connexion complet aboutit. Un pod Running et
Ready ne prouve rien ici, et c'est exactement pour cela que ce défaut survit à un redéploiement.
9. Les images du hub mettent des dizaines de secondes à s'afficher¶
Symptôme. Sur la page d'accueil du hub, une image peut mettre jusqu'à une trentaine de
secondes avant le premier octet, en interne comme en public. Les fichiers statiques de la même
page reviennent en quelques dizaines de millisecondes. Aucun redémarrage, aucun OOMKilled,
aucun évènement Kubernetes, aucune erreur dans les journaux. Le pod est Running et Ready
pendant toute la durée du problème.
La fausse piste. Soupçonner l'ingress, un WAF ou le tunnel Cloudflare, surtout si l'un d'eux
a été activé le même jour. Le témoin qui les écarte tous en une mesure : un fichier statique du
même host répond en 25 ms. Comparer un chemin instrumenté à un chemin témoin évite de chercher
au mauvais endroit. Seconde fausse piste, le réseau : curl -w montre time_starttransfer
presque égal à time_total, donc du temps passé avant le premier octet, donc du calcul et
non du transfert.
Cause. Un plafond CPU trop bas sur une charge de calcul par rafales. Le portail redimensionne
et ré-encode ses images à la volée (/_next/image) ; avec limits.cpu: 250m, le cgroup étale
sur quatre fois plus de temps un travail qui demande un coeur entier pendant quelques centaines
de millisecondes. Le conteneur n'est pas tué, il est ralenti. Contrairement à une limite mémoire,
qui tue le conteneur et laisse une trace (OOMKilled, exit 137), le throttling CPU est invisible
dans l'état des objets Kubernetes. Trois facteurs amplifient : un fichier source de plusieurs
mébioctets, huit variantes demandées dans plusieurs formats selon le navigateur, et un cache
éphémère et par réplique, vidé à chaque rollout.
Correctif. Porter la limite CPU du portail à 2000m et la limite mémoire à 512Mi, les tampons
d'encodage AVIF étant gourmands. Laisser les requests inchangées : rien de plus n'est alors
réservé sur le noeud. Le faire sur les trois portails, pas seulement celui qui a été
signalé : même runtime, même défaut latent, et un portail derrière authentification ne montre pas
sa lenteur depuis l'extérieur, raison de plus pour ne pas attendre qu'un utilisateur la signale.
Le correctif de fond est applicatif : alléger les fichiers sources, un WebP de 1920 px de qualité
équivalente pesant de l'ordre de 200 Ko contre plusieurs mébioctets.
Réparé quand. Deux mesures de la même commande, l'une avant le changement, l'autre après,
toutes deux à froid, c'est-à-dire juste après un rollout restart qui vide le cache :
kubectl rollout restart deployment/hub-portail -n web
kubectl rollout status deployment/hub-portail -n web
curl -sS -o /dev/null -w 'starttransfer=%{time_starttransfer} total=%{time_total}\n' \
'https://hub.${TV_DOMAIN}/_next/image?url=%2Fimages%2Fhero-slide-1.png&w=1920&q=75'
C'est le rapport entre les deux mesures qui conclut, pas une valeur absolue. Passer de 250m
à 2000m lève un facteur d'étalement du même ordre : un temps inchangé, ou qui ne gagne qu'un
tiers, signifie que le plafond n'a pas bougé. Deux raisons de ne transposer aucun chiffre : le
matériel du client est inconnu, et curl n'annonce pas Accept: image/avif, donc il ne reçoit
pas la variante AVIF, la plus coûteuse et celle qu'un navigateur demande. La mesure par curl
est un indicateur relatif, jamais le temps que voit l'utilisateur. Un second appel, cache chaud,
ne prouve rien : il répond vite dans les deux cas.
Contrôle complémentaire, celui qui prouve que la nouvelle limite est réellement en vigueur : pendant la requête,
doit montrer une consommation au-dessus de 250m. Si elle plafonne exactement à 250m, le manifeste modifié n'a pas été appliqué, quelle que soit la latence observée.
Aucun tableau de bord ni aucune alerte n'aurait signalé cette panne. Superviser la latence,
pas seulement la disponibilité : c'est le tableau tradeval-red de
Observabilité qui la porte.
10. Le nombre de pods explose et l'ordonnancement se bloque¶
Symptôme. Peu après l'installation de metrics-server, le nombre de pods bondit, le CPU des
workers monte à 98-99 %, des pods restent en Pending avec
0/6 nodes are available: 3 Insufficient cpu, les mises à jour en cours se bloquent, et des 404
intermittents apparaissent sur les hosts publics.
La fausse piste. Conclure que metrics-server a cassé quelque chose, et le désinstaller. Il
n'a rien cassé : il a rendu exécutable une configuration erronée qui dormait. Jusque-là les
HPA affichaient <unknown> et n'avaient jamais rien fait, donc leur configuration n'avait jamais
été confrontée au réel. Le désinstaller recache le défaut au lieu de le corriger, et le prochain
opérateur le retrouvera intact.
Cause. Une HPA basée sur la mémoire, avec un request inférieur à l'usage au repos, monte
indéfiniment. Les services .NET consomment nettement plus que leur request, donc structurellement
au-dessus de la cible : ajouter des répliques ne réduit pas la mémoire par pod d'un runtime à
ramasse-miettes, qui occupe ce qu'on lui laisse. Le système monte jusqu'à maxReplicas quelle que
soit la charge réelle. Facteur aggravant, un maxReplicas très élevé sur un cluster de trois
workers.
Correctif. Commencer par situer ce que l'on regarde : les dix HPA livrés portent déjà le
correctif de cet incident. Tous sont à maxReplicas: 3, sauf celui de hub-portail à 6,
tous n'ont qu'une métrique cpu, aucune métrique memory, et chaque manifeste porte
l'explication à l'endroit où la valeur se lit.
kubectl get hpa -A -o custom-columns=\
'NS:.metadata.namespace,NOM:.metadata.name,MAX:.spec.maxReplicas,METRIQUES:.spec.metrics[*].resource.name'
Ce qui se lit dans cette sortie décide de la suite :
| Ce que la sortie montre | Ce que cela signifie |
|---|---|
MAX à 3, ou 6 pour hub-portail, et METRIQUES à cpu seul |
l'état livré. Le problème est ailleurs, ce n'est pas cet incident |
Une métrique memory, ou un MAX très supérieur |
une divergence, pas l'état livré. Quelqu'un a modifié le HPA dans le cluster, ou un manifeste plus ancien a été réappliqué |
Dans le second cas, réappliquer les manifests du dépôt, qui portent les bonnes valeurs. Si la
divergence vient du dépôt lui-même, la corriger dans les deux : sinon un kubectl apply
ultérieur réapplique les anciennes valeurs. Règle générale à retenir au-delà de ce cas : ne
jamais activer une HPA mémoire sur un runtime à ramasse-miettes (.NET, JVM, Node) sans avoir
vérifié que le request reflète l'usage au repos.
Réparé quand.
La colonne TARGETS ne porte plus de métrique mémoire, REPLICAS est revenu au nominal et y
reste sur plusieurs minutes, aucun pod n'est Pending, et le CPU des workers est redescendu.
Un HPA silencieux n'est pas forcément un HPA réparé
Un HPA qui affiche <unknown> dans TARGETS n'est pas stabilisé, il est aveugle : la
source de métriques ne lui répond pas. kubectl top nodes doit rendre des valeurs. Un
cluster calme parce que ses autoscalers ne voient rien est exactement l'état qui a précédé
cet incident, et c'est une panne à part entière :
entrée 13.
11. Un pod répond mais Kubernetes le déclare indisponible, et l'host renvoie 404¶
Symptôme. Un host d'ingress renvoie 404 par intermittence, en interne comme par le tunnel. Le
pod correspondant est Running mais 0/1, parfois depuis des heures, sans jamais
redémarrer. Depuis l'intérieur du conteneur, le chemin de santé répond 200.
La fausse piste. Soupçonner Cloudflare ou l'ingress, puisque le symptôme est un 404 sur une route. Le comportement identique en interne et par le tunnel écarte le tunnel en une mesure. Seconde fausse piste, plus tenace : mesurer le chemin de santé depuis l'intérieur du conteneur, constater 200, et conclure que le pod est sain. Il l'est. Ce n'est pas ce chemin que la sonde interroge.
Cause. La sonde de disponibilité pointe un chemin couvert par le middleware
d'authentification. Ce chemin répond une redirection vers la page de connexion, et le kubelet
suit les redirections HTTP : la sonde mesure donc le temps de rendu de la page de connexion,
variable, régulièrement supérieur au timeoutSeconds accordé. Le pod perd sa disponibilité, il
est retiré du Service, et Traefik répond 404 faute d'endpoint. Une sonde qui échoue une fois sur
cinq produit un pod qui oscille, bien plus difficile à diagnostiquer qu'une panne franche.
Le signal révélateur n'est pas dans les journaux du pod, qui ne disent rien : il est dans les évènements du kubelet.
Chercher une ligne
Readiness probe failed: Get "http://<ip>:3000/api/auth/signin?callbackUrl=%2Fhealth": context
deadline exceeded. L'URL redirigée qui apparaît dans le message est le diagnostic complet :
la sonde n'interroge pas ce que l'on croit.
Correctif. Pointer les sondes de disponibilité et de démarrage sur un chemin qui répond 200
sans rediriger (/api/health côté portails Next.js, /health et /health/ready côté .NET), et
porter timeoutSeconds à 5 s pour une application Next.js en rendu serveur. Un chemin de sonde
ne doit jamais rediriger, ni s'authentifier, ni dépendre d'un service externe : sinon il ne
mesure pas la santé du pod, mais celle de la chaîne qu'il traverse.
Un seul manifeste livré ne respecte pas encore la seconde moitié de cette règle
Les trois portails pointent bien /api/health, qui ne redirige pas : la première moitié du
défaut est corrigée partout. La seconde ne l'est pas.
apps/web/portal/portal-portail.yaml et apps/web/internal/internal-portail.yaml portent
timeoutSeconds: 5 sur leur sonde de disponibilité ; apps/web/hub/hub-portail.yaml n'en
porte aucun, il prend donc le défaut de Kubernetes, 1 seconde, exactement la valeur que
l'incident incrimine, pour un rendu serveur mesuré à 40-80 ms avec des pointes à 2,3 s.
hub-portail est le seul portail public sans authentification, donc celui dont un battement
se voit immédiatement de l'extérieur. Aligner sa sonde à 5 s avant la mise en service.
Réparé quand. Un défaut intermittent ne se constate pas en un appel.
kubectl get endpoints <service> -n <zone>
kubectl get pods -n <zone> -l app=<nom> -w # observer au moins deux minutes
for i in $(seq 1 20); do
curl -s -o /dev/null -w '%{http_code} ' https://portal.${TV_DOMAIN}/
done; echo
L'IP du pod figure bien dans les endpoints, c'est elle qui manquait quand Traefik répondait 404.
Le pod reste 1/1 sur toute la durée d'observation, sans osciller. Et les vingt requêtes rendent
vingt fois le même code de succès, sans un seul 404 et sans un seul 000 : ce dernier n'est pas un
code de réponse, c'est curl qui n'a pas abouti, et vingt 000 ne sont donc pas vingt codes
identiques. C'est le seul endroit du guide où curl reste volontairement muet, pour que les vingt
codes restent lisibles : devant un 000, rejouer une seule requête en
curl -sS -o /dev/null -w '%{http_code}\n' https://portal.${TV_DOMAIN}/ donne la raison que la
boucle taisait. Enfin, kubectl describe pod ne montre plus aucun évènement
Readiness probe failed récent.
12. kubectl devient injoignable et les hosts tombent par intermittence¶
Symptôme. Les hosts d'ingress deviennent injoignables par intermittence, puis kubectl cesse
complètement de répondre pendant que les applications continuent de servir. Les
kube-apiserver de deux des trois control-planes sont en CrashLoopBackOff.
La fausse piste. Croire le cluster perdu. Le plan de données survit à la perte de l'API : les workloads continuent de servir tout du long. Et le diagnostic reste possible en visant directement le control-plane survivant :
C'est la commande à connaître avant d'en avoir besoin : avec un endpoint de kubeconfig pointé sur une machine tombée, tout paraît mort.
Cause. L'activation d'un WAF de niveau 2 (Coraza) sur les Ingress Traefik, par trois mécanismes distincts qui se sont enchaînés :
- Un middleware invalide casse le routage, il ne le laisse pas passer. Mal typé, le plugin ne se contente pas d'être inactif : les routers qui le référencent deviennent inopérants et les hosts publics tombent. Un WAF mal configuré est une panne, pas une absence de protection.
- La consommation mémoire est pilotée par la concurrence, le host WASM instanciant un module par requête concurrente. Une seule instance mesurée à 762 Mio pour une base d'environ 80 Mio.
- Relever la limite mémoire de Traefik alors qu'il tolère les taints control-plane sature
les control-planes, dont le
kube-apiserverest alors tué. C'est le mécanisme qui transforme une panne d'ingress en perte de l'API.
Correctif. Le WAF de niveau 2 n'est pas viable dans cette configuration et a été retiré de
tous les Ingress : ne pas le réactiver. Traefik ne doit jamais pouvoir être ordonnancé sur un
control-plane : l'épingler sur les workers par nodeSelector et retirer les tolerations. Garder
les Ingress publics sur le seul entrypoint websecure, le port 80 redirigeant globalement vers
443. Et dimensionner les control-planes à 4 Gio : à 2 Gio, ils tournaient à 100-117 %
d'utilisation mémoire en régime normal, avec six à sept redémarrages d'apiserver cumulés en
soixante-sept jours.
Réparé quand.
kubectl get pods -n kube-system -l component=kube-apiserver -o wide
kubectl get pods -n traefik-ingress -o wide
kubectl top nodes
Les trois apiservers Running avec un compteur RESTARTS stable sur plusieurs minutes : un
compteur qui continue de monter signifie que les noeuds sont toujours saturés. Aucun pod Traefik
sur un noeud control-plane. Et la mémoire des control-planes nettement sous 100 %. Dernier
constat, le plus simple : kubectl get nodes répond depuis l'endpoint normal du kubeconfig, et
plus seulement en visant une machine précise.
Noter les trois adresses de control-plane avant d'en avoir besoin
Si l'endpoint du kubeconfig désigne une seule machine, la perte de cette machine supprime
tout accès d'administration, y compris pour diagnostiquer. Consigner les adresses des trois
control-planes dans le dossier d'exploitation du client, à côté des clés de descellement de
Vault. Cette information ne se retrouve pas quand kubectl ne répond plus.
13. kubectl top ne rend rien et les HPA restent en <unknown>¶
Symptôme. kubectl get hpa -A affiche <unknown> dans la colonne TARGETS sur tous les
HPA, et kubectl top nodes rend error: Metrics API not available ou ne rend rien. Rien
d'autre ne se plaint : les pods applicatifs tournent, les hosts répondent, et la recette de
conformité passe, puisqu'elle ne regarde pas la source de métriques.
La fausse piste. Conclure que c'est l'état normal, au motif que « les HPA sont inertes tant
que metrics-server n'est pas installé ». C'était vrai avant l'installation. Le guide pose
core/metrics-server/metrics-server.yaml à l'étape 10 du Pas-à-pas :
après une installation conforme, <unknown> signifie que la source ne sert pas de métriques,
pas qu'elle est absente par conception. La seconde fausse piste est de s'en accommoder, parce
que rien ne casse tant qu'on ne cherche rien.
Cause. Deux, et deux commandes les séparent. L'APIService v1beta1.metrics.k8s.io est
déclaré dans le même fichier que le Deployment, et ce fichier ne fait partie ni de la couche de
base ni d'aucun autre kubectl apply de la procédure : son absence et sa présence disent donc
la même chose que celles du pod.
kubectl get apiservice v1beta1.metrics.k8s.io
kubectl get pods -n kube-system -l k8s-app=metrics-server
| Ce que les deux commandes rendent | Cause |
|---|---|
Error from server (NotFound) sur l'apiservice, et aucun pod |
1. L'étape 10 n'a pas été jouée en entier, le manifeste n'a jamais été appliqué. C'est le cas le plus fréquent, ce manifeste ne faisant pas partie de la couche de base |
L'apiservice existe en AVAILABLE=False, avec un pod 0/1 ou en attente |
2. Le pod ne sert pas de métriques. FailedDiscoveryCheck sur l'apiservice est la conséquence de cet état, pas une troisième piste : il reste False tant que le pod n'est pas 1/1 |
Sur la cause 2, le motif connu sur Talos est le refus de valider les certificats de service des
kubelets, qui ne portent pas d'IP SAN :
x509: cannot validate certificate for <adresse> because it doesn't contain any IP SANs. Le
manifeste du dépôt porte déjà le drapeau --kubelet-insecure-tls qui lève ce cas ; ce message
dans les journaux signifie donc que le Deployment en place n'est pas celui du dépôt,
typiquement parce qu'un manifeste amont a été appliqué par-dessus.
Ce que cette panne coûte, et pourquoi elle est classée ici. Elle rend muets quatre points de
contrôle donnés comme décisifs ailleurs dans ce guide : le kubectl top pod de
l'entrée 9, seul contrôle
qui prouve qu'une nouvelle limite CPU est en vigueur ; le kubectl top nodes des entrées
10 et
12 ; et la ligne
« Mémoire des control-planes » du tableau de surveillance quotidienne de
Exploitation. Un opérateur qui croit surveiller ne surveille rien. Elle
immobilise en outre les dix HPA du dépôt, sans le moindre message.
Correctif.
kubectl logs -n kube-system -l k8s-app=metrics-server --tail=50
kubectl apply -f core/metrics-server/metrics-server.yaml
Le même kubectl apply répare les deux causes : il pose le Deployment absent dans le premier
cas, et il restaure dans le second le drapeau que le manifeste amont ne porte pas. Sur la
cause 1, les journaux n'ont rien à dire, il n'y a pas de pod ; c'est sur la cause 2 qu'ils
servent, et la ligne à y chercher est celle qui contient IP SANs.
Réparé quand.
AVAILABLE=True sur l'apiservice, kubectl top nodes rend des chiffres pour tous les
noeuds, et la colonne TARGETS des HPA porte un pourcentage au lieu de <unknown>. Laisser
jusqu'à une minute après le démarrage du pod : la première fenêtre de collecte n'est pas
instantanée. Un kubectl top nodes qui ne rend qu'une partie des noeuds n'est pas réparé, il
est partiellement aveugle.
Ne pas rendre les métriques disponibles sans avoir lu l'entrée 10
Les rendre disponibles réveille d'un coup les dix HPA du dépôt, avec leur configuration telle qu'elle est écrite, sans période de rodage. C'est ce qui a saturé le CPU du cluster de référence en quelques minutes. Les valeurs livrées portent déjà le correctif de cet incident (entrée 10) : vérifier qu'elles n'ont pas été modifiées depuis avant de poursuivre.
14. Deux DaemonSets Longhorn redémarrent sans fin sur Talos¶
longhorn-iscsi-installation et longhorn-preflight-installer restent en
Init:CrashLoopBackOff, avec un compteur de redémarrages qui monte indéfiniment. Sur ce
cluster il avait atteint 24 000 par pod, sur 85 jours.
Le message est sans ambiguïté :
nsenter: can't execute 'apk': No such file or directory
nsenter: can't execute 'rc-update': No such file or directory
nsenter: can't execute 'service': No such file or directory
Ces deux DaemonSets installent open-iscsi sur l'hôte avec le gestionnaire de paquets
d'Alpine. Talos est immuable : il n'a ni apk, ni rc-update, ni service. Ils ne peuvent
structurellement jamais réussir, et aucun réglage ne les y aidera.
Ils sont aussi inutiles. Les workers embarquent l'extension Talos iscsi-tools
(Prérequis), et Longhorn le confirme lui-même :
kubectl get nodes.longhorn.io -n longhorn-system -o custom-columns=\
'NOEUD:.metadata.name,PAQUETS:.status.conditions[?(@.type=="RequiredPackages")].status'
RequiredPackages=True signifie que le prérequis est déjà satisfait.
La correction est de les supprimer :
kubectl delete daemonset longhorn-iscsi-installation -n longhorn-system
kubectl delete daemonset longhorn-preflight-installer -n longhorn-system
Vérifier avant de supprimer qu'ils ne portent aucune étiquette Helm
(app.kubernetes.io/managed-by) : appliqués à la main, rien ne les recrée. Gérés par une
release, il faut au contraire les désactiver dans les values, sans quoi ils reviennent à la
prochaine montée de version.
Ce qui n'est pas une panne¶
Deux anomalies apparaissent sur une installation conforme et n'ont pas à être corrigées. Les confondre avec un défaut fait perdre du temps, et les corriger « au cas où » en crée un vrai.
| Ce que l'on voit | Pourquoi c'est normal |
|---|---|
Un Grafana vide, sans datasource, dans le namespace observability |
Stack héritée, posée par la couche de base, qui ne reçoit rien. Celle qui porte les données est la stack monitoring (Observabilité) |
La liste s'arrête là, et c'est délibéré. Deux symptômes en particulier n'y figurent pas et ne doivent jamais y entrer :
- Un
ImagePullBackOffdans une des quatre zones applicatives n'est jamais normal. Il signifie queacr-pull-secretn'a pas été créé, que l'image n'a pas été publiée sur${TV_REGISTRY}, que son tag ne correspond pas à ce qui a été publié, ou que la sortie HTTPS sur le port 443 est filtrée depuis les noeuds (Prérequis), cause qu'aucun contrôle depreflight.shne teste. Le geste qui sépare cette quatrième cause des trois autres estkubectl get pods -A | grep -E 'ImagePullBackOff|ErrImagePull': les trois premières ne touchent que les quatre zones applicatives, alors qu'un port 443 fermé bloque aussi les images amont de la couche de base, danskube-system,traefik-ingressoucert-manager. Lekubectl describe podle confirme, un délai dépassé ou un nom non résolu sur l'hôte du registre plutôt qu'un401ou unnot found. Aucun pod de ce namespace n'est censé échouer à tirer son image. <unknown>danskubectl get hpan'est pas normal non plus après une installation conforme : c'est une panne, et elle rend muets quatre contrôles de ces deux pages. Voir l'entrée 13.
En revanche, trois interfaces d'exploitation exposées sans authentification ne sont pas une anomalie bénigne : c'est une exposition connue, à traiter avant toute mise en service, selon la recette de Observabilité.
Collecter les éléments avant de demander de l'aide¶
Avant tout redémarrage, et dans cet ordre. Un rollout restart passé trop tôt détruit la preuve
du défaut qu'on cherche à faire analyser.
scripts/validate-install.sh --env scripts/install.env > diag-validate.txt 2>&1
kubectl get events --sort-by=.lastTimestamp -A > diag-events.txt
kubectl get pods -A -o wide > diag-pods.txt
kubectl get certificate,order,challenge -A > diag-certs.txt
kubectl get externalsecret -A > diag-secrets.txt
kubectl get clustersecretstore vault-store -o wide >> diag-secrets.txt
kubectl describe pod <pod-concerne> -n <ns> > diag-describe.txt
kubectl logs deploy/<service-concerne> -n <ns> --tail=500 --timestamps > diag-logs.txt
kubectl logs deploy/<service-concerne> -n <ns> --previous --tail=500 > diag-logs-precedent.txt 2>/dev/null
Quatre précautions qui décident de l'exploitabilité de ce que vous envoyez :
- Les évènements d'abord.
kubectl get eventsne porte qu'une heure d'historique par défaut. C'est la première chose à collecter, et la seule qui contienne le message de sonde de l'entrée 11. --previousest la seule façon de lire les journaux d'un conteneur qui a déjà redémarré. Après un second redémarrage, ils sont perdus aussi.- Aucune valeur de secret. Ne jamais joindre la sortie de
vault kv get, ni unkubectl get secret -o yaml, ni le fichier produit parvault operator init. Envoyer les noms de clés : - Dire depuis où le symptôme se voit : depuis un poste, depuis l'intérieur du cluster, depuis internet, ou depuis les trois. La moitié des entrées de cette page se départagent sur cette seule information.
Joindre enfin, en clair, ce qui n'est pas secret et sans quoi rien n'est interprétable :
${TV_DOMAIN}, ${TV_VIP}, ${TV_KC_REALM}, la date et l'heure de la dernière modification
faite sur le cluster, et ce qui a été tenté depuis.