docs: kubernetes — section NFS provisioner complète (install, usage, admin, pièges)

Ajout section 16 (NFS provisioner) dans le guide d'installation k8s :
- Architecture du provisionnement dynamique (StorageClass → NFS RAID5)
- Prérequis storage-01, fichiers de config, pattern App-of-Apps
- Utilisation dans les pods (PVC, Deployment, Grafana, Prometheus)
- Section admin : diagnostics, PVC manuels, cleanup, re-sync ArgoCD

Ajout dans section admin courante : commandes de gestion NFS, tableau
diagnostics PVC Pending, procédure test PVC manuel.

Ajout dans pièges : StorageClass not found, SharedResourceWarning (boucle
nom ArgoCD), StatefulSet volumeClaimTemplate immutable, PodSecurity baseline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alkatrazz 2026-05-13 22:10:26 +02:00
parent f42c6c17dc
commit 8b6f906a89

View file

@ -21,9 +21,10 @@ Guide d'installation de A à Z — écrit après avoir effectué l'installation
13. [Installer MetalLB](#13-installer-metalLB)
14. [Installer Traefik](#14-installer-traefik)
15. [Configurer le DNS wildcard *.lab.local](#15-configurer-le-dns-wildcard-lablocal)
16. [Vérification finale](#16-vérification-finale)
17. [Administration courante](#17-administration-courante)
18. [Pièges et erreurs rencontrés](#18-pièges-et-erreurs-rencontrés)
16. [Stockage persistant — NFS provisioner](#16-stockage-persistant--nfs-provisioner)
17. [Vérification finale](#17-vérification-finale)
18. [Administration courante](#18-administration-courante)
19. [Pièges et erreurs rencontrés](#19-pièges-et-erreurs-rencontrés)
---
@ -679,7 +680,199 @@ Avec ça, n'importe quel `<service>.lab.local` sera résolu vers Traefik, qui se
---
## 16. Vérification finale
## 16. Stockage persistant — NFS provisioner
Sans stockage persistant, les pods comme Grafana ou Prometheus perdent toutes leurs données à chaque redémarrage. Le cluster Funk utilise le RAID5 de storage-01 comme backend via NFS.
### Architecture
```
Pod (Grafana, Prometheus, etc.)
└── PersistentVolumeClaim (PVC)
└── StorageClass "nfs"
└── nfs-subdir-external-provisioner (Deployment, namespace nfs-provisioner)
└── NFS export → /srv/data/nfs/k8s sur storage-01 (RAID5)
```
Quand un pod demande du stockage (`PersistentVolumeClaim`), le provisioner :
1. Reçoit la demande via l'API Kubernetes
2. Crée automatiquement un sous-répertoire dans `/srv/data/nfs/k8s/` sur storage-01
3. Crée un `PersistentVolume` pointant vers ce sous-répertoire
4. Bind le PV au PVC — le pod peut monter son volume
### Prérequis sur storage-01
Le répertoire NFS doit exister et être exporté **avant** de déployer le provisioner :
```bash
# Sur storage-01 — vérifier l'export
sudo exportfs -v | grep k8s
# → /srv/data/nfs/k8s 192.168.10.0/24(rw,sync,no_subtree_check,no_root_squash)
# Si le répertoire n'existe pas encore
sudo mkdir -p /srv/data/nfs/k8s
sudo chown nobody:nobody /srv/data/nfs/k8s
sudo chmod 0777 /srv/data/nfs/k8s
# Recharger les exports
sudo exportfs -ra
sudo systemctl status nfs-server
```
> `no_root_squash` est requis : le provisioner tourne en root dans le conteneur et doit pouvoir créer des sous-répertoires sur le partage.
### Fichiers de configuration
**`k8s/apps-of-apps/apps/nfs-provisioner.yaml`** — Application ArgoCD parente :
```yaml
spec:
source:
repoURL: git@github.com:Alkatrazz24/Funk-lab.git
path: k8s/infra/nfs-provisioner
```
**`k8s/infra/nfs-provisioner/helmrelease.yaml`** — Application ArgoCD enfant (multi-source) :
```yaml
spec:
sources:
- repoURL: https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
chart: nfs-subdir-external-provisioner
targetRevision: 4.0.18
helm:
valueFiles:
- $values/k8s/infra/nfs-provisioner/values.yaml
- repoURL: git@github.com:Alkatrazz24/Funk-lab.git
targetRevision: main
ref: values
```
**`k8s/infra/nfs-provisioner/values.yaml`** — paramètres Helm :
```yaml
nfs:
server: 192.168.10.1 # IP de storage-01
path: /srv/data/nfs/k8s # Répertoire exporté
storageClass:
name: nfs
defaultClass: true # Classe par défaut du cluster
reclaimPolicy: Retain # Conserver les données si le PVC est supprimé
archiveOnDelete: false # Ne pas archiver à la suppression
```
> `reclaimPolicy: Retain` signifie que si tu supprimes un PVC, le sous-répertoire NFS reste. Les données sont protégées. Il faut supprimer manuellement le répertoire si souhaité.
### Pattern App-of-Apps pour ce déploiement
```
root Application (watches apps-of-apps/apps/)
└── nfs-provisioner Application (watches k8s/infra/nfs-provisioner/)
└── nfs-subdir-external-provisioner Application (multi-source Helm)
├── Deployment nfs-subdir-external-provisioner (namespace nfs-provisioner)
└── StorageClass "nfs"
```
> **Important** : le `helmrelease.yaml` dans `k8s/infra/nfs-provisioner/` doit avoir un nom différent de son Application parente. Ici `nfs-subdir-external-provisioner``nfs-provisioner`. Un même nom créerait une boucle de gestion ArgoCD (SharedResourceWarning).
### Déploiement initial (déjà fait via ArgoCD)
```bash
# ArgoCD déploie automatiquement via Git. Pour forcer manuellement :
kubectl -n argocd annotate application nfs-provisioner \
argocd.argoproj.io/refresh=hard --overwrite
# Vérifier que tout est Synced
kubectl get applications -n argocd | grep nfs
# nfs-provisioner Synced Healthy
# nfs-subdir-external-provisioner Synced Healthy
```
### Utiliser le stockage NFS dans un pod
#### Créer un PVC
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mon-app-data
namespace: mon-namespace
spec:
storageClassName: nfs # Doit correspondre à la StorageClass
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
```
> `ReadWriteOnce` (RWO) : monté par un seul pod à la fois. Suffisant pour des bases de données, Prometheus, Grafana.
> `ReadWriteMany` (RWX) : monté par plusieurs pods simultanément. NFS le supporte — utile pour du contenu statique partagé.
#### Monter le PVC dans un Deployment
```yaml
spec:
template:
spec:
containers:
- name: mon-app
image: mon-image
volumeMounts:
- name: data
mountPath: /data # Chemin dans le conteneur
volumes:
- name: data
persistentVolumeClaim:
claimName: mon-app-data # Nom du PVC ci-dessus
```
#### Comment Grafana utilise le NFS
Grafana stocke dans son volume NFS :
- Sa base de données SQLite (`grafana.db`) — dashboards, utilisateurs, alertes, préférences
- Le cache des images PNG/PDF exportées depuis les dashboards
- Les plugins installés à chaud
Dans `k8s/infra/monitoring/values.yaml` :
```yaml
grafana:
persistence:
enabled: true
storageClassName: nfs
size: 2Gi
```
Le chart Grafana crée automatiquement un PVC nommé `kube-prometheus-stack-grafana`. Le provisioner NFS provisionne un répertoire dans `/srv/data/nfs/k8s/monitoring-kube-prometheus-stack-grafana-pvc-<uid>/`.
#### Comment Prometheus utilise le NFS
Prometheus utilise un `StatefulSet` avec un `volumeClaimTemplate` dans le CRD `Prometheus` :
```yaml
prometheus:
prometheusSpec:
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nfs
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
```
Le PVC créé s'appelle `prometheus-kube-prometheus-stack-prometheus-db-prometheus-kube-prometheus-stack-prometheus-0` et est lié à un répertoire dans `/srv/data/nfs/k8s/`.
> **Attention** : Les `volumeClaimTemplate` de StatefulSet ne peuvent **pas** être mis à jour une fois créés. Si tu changes la taille ou la storageClass, tu dois :
> 1. Patcher le CRD Prometheus pour retirer `storageSpec`
> 2. Supprimer le StatefulSet (sans `--cascade=foreground` pour ne pas tuer les pods)
> 3. Supprimer le PVC
> 4. Remettre `storageSpec` dans values.yaml
> 5. Git push → ArgoCD recrée tout
---
## 17. Vérification finale
```bash
# État des nœuds
@ -711,7 +904,7 @@ compute-03 Ready <none> 1h v1.33.1
---
## 17. Administration courante
## 18. Administration courante
### Variables d'environnement (à mettre dans ~/.bashrc)
@ -794,9 +987,124 @@ talhelper genconfig
talosctl kubeconfig --nodes 192.168.10.11 ~/.kube/config
```
### Administrer le stockage NFS
```bash
# État du provisioner
kubectl get pods -n nfs-provisioner
kubectl get deployment -n nfs-provisioner
# État de la StorageClass
kubectl get storageclass
# Tous les PVCs du cluster (toutes namespaces)
kubectl get pvc -A
# Tous les PVs (volumes physiques côté cluster)
kubectl get pv
# Détail d'un PVC (binding, capacité, storageClass)
kubectl describe pvc <nom-pvc> -n <namespace>
# Vérifier l'espace utilisé côté NFS (storage-01)
ssh storage-01 "du -sh /srv/data/nfs/k8s/*"
ssh storage-01 "df -h /srv/data"
# Logs du provisioner (utile si un PVC reste en Pending)
kubectl logs -n nfs-provisioner deployment/nfs-subdir-external-provisioner --tail=30
```
#### Pourquoi un PVC reste-t-il en Pending ?
Un PVC reste `Pending` quand le provisioner ne peut pas créer le volume. Causes courantes :
| Cause | Diagnostic | Fix |
|---|---|---|
| StorageClass `nfs` inexistante | `kubectl get sc` → vide | Forcer sync ArgoCD `nfs-provisioner` |
| Provisioner pod en erreur | `kubectl get pods -n nfs-provisioner` | Lire les logs, redéployer |
| storage-01 inaccessible | `ping 192.168.10.1` depuis un pod | Vérifier réseau/NFS |
| Répertoire NFS non exporté | `ssh storage-01 "exportfs -v"` | Relancer le playbook Ansible |
| Permissions insuffisantes | Logs provisioner : `permission denied` | Vérifier `no_root_squash` dans `/etc/exports` |
```bash
# Diagnostic complet pour un PVC bloqué
kubectl describe pvc <pvc-name> -n <namespace>
# → Section "Events" montre la raison du blocage
# Test de connectivité NFS depuis un node k8s
kubectl run nfs-test --image=busybox --rm -it --restart=Never -- \
mount -t nfs 192.168.10.1:/srv/data/nfs/k8s /mnt
```
#### Créer un PVC manuellement (hors ArgoCD)
```bash
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs
namespace: default
spec:
storageClassName: nfs
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
EOF
# Vérifier que le PVC passe en Bound
kubectl get pvc test-nfs -w
# Vérifier le répertoire créé sur storage-01
ssh storage-01 "ls /srv/data/nfs/k8s/ | grep test-nfs"
# Nettoyage
kubectl delete pvc test-nfs
# → Le répertoire NFS reste (reclaimPolicy: Retain) — supprimer manuellement si souhaité
```
#### Supprimer un PVC et ses données
```bash
# 1. Supprimer le PVC
kubectl delete pvc <pvc-name> -n <namespace>
# 2. Le PV passe en "Released" (pas automatiquement supprimé car reclaimPolicy=Retain)
kubectl get pv # chercher le PV en Released
# 3. Si tu veux libérer l'espace, supprimer aussi le PV et le répertoire NFS
kubectl delete pv <pv-name>
ssh storage-01 "sudo rm -rf /srv/data/nfs/k8s/<répertoire-correspondant>"
```
#### Forcer la re-synchronisation ArgoCD du provisioner
Le provisioner peut être purgé par ArgoCD si l'Application parente est OutOfSync. Pattern de récupération :
```bash
# 1. Forcer refresh root pour recréer les applications enfants
kubectl -n argocd annotate application root \
argocd.argoproj.io/refresh=hard --overwrite
# 2. Forcer sync nfs-provisioner spécifiquement
kubectl -n argocd annotate application nfs-provisioner \
argocd.argoproj.io/refresh=hard --overwrite
# 3. Attendre que les deux apps soient Synced/Healthy
kubectl get applications -n argocd | grep nfs
# nfs-provisioner Synced Healthy
# nfs-subdir-external-provisioner Synced Healthy
# 4. Vérifier la StorageClass et le pod
kubectl get sc && kubectl get pods -n nfs-provisioner
```
> Si des PVCs étaient en Pending pendant l'absence du provisioner, elles se resolveront automatiquement dès que la StorageClass et le provisioner sont de nouveau présents.
---
## 18. Pièges et erreurs rencontrés
## 19. Pièges et erreurs rencontrés
Tout ce qui a planté lors de l'installation réelle, pour ne pas y retomber.
@ -919,6 +1227,96 @@ dial tcp 10.42.2.2:9443: connect: connection refused
---
---
### ❌ PVC reste en Pending — StorageClass "nfs" not found
**Symptôme** :
```
Warning ProvisioningFailed persistentvolume-controller storageclass.storage.k8s.io "nfs" not found
```
**Cause** : L'Application ArgoCD `nfs-provisioner` (ou `nfs-subdir-external-provisioner`) a été purgée ou est OutOfSync. La StorageClass disparaît du cluster.
**Fix** :
```bash
kubectl -n argocd annotate application root \
argocd.argoproj.io/refresh=hard --overwrite
kubectl -n argocd annotate application nfs-provisioner \
argocd.argoproj.io/refresh=hard --overwrite
# Attendre ~60s, puis vérifier :
kubectl get sc && kubectl get pods -n nfs-provisioner
```
Les PVCs en Pending se resolvent automatiquement dès que la StorageClass est recréée.
---
### ❌ helmrelease.yaml — même nom que l'Application parente (SharedResourceWarning)
**Symptôme** :
```
Application/nfs-provisioner is part of applications argocd/nfs-provisioner and root
ComparisonError: Failed to load target state: permission denied
```
**Cause** : Le `helmrelease.yaml` dans `k8s/infra/nfs-provisioner/` était une Application ArgoCD nommée `nfs-provisioner` — même nom que l'Application parente qui le contient. ArgoCD essayait de gérer une ressource qui portait son propre nom → boucle.
**Fix** : Renommer l'Application dans `helmrelease.yaml` avec un nom différent (`nfs-subdir-external-provisioner`). Toujours suivre le pattern monitoring : l'Application enfant doit avoir un nom distinct de son parent.
---
### ❌ Prometheus StatefulSet bloqué — impossible de mettre à jour volumeClaimTemplate
**Symptôme** : ArgoCD signale l'Application `kube-prometheus-stack` en erreur après modification de `storageSpec` dans `values.yaml` :
```
cannot patch "prometheus-kube-prometheus-stack-prometheus" with kind StatefulSet:
StatefulSet.apps "..." is invalid: spec.volumeClaimTemplates: Forbidden: updates to statefulset
```
**Cause** : Kubernetes n'autorise pas la modification des `volumeClaimTemplates` d'un StatefulSet existant.
**Fix** :
```bash
# 1. Retirer storageSpec du CRD Prometheus pour que l'operator le détache
kubectl patch prometheus kube-prometheus-stack-prometheus -n monitoring \
--type=merge -p '{"spec":{"storage":null}}'
# 2. Supprimer le StatefulSet sans cascade (les pods continuent)
kubectl delete statefulset prometheus-kube-prometheus-stack-prometheus-0 \
-n monitoring --cascade=orphan
# 3. Supprimer le PVC
kubectl delete pvc <pvc-name> -n monitoring
# 4. Remettre storageSpec dans values.yaml → git push → ArgoCD recrée tout
```
---
### ❌ node-exporter bloqué par PodSecurity (baseline → privileged requis)
**Symptôme** : Les pods `node-exporter` restent en `Pending` ou sont rejetés :
```
pods "kube-prometheus-stack-node-exporter-xxx" is forbidden:
violates PodSecurity "baseline:latest": host namespaces (hostNetwork=true, hostPID=true),
hostPath volumes
```
**Cause** : Talos applique par défaut la politique `baseline:latest` sur les namespaces. `node-exporter` a besoin de `hostNetwork`, `hostPID`, et de montages `hostPath` pour lire les métriques système — ce qui est refusé par `baseline`.
**Fix** : Labeler le namespace `monitoring` en `privileged` dans `k8s/infra/monitoring/namespace.yaml` :
```yaml
metadata:
name: monitoring
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
```
---
### Résumé des points d'attention
| Sujet | Règle |
@ -933,3 +1331,6 @@ dial tcp 10.42.2.2:9443: connect: connection refused
| etcd backup | Critique sur single control-plane — cron hebdomadaire minimum |
| RAM workers | compute-02/03 = 8 GB — toujours mettre `resources.requests/limits` |
| clusterconfig/ | Généré par talhelper — **ne pas committer**, dans `.gitignore` |
| NFS helmrelease | Nom de l'Application enfant ≠ nom du parent ArgoCD |
| StatefulSet storage | `volumeClaimTemplate` immutable — delete StatefulSet + PVC pour changer |
| PodSecurity monitoring | Namespace `monitoring` doit être `privileged` pour node-exporter |