mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 19:54:43 +02:00
Ajout d'une section '## État vérifié' à chaque doc admin/ basée sur l'inspection read-only du cluster (systemctl, kubectl, nft, etc.). Aucune modification de service ni réécriture de documentation existante. Co-Authored-By: Hermes <hermes@funk.lab>
1343 lines
No EOL
44 KiB
Markdown
1343 lines
No EOL
44 KiB
Markdown
# Installation complète du cluster Kubernetes Funk
|
|
|
|
Guide d'installation de A à Z — écrit après avoir effectué l'installation réelle sur les 3 ThinkCentre. Toutes les erreurs rencontrées sont documentées dans la section [Pièges et erreurs](#pièges-et-erreurs-rencontrés) en bas.
|
|
|
|
---
|
|
|
|
## Sommaire
|
|
|
|
1. [Contexte et architecture](#1-contexte-et-architecture)
|
|
2. [Prérequis réseau — Ansible (dnsmasq + nftables)](#2-prérequis-réseau--ansible-dnsmasq--nftables)
|
|
3. [Installer les outils sur le poste perso](#3-installer-les-outils-sur-le-poste-perso)
|
|
4. [Installer les outils sur storage-01](#4-installer-les-outils-sur-storage-01)
|
|
5. [Configurer SOPS + age (chiffrement des secrets)](#5-configurer-sops--age-chiffrement-des-secrets)
|
|
6. [Écrire la configuration Talos](#6-écrire-la-configuration-talos)
|
|
7. [Générer les secrets et les configs nœuds](#7-générer-les-secrets-et-les-configs-nœuds)
|
|
8. [Préparer la clé USB Talos](#8-préparer-la-clé-usb-talos)
|
|
9. [Installer chaque nœud](#9-installer-chaque-nœud)
|
|
10. [Bootstrap etcd](#10-bootstrap-etcd)
|
|
11. [Récupérer le kubeconfig](#11-récupérer-le-kubeconfig)
|
|
12. [Vérifier que les nœuds sont Ready](#12-vérifier-que-les-nœuds-sont-ready)
|
|
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. [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)
|
|
|
|
---
|
|
|
|
## 1. Contexte et architecture
|
|
|
|
### Matériel
|
|
|
|
Trois Lenovo ThinkCentre M715q (AMD A10-9700E, 2.4 GHz, 4 cœurs) récupérés et réutilisés en cluster.
|
|
|
|
| Machine | IP fixe | Rôle | Disque | RAM |
|
|
|---|---|---|---|---|
|
|
| compute-01 | 192.168.10.11 | control-plane | NVMe 256 GB | 16 GB |
|
|
| compute-02 | 192.168.10.12 | worker | NVMe 256 GB | 8 GB |
|
|
| compute-03 | 192.168.10.13 | worker | NVMe 256 GB | 8 GB |
|
|
|
|
Les machines sont dans un LAN dédié `192.168.10.0/24`, isolé du LAN domestique (`192.168.1.0/24`) par storage-01 qui fait passerelle + NAT.
|
|
|
|
### Stack logicielle
|
|
|
|
| Composant | Version | Rôle |
|
|
|---|---|---|
|
|
| Talos Linux | v1.13.0 | OS immuable pour k8s — géré entièrement via API |
|
|
| Kubernetes | v1.33.1 | Orchestration des workloads |
|
|
| talhelper | latest | Génère les configs Talos depuis `talconfig.yaml` |
|
|
| SOPS + age | v3.9.4 / latest | Chiffrement des secrets dans git |
|
|
| Flannel | intégré Talos | CNI (réseau pods) — CIDR `10.42.0.0/16` |
|
|
| MetalLB | latest (Helm) | LoadBalancer bare-metal — pool `192.168.10.200-230` |
|
|
| Traefik | v3.x (Helm) | Ingress controller — IP fixe `192.168.10.200` |
|
|
| Helm | v3.x | Gestionnaire de charts k8s |
|
|
|
|
### Pourquoi Talos Linux ?
|
|
|
|
Talos est un OS immuable conçu exclusivement pour faire tourner Kubernetes. Il n'a pas de shell, pas de SSH, pas de package manager. Tout se fait via son API (`talosctl`). Ça force des bonnes pratiques : configuration déclarative, secrets chiffrés dans git, zéro dérive manuelle possible.
|
|
|
|
### Schéma réseau
|
|
|
|
```
|
|
Internet
|
|
│
|
|
Freebox (192.168.1.254)
|
|
│
|
|
├── LAN domestique 192.168.1.0/24
|
|
│ └── Poste perso (192.168.1.10)
|
|
│
|
|
└── storage-01 WAN (192.168.1.200)
|
|
│
|
|
NAT + routage
|
|
│
|
|
storage-01 LAN (192.168.10.1)
|
|
│
|
|
LAN cluster 192.168.10.0/24
|
|
├── compute-01 192.168.10.11
|
|
├── compute-02 192.168.10.12
|
|
├── compute-03 192.168.10.13
|
|
└── gpu-01 192.168.10.20
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Prérequis réseau — Ansible (dnsmasq + nftables)
|
|
|
|
Avant de toucher aux nœuds Talos, storage-01 doit être opérationnel avec :
|
|
- **dnsmasq** : sert le DHCP sur le LAN cluster (les nœuds en maintenance mode se prennent une IP DHCP) + DNS
|
|
- **nftables** : le port 67 (DHCP) doit être ouvert côté LAN
|
|
|
|
Ces deux rôles sont gérés par Ansible. Si ce n'est pas encore fait :
|
|
|
|
```bash
|
|
cd ansible/
|
|
ansible-playbook -i inventory.yml playbooks/site.yml --tags dnsmasq,gateway
|
|
```
|
|
|
|
### Ce que dnsmasq doit avoir dans sa config
|
|
|
|
Fichier `ansible/roles/dnsmasq/defaults/main.yml` :
|
|
```yaml
|
|
dhcp_range_start: 192.168.10.50
|
|
dhcp_range_end: 192.168.10.99
|
|
dhcp_lease_time: 12h
|
|
|
|
dhcp_static_hosts:
|
|
- mac: "6c:4b:90:82:8e:47"
|
|
name: compute-01
|
|
ip: 192.168.10.11
|
|
- mac: "6c:4b:90:cf:7f:c5"
|
|
name: compute-02
|
|
ip: 192.168.10.12
|
|
- mac: "6c:4b:90:b6:49:20"
|
|
name: compute-03
|
|
ip: 192.168.10.13
|
|
```
|
|
|
|
> Si les baux statiques ne sont pas encore configurés au moment de l'installation, les nœuds prendront une IP dynamique dans `192.168.10.50-99`. C'est normal — noter l'IP affiché sur l'écran du ThinkCentre ou dans les logs dnsmasq.
|
|
|
|
### Ce que nftables doit avoir
|
|
|
|
Le port 67 (DHCP) doit être ouvert :
|
|
```
|
|
udp dport 67 iif {{ lan_interface }} accept
|
|
```
|
|
|
|
> Sans cette règle, les requêtes DHCP des nœuds sont silencieusement droppées — les nœuds restent sans IP et `talosctl apply-config` ne peut pas les atteindre.
|
|
|
|
---
|
|
|
|
## 3. Installer les outils sur le poste perso
|
|
|
|
Tout cela s'installe dans `/usr/local/bin`.
|
|
|
|
```bash
|
|
# talhelper — génère les configs Talos depuis talconfig.yaml
|
|
curl -sL https://github.com/budimanjojo/talhelper/releases/latest/download/talhelper_linux_amd64.tar.gz \
|
|
| sudo tar xz -C /usr/local/bin talhelper
|
|
|
|
# age + age-keygen — chiffrement asymétrique des secrets
|
|
AGE_VERSION=$(curl -s https://api.github.com/repos/FiloSottile/age/releases/latest | grep tag_name | cut -d'"' -f4)
|
|
curl -sL "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz" \
|
|
| sudo tar xz -C /tmp
|
|
sudo mv /tmp/age/age /usr/local/bin/
|
|
sudo mv /tmp/age/age-keygen /usr/local/bin/
|
|
|
|
# sops — orchestrateur de chiffrement (utilise age en backend)
|
|
sudo curl -sL "https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64" \
|
|
-o /usr/local/bin/sops
|
|
sudo chmod +x /usr/local/bin/sops
|
|
|
|
# talosctl — CLI pour administrer les nœuds Talos
|
|
curl -sL https://talos.dev/install | sh
|
|
|
|
# kubectl — CLI Kubernetes standard
|
|
curl -sL "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
|
-o /tmp/kubectl
|
|
sudo install /tmp/kubectl /usr/local/bin/kubectl
|
|
|
|
# helm — gestionnaire de charts k8s
|
|
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
|
```
|
|
|
|
Vérifier :
|
|
```bash
|
|
talhelper --version
|
|
age-keygen --version
|
|
sops --version
|
|
talosctl version --client
|
|
kubectl version --client
|
|
helm version
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Installer les outils sur storage-01
|
|
|
|
Depuis le poste perso, se connecter en SSH puis rejouer les mêmes commandes. storage-01 doit pouvoir administrer le cluster indépendamment (backup etcd, urgences...).
|
|
|
|
```bash
|
|
ssh storage-01
|
|
|
|
# talhelper
|
|
curl -sL https://github.com/budimanjojo/talhelper/releases/latest/download/talhelper_linux_amd64.tar.gz \
|
|
| sudo tar xz -C /usr/local/bin talhelper
|
|
|
|
# age
|
|
AGE_VERSION=$(curl -s https://api.github.com/repos/FiloSottile/age/releases/latest | grep tag_name | cut -d'"' -f4)
|
|
curl -sL "https://github.com/FiloSottile/age/releases/download/${AGE_VERSION}/age-${AGE_VERSION}-linux-amd64.tar.gz" \
|
|
| sudo tar xz -C /tmp
|
|
sudo mv /tmp/age/age /usr/local/bin/
|
|
sudo mv /tmp/age/age-keygen /usr/local/bin/
|
|
|
|
# sops
|
|
sudo curl -sL "https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64" \
|
|
-o /usr/local/bin/sops
|
|
sudo chmod +x /usr/local/bin/sops
|
|
|
|
# talosctl
|
|
curl -sL https://talos.dev/install | sh
|
|
|
|
# kubectl
|
|
curl -sL "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
|
-o /tmp/kubectl
|
|
sudo install /tmp/kubectl /usr/local/bin/kubectl
|
|
|
|
# helm
|
|
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Configurer SOPS + age (chiffrement des secrets)
|
|
|
|
Les secrets Talos (clés PKI du cluster) doivent être dans git mais ne peuvent pas être en clair. SOPS les chiffre avec age.
|
|
|
|
### Générer la clé age (une fois par poste d'administration)
|
|
|
|
**Sur le poste perso :**
|
|
```bash
|
|
mkdir -p ~/.config/sops/age
|
|
age-keygen -o ~/.config/sops/age/keys.txt
|
|
```
|
|
|
|
La sortie affiche la clé publique, exemple :
|
|
```
|
|
Public key: age16rg9hnvnu89rll5schg8d2q5sccxlp7cxms9jmflguxapmtjp3lq0j4393
|
|
```
|
|
|
|
> La clé privée est dans `~/.config/sops/age/keys.txt`. Ne jamais la committer. Elle est dans `.gitignore`.
|
|
|
|
**Sur storage-01 (si administration depuis storage-01 souhaitée) :**
|
|
|
|
Option A — copier la même clé privée (plus simple) :
|
|
```bash
|
|
scp ~/.config/sops/age/keys.txt storage-01:~/.config/sops/age/keys.txt
|
|
```
|
|
|
|
Option B — générer une clé séparée et l'ajouter dans `.sops.yaml` (plus sécurisé, mais nécessite de recréer `talsecret.sops.yaml`).
|
|
|
|
### Créer le fichier `.sops.yaml` à la racine du repo
|
|
|
|
```yaml
|
|
creation_rules:
|
|
- path_regex: \.sops\.yaml$
|
|
age: age16rg9hnvnu89rll5schg8d2q5sccxlp7cxms9jmflguxapmtjp3lq0j4393
|
|
```
|
|
|
|
> La `path_regex` doit matcher le fichier `talsecret.sops.yaml`. Une regex trop restrictive (ex: `talos/.*\.sops\.yaml$`) peut ne pas matcher si SOPS calcule le chemin relatif différemment. La regex `\.sops\.yaml$` est plus robuste.
|
|
|
|
---
|
|
|
|
## 6. Écrire la configuration Talos
|
|
|
|
### `talos/talconfig.yaml` — source de vérité du cluster
|
|
|
|
```yaml
|
|
clusterName: funk
|
|
talosVersion: v1.13.0
|
|
kubernetesVersion: v1.33.1
|
|
endpoint: https://192.168.10.11:6443
|
|
domain: cluster.local
|
|
allowSchedulingOnControlPlanes: false
|
|
|
|
clusterPodNets:
|
|
- 10.42.0.0/16
|
|
clusterSvcNets:
|
|
- 10.43.0.0/16
|
|
|
|
cniConfig:
|
|
name: flannel
|
|
|
|
patches:
|
|
- "@./patches/all.yaml"
|
|
|
|
nodes:
|
|
- hostname: compute-01
|
|
ipAddress: 192.168.10.11
|
|
installDisk: /dev/nvme0n1
|
|
controlPlane: true
|
|
networkInterfaces:
|
|
- deviceSelector:
|
|
driver: r8169
|
|
dhcp: false
|
|
addresses:
|
|
- 192.168.10.11/24
|
|
routes:
|
|
- network: 0.0.0.0/0
|
|
gateway: 192.168.10.1
|
|
|
|
- hostname: compute-02
|
|
ipAddress: 192.168.10.12
|
|
installDisk: /dev/nvme0n1
|
|
controlPlane: false
|
|
patches:
|
|
- "@./patches/workers.yaml"
|
|
networkInterfaces:
|
|
- deviceSelector:
|
|
driver: r8169
|
|
dhcp: false
|
|
addresses:
|
|
- 192.168.10.12/24
|
|
routes:
|
|
- network: 0.0.0.0/0
|
|
gateway: 192.168.10.1
|
|
|
|
- hostname: compute-03
|
|
ipAddress: 192.168.10.13
|
|
installDisk: /dev/nvme0n1
|
|
controlPlane: false
|
|
patches:
|
|
- "@./patches/workers.yaml"
|
|
networkInterfaces:
|
|
- deviceSelector:
|
|
driver: r8169
|
|
dhcp: false
|
|
addresses:
|
|
- 192.168.10.13/24
|
|
routes:
|
|
- network: 0.0.0.0/0
|
|
gateway: 192.168.10.1
|
|
```
|
|
|
|
Points critiques :
|
|
- `installDisk: /dev/nvme0n1` — les ThinkCentre ont un NVMe, **pas** `/dev/sda` (qui est la clé USB)
|
|
- `endpoint: https://192.168.10.11:6443` — IP directe du control-plane, pas de VIP
|
|
- Pas de VIP configuré — un VIP = même IP que le nœud = casse etcd au bootstrap
|
|
- `cniConfig: name: flannel` — Talos gère Flannel nativement, ne pas appliquer de manifest externe
|
|
- `deviceSelector: driver: r8169` — carte réseau des ThinkCentre M715q
|
|
|
|
### `talos/patches/all.yaml` — appliqué à tous les nœuds
|
|
|
|
```yaml
|
|
machine:
|
|
time:
|
|
servers:
|
|
- 192.168.10.1
|
|
- time.cloudflare.com
|
|
network:
|
|
nameservers:
|
|
- 192.168.10.1
|
|
```
|
|
|
|
NTP et DNS pointent vers storage-01 (`192.168.10.1`) qui est la passerelle + dnsmasq du LAN cluster.
|
|
|
|
### `talos/patches/workers.yaml` — appliqué aux workers seulement
|
|
|
|
```yaml
|
|
machine:
|
|
kubelet:
|
|
extraArgs:
|
|
system-reserved: cpu=500m,memory=1Gi
|
|
kube-reserved: cpu=500m,memory=1Gi
|
|
```
|
|
|
|
compute-02 et compute-03 n'ont que 8 GB de RAM. Ces réservations protègent l'OS et kubelet de l'OOM killer quand les workloads chargent.
|
|
|
|
---
|
|
|
|
## 7. Générer les secrets et les configs nœuds
|
|
|
|
```bash
|
|
cd talos/
|
|
|
|
# Générer les secrets du cluster (une seule fois — contient les PKI, tokens, etc.)
|
|
talhelper gensecret | sops --filename-override talsecret.sops.yaml --encrypt /dev/stdin > talsecret.sops.yaml
|
|
|
|
# Vérifier que le fichier est bien chiffré (doit commencer par "sops:")
|
|
head -5 talsecret.sops.yaml
|
|
|
|
# Générer les configs par nœud + talosconfig dans clusterconfig/
|
|
talhelper genconfig
|
|
```
|
|
|
|
Le dossier `talos/clusterconfig/` est dans `.gitignore` — il est généré localement à partir des secrets déchiffrés. Ne pas committer.
|
|
|
|
```bash
|
|
ls talos/clusterconfig/
|
|
# funk-compute-01.yaml
|
|
# funk-compute-02.yaml
|
|
# funk-compute-03.yaml
|
|
# talosconfig
|
|
```
|
|
|
|
Committer les fichiers sources (pas le `clusterconfig/`) :
|
|
```bash
|
|
git add talos/talconfig.yaml talos/talsecret.sops.yaml talos/patches/ .sops.yaml
|
|
git commit -m "feat: talos cluster config funk"
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Préparer la clé USB Talos
|
|
|
|
Télécharger l'ISO Talos v1.13.0 (metal AMD64) depuis `https://github.com/siderolabs/talos/releases`.
|
|
|
|
```bash
|
|
# Identifier le device de la clé USB
|
|
lsblk
|
|
|
|
# Flasher — remplacer /dev/sdX par le bon device
|
|
sudo dd if=/chemin/vers/metal-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
|
sync
|
|
```
|
|
|
|
> **Utiliser `dd` direct, pas Ventoy.** Talos doit être le seul système bootable sur la clé. Ventoy ajoute ses propres entrées EFI qui peuvent interférer.
|
|
|
|
La même clé peut être utilisée pour les 3 nœuds successivement.
|
|
|
|
---
|
|
|
|
## 9. Installer chaque nœud
|
|
|
|
Le processus est identique pour les 3 machines. Les étapes pour chaque nœud :
|
|
|
|
### Étape A — Démarrer en maintenance mode
|
|
|
|
1. Brancher la clé USB dans le ThinkCentre
|
|
2. Allumer ou redémarrer
|
|
3. Si le BIOS démarre sur un autre OS : appuyer sur **F12** au boot pour choisir la clé USB
|
|
4. Talos démarre en "maintenance mode" (pas encore d'OS installé)
|
|
5. Le nœud obtient une IP DHCP de dnsmasq dans `192.168.10.50-99`
|
|
|
|
### Étape B — Identifier l'IP DHCP attribuée
|
|
|
|
```bash
|
|
# Option 1 — regarder les logs dnsmasq sur storage-01
|
|
ssh storage-01 "journalctl -u dnsmasq -n 20 | grep DHCP"
|
|
|
|
# Option 2 — scanner le réseau
|
|
nmap -sn 192.168.10.50-99
|
|
```
|
|
|
|
### Étape C — Vérifier le disque cible (IMPORTANT)
|
|
|
|
Avant d'appliquer la config, confirmer que `nvme0n1` existe bien :
|
|
|
|
```bash
|
|
export TALOSCONFIG=talos/clusterconfig/talosconfig
|
|
talosctl get disks --insecure --nodes <IP-DHCP>
|
|
```
|
|
|
|
Exemple de sortie attendue :
|
|
```
|
|
NODE NAMESPACE TYPE ID SIZE TRANSPORT ROTATIONAL
|
|
192.168.10.78 runtime Disk nvme0n1 238 GB nvme false
|
|
192.168.10.78 runtime Disk sda 16 GB usb false
|
|
```
|
|
|
|
> `nvme0n1` = le SSD NVMe interne → cible de l'installation.
|
|
> `sda` = la clé USB → **ne PAS mettre ça dans `installDisk`**.
|
|
|
|
### Étape D — Appliquer la config et déclencher l'installation
|
|
|
|
```bash
|
|
# compute-01 (remplacer l'IP DHCP réelle)
|
|
talosctl apply-config --insecure --nodes 192.168.10.78 \
|
|
--file talos/clusterconfig/funk-compute-01.yaml
|
|
|
|
# compute-02
|
|
talosctl apply-config --insecure --nodes <IP-DHCP-compute-02> \
|
|
--file talos/clusterconfig/funk-compute-02.yaml
|
|
|
|
# compute-03
|
|
talosctl apply-config --insecure --nodes <IP-DHCP-compute-03> \
|
|
--file talos/clusterconfig/funk-compute-03.yaml
|
|
```
|
|
|
|
Après l'application de la config :
|
|
1. Talos installe automatiquement sur `/dev/nvme0n1`
|
|
2. Il efface les entrées EFI existantes sur le NVMe
|
|
3. Il reboot
|
|
4. Après reboot, le nœud démarre sur son IP fixe configurée dans `talconfig.yaml`
|
|
|
|
Vérifier que le nœud est disponible sur son IP fixe :
|
|
```bash
|
|
talosctl --nodes 192.168.10.11 get members
|
|
# ou juste un ping
|
|
ping 192.168.10.11
|
|
```
|
|
|
|
La clé USB n'est plus nécessaire après la première installation réussie.
|
|
|
|
### Si le nœud reboot sur un ancien OS
|
|
|
|
Si le ThinkCentre avait déjà un OS (Windows, Linux...), ses entrées EFI peuvent prendre la priorité.
|
|
|
|
**Solution A — Retirer la clé USB pendant que Talos est en train de s'installer.** Le reboot se fait directement sur le NVMe fraîchement installé.
|
|
|
|
**Solution B — Via BIOS** : entrer dans le BIOS (F1 sur ThinkCentre), aller dans Boot Order, mettre le NVMe en premier.
|
|
|
|
**Solution C — Force via `talosctl upgrade`** (si Talos démarre mais depuis la clé, pas le NVMe) :
|
|
```bash
|
|
talosctl upgrade --image ghcr.io/siderolabs/installer:v1.13.0 --nodes <IP>
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Bootstrap etcd
|
|
|
|
Une fois les 3 nœuds sur leurs IPs fixes, initialiser etcd sur le control-plane.
|
|
|
|
```bash
|
|
talosctl bootstrap --nodes 192.168.10.11
|
|
```
|
|
|
|
> **Cette commande ne se lance qu'une seule fois, jamais sur un cluster existant.** Relancer bootstrap casse etcd irrémédiablement — il faudrait tout réinstaller.
|
|
|
|
Attendre environ 2 minutes que etcd démarre et que Kubernetes s'initialise :
|
|
```bash
|
|
talosctl --nodes 192.168.10.11 service etcd
|
|
# Attendre que l'état passe à "Running"
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Récupérer le kubeconfig
|
|
|
|
```bash
|
|
# Sur le poste perso
|
|
talosctl kubeconfig --nodes 192.168.10.11 ~/.kube/config
|
|
|
|
# Sur storage-01 (depuis storage-01 après avoir cloné le repo et régénéré clusterconfig/)
|
|
talosctl kubeconfig --nodes 192.168.10.11 ~/.kube/config
|
|
```
|
|
|
|
> storage-01 doit avoir son propre `clusterconfig/` généré localement (après avoir copié la clé age privée et `talsecret.sops.yaml`). Il faut faire `talhelper genconfig` sur storage-01 également.
|
|
|
|
---
|
|
|
|
## 12. Vérifier que les nœuds sont Ready
|
|
|
|
```bash
|
|
kubectl get nodes -o wide
|
|
```
|
|
|
|
Sortie attendue :
|
|
```
|
|
NAME STATUS ROLES AGE VERSION INTERNAL-IP
|
|
compute-01 Ready control-plane 5m v1.33.1 192.168.10.11
|
|
compute-02 Ready <none> 4m v1.33.1 192.168.10.12
|
|
compute-03 Ready <none> 4m v1.33.1 192.168.10.13
|
|
```
|
|
|
|
Si les nœuds sont `NotReady`, c'est normal dans les premières minutes — Flannel intégré à Talos démarre automatiquement. Patienter 1-2 minutes.
|
|
|
|
> **Ne pas appliquer le manifest Flannel externe** (`kubectl apply -f kube-flannel.yml`). Talos v1.13 inclut Flannel nativement dans `kube-system` avec le bon CIDR `10.42.0.0/16`. Appliquer le manifest externe crée deux DaemonSets Flannel en conflit.
|
|
|
|
---
|
|
|
|
## 13. Installer MetalLB
|
|
|
|
MetalLB permet d'assigner des IPs réelles (du LAN cluster) aux services Kubernetes de type `LoadBalancer`. Sans MetalLB, les services LoadBalancer restent en `<pending>` indéfiniment.
|
|
|
|
### Installation via Helm
|
|
|
|
```bash
|
|
helm repo add metallb https://metallb.github.io/metallb
|
|
helm repo update
|
|
|
|
kubectl create namespace metallb-system
|
|
|
|
helm install metallb metallb/metallb --namespace metallb-system
|
|
|
|
# Attendre que tous les pods soient Running (controller + 3 speakers)
|
|
kubectl get pods -n metallb-system -w
|
|
```
|
|
|
|
Sortie attendue :
|
|
```
|
|
NAME READY STATUS RESTARTS AGE
|
|
controller-7dd9f6d5f9-xxxxx 1/1 Running 0 2m
|
|
speaker-xxxxx 1/1 Running 0 2m
|
|
speaker-xxxxx 1/1 Running 0 2m
|
|
speaker-xxxxx 1/1 Running 0 2m
|
|
```
|
|
|
|
### Configuration du pool d'IPs
|
|
|
|
```bash
|
|
cat <<'EOF' | kubectl apply -f -
|
|
apiVersion: metallb.io/v1beta1
|
|
kind: IPAddressPool
|
|
metadata:
|
|
name: funk-pool
|
|
namespace: metallb-system
|
|
spec:
|
|
addresses:
|
|
- 192.168.10.200-192.168.10.230
|
|
---
|
|
apiVersion: metallb.io/v1beta1
|
|
kind: L2Advertisement
|
|
metadata:
|
|
name: funk-l2
|
|
namespace: metallb-system
|
|
spec:
|
|
ipAddressPools:
|
|
- funk-pool
|
|
EOF
|
|
```
|
|
|
|
> Si l'application échoue avec une erreur de webhook (`connection refused` vers `metallb-webhook-service.metallb-system.svc:443`), c'est un signe que le réseau pod est instable. Vérifier d'abord que tous les pods Flannel sont `1/1 Running`.
|
|
|
|
---
|
|
|
|
## 14. Installer Traefik
|
|
|
|
Traefik est l'ingress controller — il reçoit tout le trafic HTTP/HTTPS entrant sur `192.168.10.200` et le route vers les bons services en fonction des IngressRoute.
|
|
|
|
```bash
|
|
helm repo add traefik https://traefik.github.io/charts
|
|
helm repo update
|
|
|
|
kubectl create namespace infra
|
|
|
|
helm install traefik traefik/traefik \
|
|
--namespace infra \
|
|
--set service.type=LoadBalancer \
|
|
--set service.loadBalancerIP=192.168.10.200 \
|
|
--set ports.web.port=80 \
|
|
--set ports.websecure.port=443 \
|
|
--set ingressRoute.dashboard.enabled=true \
|
|
--set logs.general.level=INFO
|
|
```
|
|
|
|
Vérifier que MetalLB a bien assigné l'IP :
|
|
```bash
|
|
kubectl get svc -n infra traefik
|
|
```
|
|
|
|
Sortie attendue :
|
|
```
|
|
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
|
|
traefik LoadBalancer 10.43.192.29 192.168.10.200 80:30507/TCP,443:30480/TCP
|
|
```
|
|
|
|
### Accéder au dashboard Traefik
|
|
|
|
Le dashboard est disponible sur le port 9000 via un IngressRoute interne :
|
|
```bash
|
|
kubectl port-forward -n infra svc/traefik 9000:9000
|
|
# Ouvrir http://localhost:9000/dashboard/
|
|
```
|
|
|
|
---
|
|
|
|
## 15. Configurer le DNS wildcard *.lab.local
|
|
|
|
Le rôle Ansible `dnsmasq` gère ça via une ligne dans la config dnsmasq :
|
|
|
|
```
|
|
address=/.lab.local/192.168.10.200
|
|
```
|
|
|
|
Elle est générée automatiquement si la variable `traefik_ip` est définie dans `ansible/roles/dnsmasq/defaults/main.yml` :
|
|
|
|
```yaml
|
|
traefik_ip: 192.168.10.200
|
|
```
|
|
|
|
Et dans le template `dnsmasq.conf.j2` :
|
|
```jinja2
|
|
address=/.{{ dns_domain }}/{{ traefik_ip }}
|
|
```
|
|
|
|
Appliquer :
|
|
```bash
|
|
ansible-playbook -i ansible/inventory.yml ansible/playbooks/site.yml --tags dnsmasq
|
|
```
|
|
|
|
Tester depuis le poste perso ou storage-01 :
|
|
```bash
|
|
dig @192.168.10.1 monservice.lab.local +short
|
|
# → 192.168.10.200
|
|
```
|
|
|
|
Avec ça, n'importe quel `<service>.lab.local` sera résolu vers Traefik, qui se charge du routage vers le bon service k8s.
|
|
|
|
---
|
|
|
|
## 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
|
|
kubectl get nodes -o wide
|
|
|
|
# Tous les pods (rien ne doit être en erreur)
|
|
kubectl get pods -A
|
|
|
|
# MetalLB
|
|
kubectl get pods -n metallb-system
|
|
|
|
# Traefik avec son IP
|
|
kubectl get svc -n infra traefik
|
|
|
|
# Flannel (doit être 3/3 Running)
|
|
kubectl get pods -n kube-system | grep flannel
|
|
|
|
# Test DNS wildcard depuis storage-01
|
|
ssh storage-01 "dig @192.168.10.1 test.lab.local +short"
|
|
```
|
|
|
|
Sortie finale attendue :
|
|
```
|
|
NAME STATUS ROLES AGE VERSION
|
|
compute-01 Ready control-plane 1h v1.33.1
|
|
compute-02 Ready <none> 1h v1.33.1
|
|
compute-03 Ready <none> 1h v1.33.1
|
|
```
|
|
|
|
---
|
|
|
|
## 18. Administration courante
|
|
|
|
### Variables d'environnement (à mettre dans ~/.bashrc)
|
|
|
|
```bash
|
|
export TALOSCONFIG=~/Projets/lab/talos/clusterconfig/talosconfig
|
|
export KUBECONFIG=~/.kube/config
|
|
```
|
|
|
|
### Commandes du quotidien
|
|
|
|
```bash
|
|
# État global
|
|
kubectl get nodes -o wide
|
|
kubectl get pods -A
|
|
kubectl get events -A --sort-by='.lastTimestamp'
|
|
|
|
# État Talos (services internes)
|
|
talosctl --nodes 192.168.10.11,192.168.10.12,192.168.10.13 service
|
|
|
|
# Logs d'un service Talos
|
|
talosctl --nodes 192.168.10.11 dmesg | tail -30
|
|
talosctl --nodes 192.168.10.11 service etcd
|
|
|
|
# Logs d'un pod k8s
|
|
kubectl logs -n <namespace> <pod>
|
|
kubectl logs -n <namespace> <pod> -f # follow
|
|
|
|
# Reboot propre d'un nœud
|
|
talosctl --nodes 192.168.10.12 reboot
|
|
|
|
# Shutdown d'un nœud
|
|
talosctl --nodes 192.168.10.12 shutdown
|
|
|
|
# Tous les workers d'un coup
|
|
talosctl --nodes 192.168.10.12,192.168.10.13 reboot
|
|
```
|
|
|
|
### Mise à jour de la config d'un nœud
|
|
|
|
```bash
|
|
cd talos/
|
|
|
|
# 1. Modifier talconfig.yaml
|
|
vim talconfig.yaml
|
|
|
|
# 2. Régénérer les configs
|
|
talhelper genconfig
|
|
|
|
# 3. Appliquer sur le nœud (sans --insecure cette fois, on connaît déjà le nœud)
|
|
talosctl --nodes 192.168.10.11 apply-config \
|
|
--file clusterconfig/funk-compute-01.yaml
|
|
```
|
|
|
|
### Backup etcd (critique — single control-plane)
|
|
|
|
```bash
|
|
talosctl --nodes 192.168.10.11 etcd snapshot /tmp/etcd-backup-$(date +%Y%m%d).db
|
|
scp /tmp/etcd-backup-*.db storage-01:/srv/data/backups/etcd/
|
|
```
|
|
|
|
> À planifier en cron hebdomadaire. Sur un single control-plane, la perte de etcd sans backup = reinstallation complète du cluster.
|
|
|
|
### Régénérer les configs depuis un nouveau poste
|
|
|
|
Si tu changes de poste ou récupères le repo sur storage-01 :
|
|
|
|
```bash
|
|
# 1. Copier la clé age privée
|
|
mkdir -p ~/.config/sops/age
|
|
# Copier keys.txt depuis l'ancien poste
|
|
|
|
# 2. Cloner le repo
|
|
git clone git@github.com:Alkatrazz24/Funk-lab.git
|
|
|
|
# 3. Régénérer clusterconfig/ (déchiffre talsecret.sops.yaml avec la clé age)
|
|
cd Funk-lab/talos
|
|
talhelper genconfig
|
|
|
|
# 4. Récupérer le kubeconfig
|
|
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.
|
|
|
|
---
|
|
|
|
## 19. Pièges et erreurs rencontrés
|
|
|
|
Tout ce qui a planté lors de l'installation réelle, pour ne pas y retomber.
|
|
|
|
---
|
|
|
|
### ❌ installDisk = /dev/sda → Talos installe sur la clé USB
|
|
|
|
**Symptôme** : Le nœud reboot mais reste en maintenance mode ou redémarre en boucle.
|
|
|
|
**Cause** : `installDisk: /dev/sda` dans `talconfig.yaml`. Sur les ThinkCentre M715q, `/dev/sda` est la clé USB. Le NVMe interne est `/dev/nvme0n1`.
|
|
|
|
**Vérification avant d'appliquer** :
|
|
```bash
|
|
talosctl get disks --insecure --nodes <IP-DHCP>
|
|
# Chercher le disk avec TRANSPORT=nvme
|
|
```
|
|
|
|
**Fix** : Corriger `talconfig.yaml`, régénérer avec `talhelper genconfig`, reflasher la clé USB avec le bon ISO, redémarrer le nœud en maintenance mode, réappliquer.
|
|
|
|
---
|
|
|
|
### ❌ VIP = même IP que le nœud → etcd bloque sur "Waiting for etcd spec"
|
|
|
|
**Symptôme** : Après `talosctl bootstrap`, etcd ne démarre jamais. `talosctl service etcd` montre `Waiting for etcd spec`.
|
|
|
|
**Cause** : Une VIP (Virtual IP) avait été configurée dans `talconfig.yaml` avec la même adresse que le nœud (`192.168.10.11`). Talos attend que la VIP soit différente du nœud pour certaines opérations réseau internes, et ça crée un deadlock.
|
|
|
|
**Fix** : Supprimer complètement la section VIP dans `talconfig.yaml`. On n'en a pas besoin — sur un single control-plane, l'endpoint est l'IP directe du nœud.
|
|
|
|
---
|
|
|
|
### ❌ Nœud reboot sur l'ancien OS (entrées EFI résiduelles)
|
|
|
|
**Symptôme** : Après `talosctl apply-config`, le nœud reboot mais démarre sur Windows ou un Linux précédent.
|
|
|
|
**Cause** : L'ancien OS avait des entrées dans le firmware EFI (NVRAM) avec une priorité plus haute que le nouveau bootloader Talos.
|
|
|
|
**Fix A** : Retirer la clé USB pendant que Talos est en train d'installer sur le NVMe — le reboot suivant n'a plus que l'entrée NVMe disponible.
|
|
|
|
**Fix B** : Via BIOS (F1) → Boot Priority → mettre le NVMe en premier.
|
|
|
|
---
|
|
|
|
### ❌ DHCP ne fonctionne pas — nœuds sans IP
|
|
|
|
**Symptôme** : Le nœud démarre en maintenance mode mais reste sans IP. `talosctl apply-config --insecure --nodes <IP>` ne trouve rien.
|
|
|
|
**Cause** : nftables sur storage-01 ne laissait pas passer le port 67 (DHCP). Les broadcast DHCP des nœuds étaient silencieusement droppés.
|
|
|
|
**Fix** : Ajouter la règle dans `ansible/roles/gateway/templates/nftables.conf.j2` :
|
|
```
|
|
udp dport 67 iif {{ lan_interface }} accept
|
|
```
|
|
|
|
Puis rejouer le playbook `gateway` :
|
|
```bash
|
|
ansible-playbook -i inventory.yml playbooks/site.yml --tags gateway
|
|
```
|
|
|
|
---
|
|
|
|
### ❌ SOPS "no matching creation rules found"
|
|
|
|
**Symptôme** : `talhelper gensecret | sops --filename-override talsecret.sops.yaml --encrypt /dev/stdin` échoue.
|
|
|
|
**Cause** : La `path_regex` dans `.sops.yaml` ne matchait pas le chemin relatif calculé par SOPS. Exemple : `talos/.*\.sops\.yaml$` ne matche pas si SOPS calcule le chemin depuis la racine du repo.
|
|
|
|
**Fix** : Utiliser une regex plus simple et robuste :
|
|
```yaml
|
|
creation_rules:
|
|
- path_regex: \.sops\.yaml$
|
|
age: <clé-publique>
|
|
```
|
|
|
|
---
|
|
|
|
### ❌ Flannel CrashLoopBackOff sur 2 pods sur 3
|
|
|
|
**Symptôme** :
|
|
```
|
|
kube-flannel-frw6t 0/1 CrashLoopBackOff
|
|
kube-flannel-zl6ds 0/1 CrashLoopBackOff
|
|
```
|
|
|
|
Logs :
|
|
```
|
|
Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-frw6t':
|
|
pods "kube-flannel-frw6t" is forbidden: User "system:serviceaccount:kube-system:flannel"
|
|
cannot get resource "pods" in API group "" in the namespace "kube-system"
|
|
```
|
|
|
|
**Cause** : Le manifest Flannel externe avait été appliqué (`kubectl apply -f kube-flannel.yml`), créant une `ClusterRoleBinding` qui pointait vers le service account `flannel` dans le namespace `kube-flannel`. Quand on a supprimé ce namespace (conflit avec le Flannel intégré de Talos), la `ClusterRoleBinding` a perdu sa cible mais est restée — sauf qu'elle pointait toujours vers `kube-flannel`, pas vers `kube-system` où tournent les pods Flannel natifs de Talos.
|
|
|
|
**Fix** :
|
|
```bash
|
|
# 1. Corriger le namespace dans la ClusterRoleBinding
|
|
kubectl patch clusterrolebinding flannel --type='json' \
|
|
-p='[{"op": "replace", "path": "/subjects/0/namespace", "value": "kube-system"}]'
|
|
|
|
# 2. Supprimer les pods en erreur pour forcer leur redémarrage
|
|
kubectl delete pod -n kube-system kube-flannel-frw6t kube-flannel-zl6ds
|
|
```
|
|
|
|
**Leçon** : Avec Talos v1.13+, ne jamais appliquer le manifest Flannel externe. Flannel est déjà là, dans `kube-system`, avec le bon CIDR.
|
|
|
|
---
|
|
|
|
### ❌ MetalLB webhook timeout
|
|
|
|
**Symptôme** : `kubectl apply` de l'`IPAddressPool` échoue avec :
|
|
```
|
|
Internal error occurred: failed calling webhook "ipaddresspoolvalidationwebhook.metallb.io":
|
|
Post "https://metallb-webhook-service.metallb-system.svc:443/validate-metallb-io-v1beta1-ipaddresspool"
|
|
dial tcp 10.42.2.2:9443: connect: connection refused
|
|
```
|
|
|
|
**Cause** : Le réseau pod était instable à cause des deux pods Flannel en CrashLoopBackOff. L'appel webhook du kube-apiserver vers le pod MetalLB ne passait pas.
|
|
|
|
**Fix** : Résoudre d'abord le problème Flannel (voir ci-dessus), attendre que les 3 pods Flannel soient `1/1 Running`, puis réappliquer la config MetalLB.
|
|
|
|
---
|
|
|
|
---
|
|
|
|
### ❌ 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 |
|
|
|---|---|
|
|
| `installDisk` | Toujours `/dev/nvme0n1` sur les ThinkCentre M715q |
|
|
| USB ISO | `dd` direct, **pas Ventoy** |
|
|
| Bootstrap etcd | **Une seule fois** sur compute-01, jamais relancer |
|
|
| VIP | Ne pas configurer si VIP = IP du nœud |
|
|
| Flannel externe | Ne **pas** appliquer `kube-flannel.yml` — Talos le gère nativement |
|
|
| MetalLB webhook | Vérifier que Flannel est 100% healthy avant d'appliquer IPAddressPool |
|
|
| SOPS path_regex | Utiliser `\.sops\.yaml$` (simple) plutôt qu'un chemin relatif complet |
|
|
| 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 |
|
|
|
|
|
|
---
|
|
|
|
## État vérifié — 2026-06-05
|
|
|
|
Tous les nœuds du cluster sont actifs et en état Ready. Les versions Kubernetes (v1.33.1) et les systèmes d'exploitation (Talos v1.13.0) sont conformes aux spécifications. Aucune divergence notable avec la documentation. Un pod dans l'espace sacrifice (sacrifice-assign-renfort-29676600-wcjnr) a terminé son execution, ce qui pourrait indiquer une tâche ponctuelle ou un test réussi. |