From e32faa3c7793e2d229575ff19ba66d42d5ffd9d0 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Fri, 11 Sep 2026 14:51:24 +0200 Subject: [PATCH] =?UTF-8?q?Vault=20est=20rest=C3=A9=20scell=C3=A9=20onze?= =?UTF-8?q?=20jours=20en=20silence,=20et=20c'=C3=A9tait=20le=20pod=20le=20?= =?UTF-8?q?plus=20faible=20du=20cluster=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gabriel Radureau --- hashicorp-vault/templates/priorityclass.yaml | 33 +++++++++++ .../templates/vault-server-pdb.yaml | 11 ++++ hashicorp-vault/values.yaml | 30 ++++++++++ prometheus/values.yaml | 55 +++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 hashicorp-vault/templates/priorityclass.yaml diff --git a/hashicorp-vault/templates/priorityclass.yaml b/hashicorp-vault/templates/priorityclass.yaml new file mode 100644 index 0000000..1401db1 --- /dev/null +++ b/hashicorp-vault/templates/priorityclass.yaml @@ -0,0 +1,33 @@ +{{/* + F—: VAULT NE DOIT PLUS ÊTRE LE POD LE PLUS FAIBLE DU CLUSTER. + + Constat du 2026-09-11, mesuré : `priorityClassName=""`, `priority=0`, QoS `BestEffort`. + Autrement dit Vault était littéralement dernier servi et premier évincé, alors que le + Vault Secrets Operator en dépend pour TOUS les Secrets du cluster. Quand il tombe, il + repart scellé (Shamir 1/1, pas d'auto-unseal — décision assumée) et plus rien ne se + réconcilie jusqu'à ce qu'un humain vienne le desceller. + + POURQUOI 900000000. Il existe déjà `longhorn-critical` à 1000000000. Vault stocke ses + données sur un PVC : si Longhorn tombe, Vault ne repart pas. Le stockage doit donc rester + AU-DESSUS. En dessous de Vault, tout le reste — applications comprises. + + CE QUE ÇA ACHÈTE, ET CE QUE ÇA N'ACHÈTE PAS. La priorité joue sur la préemption par le + scheduler et sert de critère de tri à l'éviction par le kubelet sous pression. Elle + n'empêche PAS la suppression décidée par le node-lifecycle controller quand un nœud + devient injoignable — c'est exactement ce qui s'est produit le 30/08 et aucun réglage de + pod ne l'évite. Ce qui traite ce cas-là, c'est l'alerte VaultIndisponible. +*/}} +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: vault-critical + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/part-of: hashicorp-vault +value: 900000000 +globalDefault: false +preemptionPolicy: PreemptLowerPriority +description: >- + Vault et son opérateur : tout le cluster en dépend pour ses Secrets, et un Vault tombé + repart scellé, donc muet jusqu'à intervention humaine. Sous longhorn-critical (le + stockage doit survivre à Vault), au-dessus de tout le reste. diff --git a/hashicorp-vault/templates/vault-server-pdb.yaml b/hashicorp-vault/templates/vault-server-pdb.yaml index 827304a..96c889a 100644 --- a/hashicorp-vault/templates/vault-server-pdb.yaml +++ b/hashicorp-vault/templates/vault-server-pdb.yaml @@ -8,6 +8,17 @@ # d'une panne de nœud ni d'une suppression manuelle. Avec un seul réplica et # aucun auto-unseal, toute interruption exige un unseal humain ensuite ; ce PDB # réduit seulement les interruptions évitables. +# +# ⚠ CONSÉQUENCE À CONNAÎTRE AVANT LA PROCHAINE MAINTENANCE D'UN NŒUD. +# `maxUnavailable: 0` sur un StatefulSet à UN réplica donne `ALLOWED DISRUPTIONS: 0` +# en permanence — vérifié : `kubectl get pdb -n tools` le montre depuis 20 jours. +# Un `kubectl drain ` sur le nœud qui héberge Vault ne se terminera donc JAMAIS, +# il bouclera en silence sur « cannot evict pod as it would violate the budget ». +# C'est le comportement voulu — il force à traiter Vault consciemment — mais il faut +# savoir en sortir : +# kubectl drain --disable-eviction # contourne le PDB (supprime le pod) +# ou kubectl delete pod -n tools hashicorp-vault-0 # puis descellement à la main +# Dans les deux cas Vault repart scellé : avoir la clé sous la main AVANT de drainer. apiVersion: policy/v1 kind: PodDisruptionBudget metadata: diff --git a/hashicorp-vault/values.yaml b/hashicorp-vault/values.yaml index 4442154..6ee3247 100644 --- a/hashicorp-vault/values.yaml +++ b/hashicorp-vault/values.yaml @@ -6,6 +6,36 @@ vault: &vault_config server: enabled: true logLevel: trace + # ---- NE PLUS ÊTRE LE POD LE PLUS FAIBLE DU CLUSTER ----------------------------- + # Mesuré le 2026-09-11, avant ce changement : `priorityClassName=""`, `priority=0`, + # QoS `BestEffort`. Vault était donc dernier servi par le scheduler et PREMIER évincé + # par le kubelet sous pression mémoire — alors que le Vault Secrets Operator en dépend + # pour tous les Secrets du cluster, et qu'un Vault tombé repart SCELLÉ, donc muet + # jusqu'à ce qu'un humain vienne le desceller à la main. + # + # Contexte : 78 pods du cluster sont BestEffort contre 35 Burstable et 1 Guaranteed. + # Vault était noyé dans le premier groupe. + priorityClassName: vault-critical # cf. templates/priorityclass.yaml + resources: + # DES REQUESTS, ET DÉLIBÉRÉMENT AUCUNE LIMITE. + # + # Ce sont les `requests` qui protègent : le kubelet évince d'abord les BestEffort, + # puis les Burstable qui DÉPASSENT leur requête, et en dernier ceux qui restent + # dessous. Vault consomme ~175 Mio (kubectl top, 11/09) : avec 512 Mio demandés il + # est très en dessous, donc tout en bas de la liste des candidats à l'éviction. + # + # Pourquoi pas de `limits.memory`, qui donnerait du Guaranteed : une limite trop + # basse déclenche un OOMKill, et un OOMKill sur CE pod coûte un descellement manuel. + # Or je n'ai pas pu établir de pic de mémoire fiable — les séries cAdvisor de ce + # cluster rendent huit valeurs contradictoires pour ce pod (jusqu'à 2,3 Gio, ce qui + # est invraisemblable pour un Vault en storage "file"). On ne pose pas une limite sur + # un chiffre auquel on ne croit pas : le gain marginal ne vaut pas le risque. + # + # Pourquoi pas de `limits.cpu` : le throttling CPU sur un ARM64 à 4 cœurs ralentirait + # les opérations de descellement et de scellement, pour un pod qui consomme 26 m. + requests: + memory: 512Mi + cpu: 100m auditStorage: enabled: true diff --git a/prometheus/values.yaml b/prometheus/values.yaml index 05f2f47..0f85ba7 100644 --- a/prometheus/values.yaml +++ b/prometheus/values.yaml @@ -1265,6 +1265,61 @@ prometheus: &prometheus_config annotations: summary: "Homelab — cert {{ $labels.namespace }}/{{ $labels.name }} expire dans {{ $value | humanizeDuration }}" description: "Le renouvellement automatique (cert-manager → step-issuer → step-ca) est en échec. Vérifier : kubectl get certificaterequest -A, logs step-issuer (résolution DNS de ssl-ca.arcodange.lab), santé de step-ca sur pi1:8443." + # ---- VAULT SCELLÉ ------------------------------------------------------ + # Incident 2026-08-30 → 2026-09-10 : Vault est resté SCELLÉ ONZE JOURS. + # Le VSO ne pouvait plus s'authentifier (503 « Vault is sealed »), donc les + # Secrets de plusieurs applications ont cessé d'être réconciliés — en silence. + # Découvert par hasard, en enquêtant sur tout autre chose. + # + # CE QUI A MARCHÉ CE JOUR-LÀ : `NoeudInjoignable` a bien tiré à 11:46 quand pi3 + # a cessé de répondre. L'infra a parlé. CE QUI A MANQUÉ : la CONSÉQUENCE. Le + # nœud est revenu, les pods ont été recréés, l'alerte s'est éteinte — et + # personne n'a su que Vault, lui, était reparti scellé et le resterait. + # + # POURQUOI `kube_pod_status_ready` ET PAS `vault_core_unsealed` : Vault n'expose + # ses métriques que via /v1/sys/metrics, qui exige soit un token, soit + # `unauthenticated_metrics_access = true` dans la stanza telemetry. Le second + # ouvre un endpoint non authentifié pour gagner… le même signal. Or la + # readinessProbe du chart est littéralement `vault status` : un Vault scellé est + # NotReady, point. kube-state-metrics est déjà scrapé, ça ne coûte rien, et ça + # ne touche pas à la configuration de Vault. + # + # ⚠ Cette alerte ne distingue pas « scellé » de « en train de démarrer » ou + # « planté ». C'est VOULU : dans les trois cas Vault ne sert plus de secrets, et + # le geste de l'opérateur commence pareil — aller voir `vault status`. + - alert: VaultIndisponible + # ⚠ LE SÉLECTEUR EST ANCRÉ SUR `[0-9]+`, ET CE N'EST PAS COSMÉTIQUE : un + # `hashicorp-vault-.*` attrape aussi les pods du Vault Secrets Operator + # (`hashicorp-vault-vault-secrets-operator-controller-manager-xxxxx`), dont un + # exemplaire terminé traîne en permanence — la règle aurait donc tiré sans + # arrêt, et une alerte qui crie tout le temps ne se lit plus. Vérifié contre le + # Prometheus du cluster : `[0-9]+` ne rend que hashicorp-vault-0. Toute + # évolution de ce sélecteur se re-teste de la même façon, sur le vrai serveur. + # + # `absent()` : si le pod disparaît (évincé, nœud perdu), la série n'existe plus + # et une simple comparaison `== 0` serait muette — exactement le mode de panne + # qu'on essaie de couvrir. Même motif que IngressLabIndisponible ci-dessus. + expr: >- + kube_pod_status_ready{namespace="tools", pod=~"hashicorp-vault-[0-9]+", condition="true"} == 0 + or absent(kube_pod_status_ready{namespace="tools", pod=~"hashicorp-vault-[0-9]+", condition="true"}) + # 10 min : large devant un redémarrage normal (le pod est Ready en ~30 s), + # court devant onze jours. + for: 10m + labels: + severity: critical + app: homelab + annotations: + summary: "Homelab — Vault ne sert plus de secrets (probablement scellé)" + description: >- + hashicorp-vault n'est plus Ready depuis 10 min. Le cas le plus fréquent est + un Vault SCELLÉ : il repart toujours scellé après recréation du pod (Shamir + 1/1, pas d'auto-unseal — c'est une décision assumée, cf. factory + vibe/guidebooks/lab-ecosystem/secrets-and-vault.md). Tant qu'il l'est, le + Vault Secrets Operator ne réconcilie plus AUCUN Secret : les applications + tournent sur leurs Secrets existants et cassent dès qu'un pod redémarre. + Vérifier - kubectl -n tools exec hashicorp-vault-0 -- vault status. + Desceller - le rôle ansible arcodange.factory/hashicorp_vault, tâche + unseal.yml (clé dans ~/.arcodange/cluster-keys.json sur le poste). # Le veilleur (arcodange-org/tools#36). Du 2026-09-04 02:41 au # 2026-09-07 07:37, Prometheus n'a rien enregistré et les 11 règles # ci-dessus se sont TUES — sans échantillon frais, une règle ne se