Vault est resté scellé onze jours en silence, et c'était le pod le plus faible du cluster #46

Merged
arcodange merged 1 commits from arcodange/vault-resilience into main 2026-09-11 14:51:29 +02:00
4 changed files with 129 additions and 0 deletions
@@ -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.
@@ -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 <nœud>` 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 <nœud> --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:
+30
View File
@@ -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
+55
View File
@@ -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