Sprint 8 — Tests & Signaux¶
INC-077 : le bootstrap est vert, Grafana est cassé depuis trois heures¶
Sévérité : High Statut : 🟠 Contourné à la main le 2026-09-28 — correctif écrit et prouvé en lab (#236), à confirmer au prochain montage ; détection dans #238
Contexte : montage du 2026-09-28 pour une démo (pipeline 2888592193). bootstrap vert à 08:25:31 UTC. Grafana ouvert à la main vers 13 h, heure de Paris : la page ne répond pas.
Fichiers : k8s/platform/argocd-apps/monitoring.yaml (sync-wave 1), k8s/platform/argocd-apps/kube-prometheus-stack.yaml (sync-wave 0), k8s/platform/monitoring/kustomization.yaml
Symptôme : trois symptômes, qui semblaient sans lien.
grafana.devopsyouss.comn'a aucun enregistrement DNS, alors queapi,appetargocdpointent vers le NLB.- Le pod Grafana ne démarre pas, depuis 3 h 07 et 852 tentatives :
Warning Failed kubelet spec.containers{grafana-sc-dashboard}: Error: secret "grafana-admin" not found
- L'Application
monitoringestOutOfSync/Missing:
SyncError: Failed last sync attempt to [2257df1]: one or more synchronization
tasks are not valid (retried 5 times).
Diagnostic : du symptôme vers la cause, maillon par maillon.
- Le Secret
grafana-adminest matérialisé par ESO depuis uneExternalSecretdu dossierk8s/platform/monitoring/. LaHTTPRoutede Grafana, que lit ExternalDNS, est dans le même dossier. Les deux absences ont donc une seule cause : ArgoCD n'a rien appliqué de ce dossier. - Le détail de la sync (
status.operationState.syncResult.resources) nomme quatre ressources refusées :ServiceMonitor fastapi,PrometheusRule fastapi,PrometheusRule probes,Probe api-health, toutes aveccould not find monitoring.coreos.com/.... - Les horodatages tranchent :
| Événement | Heure (UTC) |
|---|---|
Début de la sync de monitoring |
08:25:50 |
monitoring abandonne après 5 essais |
08:31:05 |
CRD prometheus-operator créées (kubectl get crd) |
08:32:01 |
Il a manqué 56 secondes.
Cause : les sync-waves ordonnent la création des Applications, pas leur disponibilité. kube-prometheus-stack est en vague 0, monitoring en vague 1. Mais aucun health check n'est configuré pour le type argoproj.io/Application : depuis ArgoCD 1.8, l'app parente n'attend pas qu'une Application enfant soit Healthy pour passer à la vague suivante. monitoring fait donc la course avec l'installation des CRD. Les montages précédents l'ont gagnée dans les 5 essais. Celui-ci l'a perdue, et une fois les essais épuisés, l'auto-sync ne retente plus sur la même révision.
Aggravant : ArgoCD valide tout le lot avant d'appliquer. Quatre ressources invalides ont bloqué toutes les autres, y compris l'ExternalSecret et la HTTPRoute, qui ne dépendent d'aucune CRD de prometheus-operator.
Contournement : deux pièges en plus sur le chemin.
- Le bouton Sync de l'interface ArgoCD n'a lancé aucune opération :
operationStaterestait daté de 08:25. Le vérifier par l'horodatage, pas par le message, qui était identique. - Une demande de sync explicite a marché (
Succeededà 11:50:38 UTC) :
kubectl -n argocd annotate application monitoring argocd.argoproj.io/refresh=hard --overwrite
kubectl -n argocd patch application monitoring --type merge \
-p '{"operation":{"initiatedBy":{"username":"youssef"},"sync":{"revision":"develop"}}}'
- Le DNS public (
1.1.1.1) résolvait Grafana, mais le poste gardait la réponse négative du matin en cache. Grafana répondait200sur/api/healthen forçant l'adresse du NLB. Il a fallu vider le cache DNS de Windows (ipconfig /flushdns) et celui du navigateur.
Validation : monitoring en Synced / Healthy, ExternalSecret grafana-admin en SecretSynced, HTTPRoute grafana présente, Grafana accessible. Puis la chaîne d'alerte a été rejouée de bout en bout dans la même séance : prod passée à 0 pod, FastAPITargetMissing et ApiEndpointDownProd en FIRING sur Slack après les 2 min de for:, puis RESOLVED.
Correctif (#236) : un syncPolicy.retry explicite sur l'Application monitoring : 20 essais, 1 minute au plus entre deux, soit environ 20 minutes de patience au lieu de 5. La sync ne fait plus la course, elle attend que les CRD existent.
Rejoué sur un cluster kind jetable, avec ArgoCD 3.4.3 et les manifests réels, les CRD de prometheus-operator étant installées 7 minutes après le début de la sync :
Version de monitoring.yaml |
Résultat |
|---|---|
sans retry (celle de l'incident) |
abandon à +322 s, retried 5 times, toujours OutOfSync 3 minutes après l'arrivée des CRD |
avec le retry |
Synced à +439 s, 14 secondes après les CRD, sans geste manuel |
Le lab ne prouve que le mécanisme de réessai : aucun opérateur n'y tourne, donc il ne dit rien de Grafana lui-même.
Ce qui reste : confirmer le correctif sur un montage EKS neuf (monitoring en Synced / Healthy sans geste manuel), #238 (un job non bloquant après bootstrap qui vérifie toutes les Applications et les adresses publiques).
Leçon. Un bootstrap vert disait « tout est lancé », pas « tout marche ». Et les alertes ne pouvaient pas le voir : les PrometheusRule vivent dans l'Application monitoring, celle qui était cassée. Le détecteur était dans le composant en panne. Seul un contrôle extérieur à ArgoCD voit cette classe de panne.
INC-078 : Forbidden sur kubectl get nodes, deux secondes après avoir reçu le droit¶
Sévérité : Low Statut : 🟠 Contourné par un retry du job le 2026-09-28 — correctif dans #237
Contexte : même montage (pipeline 2888592193), job infra-start, première tentative (job 16772745864, 1058 s).
Fichiers : .gitlab-ci-infra.yml (infra-start, ligne 156 ; before_script de bootstrap, ligne 173)
Symptôme : terraform apply réussit (46 ressources créées), puis :
Error from server (Forbidden): nodes is forbidden: User
"arn:aws:iam::199167114788:user/ci/fastapi-eks-gitlab-ci-infra" cannot list
resource "nodes" in API group "" at the cluster scope
Cause : la cohérence à terme (eventual consistency) des droits EKS.
| Événement | Heure (UTC) |
|---|---|
aws_eks_access_policy_association.cluster_admin["gitlab_ci_infra"] créée |
08:12:03 |
kubectl get nodes |
08:12:05 |
L'API confirme l'association tout de suite, mais le droit n'est pas encore actif partout. Le job n'attend pas. Les montages précédents sont passés par chance sur le timing.
Contournement : relance du job (Retry). Terraform ne trouve rien à changer, kubectl get nodes passe, bootstrap s'enchaîne.
Leçon. Même famille qu'INC-077, le même matin : créé n'est pas prêt. Un appel qui suit immédiatement une création doit réessayer un nombre borné de fois, sans quoi un refus réel et un délai de propagation se confondent.
INC-079 : 503 à chaque rollout, le pod ferme son port avant qu'Envoy ne le retire¶
Sévérité : Medium Statut : ✅ Résolu le 2026-10-26 (#253, !472), prouvé au montage du même jour
Contexte : montage du 2026-10-15 (pipeline 2950601050), promotion de deployed-b15a0c5c en prod (!460). Premier passage du job e2e-prod (#234, job 17200568991).
Fichiers : k8s/base/deployment.yaml (aucun lifecycle.preStop), scripts/e2e-wait-dev.sh (s'arrête au premier /version correct)
Symptôme : e2e-prod attend que la prod serve le nouveau SHA (225 s), puis 3 tests sur 5 prennent un 503 :
upstream connect error or disconnect/reset before headers. reset reason: remote connection
failure, transport failure reason: delayed connect error: Connection refused
Une minute plus tard, la prod répond normalement.
Diagnostic : log d'accès Envoy dans Loki pour les 3 requêtes : response_flags: UF, upstream_host: 10.42.1.253:8080, Connection_refused. Cette IP a servi 39 réponses 200 depuis 11:48 : c'est l'ancien pod mc8zs. Les nouveaux pods sont en 10.42.1.120 et 10.42.1.35.
| Événement | Heure (UTC) |
|---|---|
/version répond le nouveau SHA, e2e-wait conclut |
~12:11:50 |
Killing de l'ancien pod mc8zs |
12:12:02 |
3 × 503 UF vers 10.42.1.253 |
12:12:03 |
Cause : deux défauts. (1) Au SIGTERM, uvicorn ferme son port tout de suite, alors que le retrait de l'endpoint met environ une seconde à atteindre Envoy. Sans preStop, rien ne couvre ce délai. (2) e2e-wait conclut dès qu'une réponse porte le bon SHA, pendant que le rollout continue.
Validation : reproduit en dev le même jour. kubectl rollout restart sous une boucle de requêtes : 2 × 503 sur 451, fenêtre d'environ 250 ms, même motif UF sur l'IP de l'ancien pod, au moment de son Killing.
Correctif (#253) : un correctif pour chacun des deux défauts.
lifecycle.preStop.sleepde 5 s sur le conteneurfastapi(action native, sans binairesleepdans l'image). Kubernetes joue la pause avant le SIGTERM : le pod sert encore pendant qu'Envoy le retire. Les 5 s restent loin des 30 s determinationGracePeriodSeconds. Elles ne touchent pas au couplage 3 s Envoy / 5 s uvicorn (backendtrafficpolicy-idle.yaml), qui porte sur les connexions inactives.e2e-wait-dev.shexige ensuite que toutes les lectures de/versionportent le bon SHA pendant 30 s d'affilée (STABLE, lecture toutes les 2 s). Un ancien SHA relance la fenêtre.
Validé hors infra : kube-linter v0.8.3, kustomize build des trois overlays, kubeconform -strict, puis le script contre un faux /version qui mélange ancien et nouveau SHA. Il conclut 6 s après la fin du mélange, et échoue au délai si le mélange ne finit jamais.
Preuve au montage du 2026-10-26 : même protocole qu'au 2026-10-15 sur fastapi-dev (une requête toutes les ~0,2 s sur /posts/public, kubectl rollout restart au milieu). Sur 3 rollouts, 1 241 requêtes, 0 × 503, contre 2 × 503 sur 451 sans preStop. Puis e2e-prod vert sur la promotion de deployed-47359edf (!474, job 17473608325) : la prod sert le nouveau SHA après 135 s, le script attend 30 s de lectures stables, puis 5 tests PASSED.
Leçon. Le test a vu ce que verrait n'importe quel client pendant un déploiement. Un 503 ne dit pas sa cause : c'est response_flags qui sépare ce cas (UF, port fermé) du 503 sous charge (UC, INC-066/067). Méthode de lecture : Lire les logs.