Procédure complète d’installation d’un cluster Kubernetes à partir de serveurs
Debian Trixie (13), depuis la préparation OS jusqu’à la jonction des workers.
Préparation des serveurs
Préparation des disques pour le stockage distribué
Afin de préparer les disques pour le stockage distribué, lancez le script
suivant pour supprimer toutes les traces d’installation précédentes.
Image ISO : Debian Trixie (13)avec firmware non-libre (DVD officiel ou
netinst « with firmware »). Le firmware non-libre est obligatoire pour activer
les cartes réseau Broadcom BCM57416 ; il est intégré aux ISO officielles
depuis Debian 12.4.
Téléchargez l’image ISO de Debian Trixie (13) avec firmware.
Attachez l’image ISO au serveur (KVM iLO / clé USB).
Démarrez en mode expert : au menu de boot, choisir « Advanced options »
→ « Expert install ». Le mode expert est nécessaire sur ce matériel pour
pouvoir choisir manuellement l’interface câblée et saisir une IP statique
(cf. ci-dessous).
Procédure réseau en mode expert (IP statique sur Broadcom BCM57416)
Le BIOS énumère 4 ports 10 GbE — seul ens10f0np0 est câblé sur le réseau
cluster 10.0.0.0/22. Il n’y a pas de serveur DHCP sur ce réseau, donc
toute autoconfiguration échoue : il faut configurer l’IP manuellement.
Charger les composants d’installation (étape « Load installer components
from CD ») : laisser les défauts ; cocher éventuellement network-console si
tu veux poursuivre l’install via SSH.
Détecter le matériel réseau : si l’installateur signale « Firmware
manquant : bnxt/… », répondre Oui, charger le firmware. Avec l’ISO «
with firmware » il est trouvé sur le média lui-même. Le pilote noyau
bnxt_en est ensuite chargé automatiquement.
Si rien n’est détecté, basculer en console (Alt+F2) pour diagnostic :
Fenêtre de terminal
lspci-nn|grep-iethernet# doit lister le BCM57416 (vendor 14e4)
modprobebnxt_en# force le chargement du pilote
dmesg|tail-30# cherche les erreurs de firmware
Revenir à l’installateur (Alt+F1) et relancer « Détecter le matériel réseau
».
Choix de l’interface (l’installateur le demande en mode expert s’il y en
a plusieurs) → choisir ens10f0np0 (le port câblé ; les 3 autres ports
n’ont pas de lien).
Configuration automatique du réseau : le DHCP va échouer (~30 s de
timeout) — c’est attendu. Annuler dès qu’il propose un menu.
Au menu suivant, choisir « Configurer le réseau manuellement », puis
saisir :
Adresse IP : 10.0.0.11 (puis .12, .13, .14 pour les workers)
Masque : 255.255.252.0 (= /22)
Passerelle : la passerelle réelle du /22
DNS : ton résolveur
Nom de machine : cp1, puis node1/node2/node3
Domaine : vide
Si l’écran « manuel » n’apparaît jamais : soit le DHCP a abouti sur un autre
port (revenir au menu principal et relancer « Configurer le réseau »), soit
l’interface n’a pas été détectée (cf. firmware, étape 3).
Partitionnement du disque de démarrage
Ne concerne que le disque de démarrage (miroir NVMe HPE NS204i-p, ~447
GiB). Les 12 HDD SAS 5,5 TiB et le NVMe nvme1n1 2,9 TiB restent bruts, non
partitionnés : ils sont consommés directement par Ceph (voir
storage/ceph/cluster.yaml).
Ne jamais créer de partition dessus.
Le défaut d’usine (/home = 404 G, /var = 9 G) étouffe /var, qui héberge
containerd, les logs, /var/lib/rook et /var/lib/etcd. On repartitionne
donc le disque de boot. Tous les nœuds reçoivent le MÊME layout (recette
d’installation unique, moins d’erreurs manuelles ; un nœud peut être promu
control plane sans repartitionner — cf. évolution HA,
ADR 0002).
La seule LV dont l’usage diffère est lv_etcd, détaillée sous le tableau.
Control plane (cp1) : lv_etcdhéberge etcd (la base d’état du
cluster). Indispensable : isole les I/O d’etcd et le protège d’un /var
saturé par containerd/logs/rook.
Workers (node1-3) : etcd ne tourne pas sur les workers →
/var/lib/etcd y reste vide. La LV est donc inutilisée, mais on la
crée quand même (recette identique partout) : promotion HA future sans
repartitionnement, et moins d’erreurs à l’install. Les ~9 Go immobilisés
sont négligeables face aux 12 × 5,5 To Ceph. Variante acceptable si l’espace
est compté : ne pas créer lv_etcd sur un worker et réaffecter ses 10 Go à
lv_var — au prix de l’uniformité.
⚠️ Sur les nœuds control plane, lv_etcd est une LV ext4 fraîchement
formatée : elle contient un répertoire lost+found qui fait échouer le
préflight kubeadm init ([ERROR DirAvailable--var-lib-etcd]: not empty). Le
rôle k8s-initialization retire ce lost+foundavant l’init (uniquement
si la LV est vierge — jamais sur un etcd peuplé). Rien à faire à la main.
Procédure dans l’installateur Debian (partitionnement manuel) :
À l’étape « Partitionner les disques », choisir Manuel.
Sélectionner le disque de boot (~447 GiB) → créer une nouvelle table de
partitions vide (GPT).
Créer la partition EFI : 512 Mo, usage « Partition système EFI ».
L’installateur la monte automatiquement sur /boot/efi (il ne demande pas de
point de montage : c’est normal) et pose le bon type GPT. Aucun drapeau
d’amorçage à activer — le « bootable flag » est un concept MBR, inutile en
UEFI.
Créer /boot : 1 Go, ext4, point de montage /boot.
Créer une partition occupant tout l’espace restant, usage « volume
physique pour LVM ».
Entrer dans « Configurer le gestionnaire de volumes logiques (LVM) » :
créer le groupe de volumes cp1-vg sur ce volume physique ;
y créer les volumes logiques : root 40 Go, var 360 Go, etcd
10 Go ; laisser ~30 Go non alloués dans le VG.
De retour dans le partitionnement, formater et monter chaque LV :
root → / en ext4 ;
var → /var en ext4 ;
etcd → /var/lib/etcd en ext4 (l’installateur ordonne les montages
imbriqués automatiquement).
Ne pas créer de partition d’échange (swap).
« Terminer le partitionnement » et appliquer les changements.
/tmp est déjà monté en tmpfs par défaut sur Debian 13 (systemd ≥ 256
active tmp.mount d’office) — rien à faire en post-installation.
Workers node1-3 : appliquer exactement la même recette (mêmes LV,
mêmes tailles, VG nommé d’après le nœud — node1-vg…). lv_etcd y est créée
mais reste inutilisée (cf. encadré « lv_etcd selon le rôle du nœud »
ci-dessus). Ne pas la supprimer, sauf contrainte d’espace explicite.
Configuration réseau
Chaque nœud reçoit une IP statique sur le port 10 GbE actif ens10f0np0
(réseau 10.0.0.0/22). Le plus simple est le défaut Debian (ifupdown), le
fichier /etc/network/interfaces (sans extension) :
Nœud
IP
cp1
10.0.0.11/22
node1
10.0.0.12/22
node2
10.0.0.13/22
node3
10.0.0.14/22
Exemple pour cp1 :
auto ens10f0np0
iface ens10f0np0 inet static
address 10.0.0.11/22
gateway 10.0.0.1 # à adapter : passerelle réelle du /22
dns-nameservers 10.0.0.1 # à adapter : résolveur(s) DNS
Appliquer avec sudo systemctl restart networking (ou au redémarrage).
Cette configuration peut être saisie directement à l’étape « Configurer le
réseau » de l’installateur en choisissant Configuration manuelle ; y
renseigner aussi le nom de machine (cp1, node1, node2, node3) et
laisser le domaine vide. Vérifier la passerelle et le DNS réels du /22
(non documentés dans le dépôt).
Alternatives selon le gestionnaire réseau : systemd-networkd
(/etc/systemd/network/*.network) ou NetworkManager
(/etc/NetworkManager/system-connections/*.nmconnection).
Premier accès SSH
Le script
first-access.sh
automatise le strict minimum nécessaire pour qu’Ansible puisse ensuite
piloter les nœuds sans mot de passe, et ferme immédiatement la fenêtre
d’authentification par mot de passe :
dépôt de la clé publique de l’opérateur (ssh-copy-id) ;
règle sudo NOPASSWD pour debian ;
durcissement sshd via drop-in /etc/ssh/sshd_config.d/00-hardening.conf
(PasswordAuthentication no, PermitRootLogin no, AllowUsers debian,
MaxAuthTries 3, ClientAlive*).
Le reste du hardening (mises à jour automatiques, UFW, fail2ban, auditd,
postfix, expiration de mot de passe) est délégué à
server-security — cf.
section suivante.
Pré-requis : disposer d’une clé SSH locale.
Fenêtre de terminal
ssh-keygen-ted25519# si absent
bashbootstrap/first-access.sh# cibles par défaut : cp1 node1 node2 node3
La 1re passe demande deux fois le mot de passe par hôte (ssh-copy-id puis
sudo). Les runs suivants sont silencieux et idempotents.
Vérification : le mot de passe debian a-t-il été modifié depuis l’install ?
bootstrap/state.sh couche 1 compare la date du dernier passwd
(chage -l debian) à la date de création de /etc/machine-id (= premier boot
post-install). Si l’écart est ≤ 1 jour, l’utilisateur n’a jamais changé le
mot de passe d’installation → drift fail avec remédiation suggérée
(NEW_DEBIAN_PASSWORD=… bash bootstrap/first-access.sh <hôte>). Sinon, ok
avec le nombre de jours écoulés.
Durcissement de l’OS (bootstrap/security/)
Les rôles Ansible de durcissement complet sont fusionnés dans ce dépôt via
git subtree sous bootstrap/security/ :
unattended-upgrades, UFW, fail2ban, auditd, postfix (redirection des
mails système), gestion du compte admin (expiration mot de passe). Origine :
univ-lehavre/server-security (DOI
10.5281/zenodo.16983614).
Le sshd est durci uniquement par first-access.sh (drop-in
/etc/ssh/sshd_config.d/00-hardening.conf), de même que le dépôt de la clé
publique (ssh-copy-id). secure.yml ne re-touche plus le sshd ni les clés :
les anciens tags sshd/ssh-keys (doublon Ansible, avec un AllowUsers
variable risqué) ont été retirés. first-access.sh est la source unique.
Le playbook secure.yml est entièrement opt-in : sans --tags, il ne
touche à rien (il charge juste les variables). Voir
bootstrap/security/IMPLICATIONS.md
pour, couche par couche, ce qui change, ce qui est protégé, le compromis assumé,
et la commande pour s’en convaincre soi-même.
Menu — chaque commande active une seule couche (et seulement celle-là).
nestor ansible résout le playbook (security/secure.yml, chemin relatif au
dépôt) et dérive l’inventaire de la topologie active — plus de -i :
Fenêtre de terminal
nestoransiblesecurity/secure.yml--tagsos# mises à jour auto + expiration mot de passe
nestoransiblesecurity/secure.yml--tagsalert# postfix + redirection mail root
Voir ce qui est en place — tableau de bord agrégé par hôte :
Fenêtre de terminal
bashbootstrap/security/report.sh# tous les hôtes
bashbootstrap/security/report.shcp1# un hôte
Le rapport affiche les preuves observables : services actifs/inactifs/absents,
sortie de sshd -T, dernier unattended-upgrades.log, alias root, IPs bannies
par fail2ban, règles auditd chargées, état UFW, expiration mot de passe. Lecture
seule, ne modifie rien.
⚠️ UFW × Kubernetes : le rôle network/ufw.yml durcit le pare-feu avec un
jeu de règles complet K8s/Cilium/Ceph (audit P6 #24). Plutôt qu’énumérer les
~30 ports inter-nœuds, il autorise tout le trafic entre nœuds du cluster
(plage CLUSTER_CIDR, défaut 10.0.0.0/22) — ce qui couvre API server, etcd,
kubelet, VXLAN Cilium et mon/osd Ceph sans risque d’oubli — puis restreint les
accès externes : SSH limité à SSH_ADMIN_CIDR (défaut = réseau cluster)
et plage NodePort30000-32767/tcp ouverte pour les services exposés.
À n’activer qu’APRÈS le bootstrap K8s (secure.yml --tags ufw) : activer
UFW avant que le cluster existe couperait l’init. L’état d’UFW est surveillé
par
state.sh
(couche 2) — un UFW actif sans la règle inter-nœuds, ou installé mais inactif,
est signalé comme drift.
La désactivation du swap n’apparaît plus ici : elle est gérée automatiquement
par le rôle Ansible k8s-pre-install du présent dépôt (checks.yaml).
Installation de k8s
CNI
L’inventaire n’a plus d’existence persistante : il est dérivé de la
topologie active à chaque commande nestor ansible (source unique
d’inventaire,
ADR 0098)
— fini le bootstrap/hosts.yaml à créer/éditer. L’opérateur déclare ses nœuds
dans une topology.yaml locale (config de déploiement gitignorée,
ADR 0023) : copier
un modèle générique versionné du catalogue (p. ex.
topologies/socle.example.yaml,
le profil prod générique) puis renseigner ses noms/IP réels.
Fenêtre de terminal
# activer une topologie (symlink gitignoré) à partir d'un modèle du catalogue
cptopologies/socle.example.yamltopologies/prod.yaml# puis éditer noms/IP
ln-sftopologies/prod.yamltopology.yaml
nestor ansible <playbook> résout alors le playbook sous bootstrap/ et dérive
l’inventaire de cette topologie à la volée (temporaire éphémère, jamais un
fichier statique pointable — ADR 0098). Exécutez les playbooks Ansible pour
installer Kubernetes :
Fenêtre de terminal
nestoransibleos-upgrade.yaml
nestoransiblechecks.yaml
nestoransiblecri.yaml
nestoransiblekubeadm.yaml
nestoransiblecontrol-planes.yaml
nestoransibleinitialisation.yaml
Déplacez le fichier cni.sh sur le control plane (control1).
Fenêtre de terminal
scp./cni.shcontrol1:/home/debian
Exécutez le script sur le control node. Puis, une fois que les pods sont
disponibles, lancez la série de tests.
Cilium assure l’exposition réseau du cluster, en remplacement de MetalLB et
d’ingress-nginx
(ADR 0020).
cni.sh
arme désormais, à l’install et à l’upgrade :
k8sServicePort=6443 (obligatoires sans kube-proxy : l’agent ne joint plus
l’API via la ClusterIP kubernetes.default). En 1.19 le flag est un booléen
et active déjà NodePort/HostPort/ExternalIPs.
Ces features restent disponibles côté Cilium, mais les UI ne sont plus
exposées par le Gateway L7 : depuis
l’ADR 0092, l’accès aux
UI passe en L4 (NodePort/hostPort sur l’IP du nœud,
http://<IP-nœud>:<port> — zéro DNS, zéro LB-IPAM). Le dossier de CRs
platform/cilium-expo/ (GatewayClass/pools/policies) et les gateway.yaml de
brique ont été retirés avec cette bascule ; le GatewayClass cilium et les
CRD éventuelles restent inoffensifs.
Retrait de kube-proxy : sur un cluster neuf, kubeadm init ne le déploie
plus (skipPhases: [addon/kube-proxy] dans la config kubeadm). Sur un cluster
existant, cni.sh le retire après avoir vérifié
KubeProxyReplacement: True, puis purge les règles iptables KUBE-* (à répéter
sur chaque nœud : iptables-save | grep -v KUBE | iptables-restore, en root).
Valider sur le banc multi-nœuds avant la prod : pas de régression de
service-routing après la bascule eBPF (Ceph HEALTH_OK, CoreDNS, ClusterIP
applicatifs), attribution d’IP du pool, annonce ARP, failover L2. La plage prod
du pool LB-IPAM reste TODO (arbitrage admin réseau, ADR 0020).
Join workers to cluster
Fenêtre de terminal
nestoransiblejoin-workers.yaml
Installation de la connexion en local
Récupérer le kubeconfig depuis le control plane. Le cluster est nommé
cluster-prod (champ clusterName, ADR 0053) → son contexte est
kubernetes-admin@cluster-prod, distinct de l’homonyme kubeadm par défaut.
Ne pas écraser~/.kube/config : le fusionner pour cohabiter avec
d’autres clusters (un banc Lima cluster-banc, par ex.) sans collision.
⚠️ NE PAS laisser la prod en contexte par défaut (current-context) de
~/.kube/config — c’est un pistolet chargé (ADR 0053). Dans un terminal frais
SANS KUBECONFIG exporté, un kubectl nu vise le contexte courant : si c’est
cluster-prod, tu MUTES la prod par accident (vécu : kubectl get pods -A
après un terminal neuf tombait sur la prod). L’outil nestor ne pose
KUBECONFIG que pour SES commandes (env/stack select) ; il ne touche
jamais ~/.kube/config (ta config perso). Donc la cible de kubectl nu est
TON affaire :
poser un contexte INOFFENSIF par défaut (banc, ou un contexte vide) :
kubectl config use-context cluster-banc (ou default) ;
viser la prod TOUJOURS explicitement :
kubectl --context kubernetes-admin@cluster-prod get … ;
ou, plus simple, nestor kubectl <args…> : il vise la cible SÛRE de la
stack active (banc ou prod selon la sélection), jamais le contexte par
défaut.
nestor et la lecture de la prod (ADR 0090)
🔭 nestor sait LIRE la prod (ADR 0090). Depuis l’ADR 0090, une topologie
prod peut DÉCLARER sa cible : un champ
kubeconfig: ~/.kube/<stack>.config (HORS dépôt, credentials jamais
commités — un fichier dédié par stack). Alors nestor preview lit l’état RÉEL
du cluster K8s (nœuds Ready + couches déjà saines via kubectl) au lieu des
VMs Lima — il ne dit plus « 10 couches à installer » sur une prod existante.
Convention de rapatriement (un fichier par stack) :
Fenêtre de terminal
# un kubeconfig dédié par stack, hors du dépôt (ADR 0090)
# puis déclarer `kubeconfig: ~/.kube/dirqual.config` dans la topo (gitignorée)
Si le kubeconfig: déclaré est absent/injoignable, nestor previewpropose
de le rapatrier depuis le control-plane (réutilise discover). nestor
reste lecture seule sur la prod : il ne la MONTE pas — l’installation prod
passe par les playbooks Ansible ci-dessus.
Cluster prod déjà installé sans clusterName (contexte homonyme
kubernetes-admin@kubernetes) ? Rejouer bootstrap/initialisation.yaml n’est
pas nécessaire : renommer le contexte en place
(kubectl config rename-context kubernetes-admin@kubernetes kubernetes-admin@cluster-prod)
avant la fusion.
Indiquez dans le contexte l’adresse IP du control plane (control1). Ensuite,
vous pouvez vérifier que tout fonctionne correctement en exécutant les commandes
suivantes :
Fenêtre de terminal
kgetnodes
kgetpods--all-namespaces
Accès distant aux services du cluster
L’accès distant aux services internes (dashboards, consoles, API d’opérateurs)
passe par kubectl port-forward depuis un poste autorisé à joindre l’API
Kubernetes — il n’y a pas de réseau d’overlay ni de VPN dédié
(ADR 0003).
Le poste de contrôle dispose déjà du kubeconfig fusionné (section précédente) ;
le tunnel est local au poste et ne traverse que l’API server.
Fenêtre de terminal
# Exposer localement un service ClusterIP (ex. un dashboard) :
Format : timestamp UTC ISO-8601, nom du playbook, identité de l’opérateur sur le
poste de contrôle ($USER@hostname), nom du compte SSH côté serveur (debian).
⚠️ Ce n’est pas une preuve de non-répudiation (audit P6). L’identité
opérateur ($USER@hostname) provient de variables d’environnement côté
contrôle — falsifiables par celui qui lance le playbook. Ce journal est un
outil d’exploitation (« quel playbook a tourné quand »), pas une preuve
opposable. Pour la traçabilité forte, s’appuyer sur ce qui est journalisé
côté serveur et non falsifiable par l’opérateur : sshd en
LogLevel VERBOSE (déjà posé par le rôle sshd — empreinte de clé publique
à chaque connexion) et auditd (couche --tags audit — syscalls
privilégiés). Corréler les deux donne « quelle clé, quand, quels actes ».
le dernier playbook joué + son âge → suivi rapide.
l’absence totale de trace → drift potentiel : OS installé mais aucun bootstrap
appliqué, ou rotation/effacement du log.
Initialiser le journal sur des nœuds existants (baseline)
Si les nœuds ont été installés/durcis manuellement avant l’introduction du
rôle audit-log, le fichier /var/log/cluster-bootstrap.log n’existe pas →
state.sh signale un drift sur la couche 0. Pour matérialiser « état initial
reconnu après le fait » sans rejouer tout le bootstrap :
Fenêtre de terminal
nestoransibleaudit-log-baseline.yaml
audit-log-baseline.yaml
appose une ligne « baseline » et débloque la couche 0. Les playbooks suivants
ajouteront des lignes normales par-dessus.
Rollback du bootstrap K8s
rollback.yaml
ramène un nœud à un état “Debian 13 + utilisateur debian + first-access” — comme
si aucun playbook K8s n’avait été joué :
Fenêtre de terminal
# Sur le banc Lima, l'inventaire est généré par run-phases.sh dans son WORKDIR
# (bench/lima/.work/inventory.yaml). On rejoue le rollback contre cet inventaire :
Au banc, préférer le chemin nommé codé plutôt qu’un enchaînement manuel
(ADR 0045) : BANC_JETABLE=1 bench/lima/run-phases.sh rollback <phase> défait
une phase pour la re-tester (ADR 0054). La commande ci-dessus reste utile pour
un rollback complet du bootstrap (hors phases plateforme).
Ce que le rollback fait :
kubeadm reset --force
stoppe + désactive kubelet et containerd
apt purge des paquets K8s et containerd.io, autoremove
supprime /etc/kubernetes, /etc/cni, /etc/containerd,
/var/lib/{kubelet,etcd,cni}, /opt/cni, les drop-ins modules-load.d et
sysctl.d posés par le bootstrap, et les dépôts APT correspondants
Mise à jour de Kubernetes (upgrade in-place — ADR 0015)
Montée de version K8s in-place, séquencée (control plane d’abord, puis
workers un par un). Une mineure à la fois ; vérifier la compat croisée
Cilium/Rook/Ceph
(ADR 0006)
avant, et valider sur le banc multi-node d’abord.
Fenêtre de terminal
# Patch (1.34.x → 1.34.y) :
nestoransiblek8s-upgrade.yaml\
-ek8s_upgrade_version=1.34.9
# Mineure (1.34 → 1.35) : bascule aussi le dépôt apt vers la mineure cible.
Le playbook draine chaque nœud avant son upgrade et le uncordon ensuite ; un
seul nœud est indisponible à la fois. L’API est brièvement coupée pendant
l’apply sur le control plane (SPOF assumé,
ADR 0002).
Détails et compromis :
ADR 0015.
Sauvegarde etcd (SPOF assumé)
Le cluster fonctionne avec 1 seul control plane (cp1) — décision assumée
(cf.
ADR 0002).
C’est un SPOF : la perte du nœud control plane → cluster inutilisable
jusqu’à restauration. Mitigations :
--control-plane-endpoint cluster-api:6443 posé dès kubeadm init (rôle
k8s-initialization) : l’API est référencée par un nom DNS stable, donc un
futur ajout de control planes n’imposera pas de réinstaller les workers.
Pose un script /usr/local/sbin/etcd-snapshot.sh + un timer systemd
etcd-snapshot.timer qui exécute etcdctl snapshot save chaque heure et
garde les 24 derniers snapshots dans /var/lib/etcd-backups/. Vérifier :
Fenêtre de terminal
systemctlstatusetcd-snapshot.timer
systemctllist-timersetcd-snapshot.timer
ls-la/var/lib/etcd-backups/
Copie hors-nœud via
etcd-fetch.yaml
(audit P1 #3) : les snapshots du point 2 restent sur le control plane →
perdus si cp1 meurt. Ce playbook rapatrie le snapshot le plus récent vers
le poste de contrôle (dossier etcd-snapshots/, gitignoré) :
Fenêtre de terminal
nestoransibleetcd-fetch.yaml
RPO = fréquence de ce fetch. Recommandé : le planifier côté admin (cron /
launchd), p. ex. toutes les 6 h → RPO ≤ 6 h hors-nœud (et ≤ 1 h sur le nœud
via le timer horaire). ⚠️ Le snapshot contient tous les Secrets (etcd non
chiffré, ADR 0014)
: garder etcd-snapshots/ sur un poste de confiance.
Restauration etcd (procédure)
Sur le control plane à restaurer (ou un nouveau nœud qui va prendre sa place),
avec un snapshot copié dans /tmp/etcd-snapshot.db :
Fenêtre de terminal
# 1. Arrêter le kubelet et tous les conteneurs (sinon etcd ne se laisse
# pas remplacer "sous lui").
sudosystemctlstopkubelet
sudocrictlps-q|xargs-rsudocrictlstop
sudocrictlps-q|xargs-rsudocrictlrm
# 2. Restaurer le snapshot vers un nouveau data-dir.
# 4. Redémarrer kubelet → l'API + etcd remontent sur le snapshot.
sudosystemctlstartkubelet
kubectlgetnodes# attendre que les nœuds redeviennent Ready
Tester cette procédure sur le banc Lima multi-nœuds avant d’en avoir
besoin en prod (cf. bench/lima/).
Rotation de la clé de chiffrement etcd (ADR 0014)
Les Secrets sont chiffrés at-rest dans etcd (provider secretbox, clé dans
/etc/kubernetes/enc/key1.b64, posée au bootstrap par k8s-initialization).
Faire tourner la clé sur événement (suspicion de compromission, départ d’un
admin) ou échéance (ex. annuelle). La rotation est manuelle (pas de KMS —
choix ADR 0003) ; elle ne perd aucune donnée si l’ordre est respecté.
⚠️ Ne jamais se contenter de remplacer la clé : tant qu’un Secret reste
chiffré avec l’ancienne, la retirer le rend illisible. L’étape de réécriture
(3) est obligatoire.
Fenêtre de terminal
ENC=/etc/kubernetes/enc/encryption-config.yaml
# 1. Générer une nouvelle clé et l'ajouter EN TÊTE (key2 chiffre ; key1 reste
# pour déchiffrer l'existant). Éditer $ENC :
# providers:
# - secretbox:
# keys:
# - name: key2
# secret: <openssl rand -base64 32>
# - name: key1
# secret: <ancienne valeur>
# - identity: {}
# 2. Redémarrer l'API server (static pod) → il connaît key2 + key1.
Vérifier qu’un Secret est bien chiffré après chaque étape (k8s:enc:secretbox:)
via etcdctl — c’est ce que fait le scénario
bench/scenarios/15-etcd-encryption-audit.sh
(ROTATE=1 déroule et vérifie toute la rotation, témoin inclus). Tester sur
le banc avant la prod.
Vérifier une release signée (cosign / SLSA — ADR 0088)
Si vous installez à partir d’une version figée (et non du HEAD), chaque
release publie une archive source signée — vérifiez son intégrité et sa
provenance avant usage. Les assets de la release (onglet Releases) :
cluster-<TAG>.tar.gz, sa signature .sig + son certificat .pem (cosign), et
l’attestation de provenance *.intoto.jsonl (SLSA). Signature keyless :
aucune clé publique à récupérer, l’identité est le workflow GitHub lui-même.
Une vérification qui échoue (signature invalide, identité inattendue, source
divergente) = archive non fiable : ne pas l’utiliser. La signature ne vit
que dans le code versionné
(.github/workflows/release.yml),
re-prouvée à chaque release — pas d’étape manuelle (ADR 0088).