diff --git a/admin/install/kubernetes.md b/admin/install/kubernetes.md index 8f49ba2..49bac88 100644 --- a/admin/install/kubernetes.md +++ b/admin/install/kubernetes.md @@ -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 `.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-/`. + +#### 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 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 -n + +# 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 -n +# → 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 - < -n + +# 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 +ssh storage-01 "sudo rm -rf /srv/data/nfs/k8s/" +``` + +#### 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 -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 |