Partie 4 : La grande migration vers Kubernetes (K3s)
Novembre 2025
Le déclic : Pourquoi Kubernetes ?
Mon site Flask tournait bien dans Docker, mais je voyais plus grand :
- Mon NAS hébergeait une dizaine de services Docker
- 2 Raspberry Pi prenaient la poussière
- Pas de haute disponibilité : Si le NAS tombe, tout tombe
- Pas d'orchestration : Gestion manuelle des containers
Préparation du cluster K3s
Pour un guide complet et détaillé de l'installation K3s (préparation réseau, configuration système, concepts Kubernetes, etc.), consultez mon article technique : Installation d'un cluster K3s sur Raspberry Pi
Le matériel final
| Hostname | IP | Rôle | Matériel | RAM |
|---|---|---|---|---|
| rpi4-master | 192.168.1.51 | Control Plane | RPi 4 | 8GB |
| rpi4-worker | 192.168.1.50 | Worker | RPi 4 | 2GB |
| rpi3-worker1 | 192.168.1.36 | Worker | RPi 3 | 1GB |
| rpi3-worker2 | 192.168.1.15 | Worker | RPi 3 | 1GB |
Installation de K3s (résumé)
# Sur le master
curl -sfL https://get.k3s.io | sh -s - \
--disable traefik \
--write-kubeconfig-mode 644
# Sur chaque worker
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.51:6443 \
K3S_TOKEN=<TOKEN> sh -
# Vérification
kubectl get nodes
NAME STATUS ROLES AGE
rpi4-master Ready control-plane,master 5m
rpi4-worker Ready <none> 2m
rpi3-worker1 Ready <none> 1m
rpi3-worker2 Ready <none> 1m
Migration de Docker vers K3s
1. Création du namespace
kubectl create namespace home-fonta
2. Build de l'image pour ARM64
Premier piège : Mon PC est en x86, les RPi en ARM64 !
# Tentative avec buildx (échec, trop lent)
docker buildx build --platform linux/arm64 ...
# Solution : Build directement sur le Pi !
ssh pi@192.168.1.51
git clone https://github.com/mon-repo/home-fonta.git
docker build -t nocoblas/home-fonta-web:v2.0 .
docker push nocoblas/home-fonta-web:v2.0
3. Premier deployment Kubernetes
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: home-fonta-web
namespace: home-fonta
spec:
replicas: 3
selector:
matchLabels:
app: homefonta
template:
metadata:
labels:
app: homefonta
spec:
containers:
- name: web
image: nocoblas/home-fonta-web:v2.0
ports:
- containerPort: 8000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
4. Service ClusterIP
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: home-fonta-web
namespace: home-fonta
spec:
selector:
app: homefonta
ports:
- port: 80
targetPort: 8000
type: ClusterIP
5. Ingress avec nginx-ingress
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: home-fonta-web
namespace: home-fonta
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: home-fonta.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: home-fonta-web
port:
number: 80
Premier problème : CrashLoopBackOff
kubectl get pods -n home-fonta
NAME READY STATUS RESTARTS
home-fonta-web-59c5486fb5-nl22t 0/1 CrashLoopBackOff 19
kubectl logs -n home-fonta home-fonta-web-59c5486fb5-nl22t
# Erreur : Port 8000 already in use
Cause : Les health probes pointaient vers le mauvais port !
Solution :
livenessProbe:
httpGet:
path: /health
port: 8000 # Port de Gunicorn, pas 80 !
Problème majeur : Le réseau CNI
Le pire problème rencontré : Les pods sur différents nodes ne communiquaient pas !
# Ingress sur master → Pod sur worker = TIMEOUT
# Ingress sur master → Pod sur master = OK
# Test de connectivité
kubectl exec -it debug-pod -- ping 10.42.1.41
# Timeout !
Investigation du problème CNI
# Vérification Flannel
kubectl get pods -n kube-flannel
# Tous Running... mais ça ne marche pas
# Logs
kubectl logs -n kube-flannel kube-flannel-ds-xxx
# Erreur iptables
Solution temporaire : NodeSelector
# Force tous les pods sur le master
nodeSelector:
kubernetes.io/hostname: rpi4-master
Pas idéal, mais ça marche ! Investigation du CNI remise à plus tard.
Ajout des certificats SSL avec cert-manager
# Installation cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# ClusterIssuer pour Let's Encrypt
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: mon-email@example.com
privateKeySecretRef:
name: letsencrypt-prod-dns
solvers:
- dns01:
cloudflare:
email: mon-email@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
Performance : Toujours pas terrible
Même sur K3s, le site était encore plus lent. 10-15 secondes pour charger !
Diagnostic
# Analyse des requêtes
curl -w "@curl-format.txt" https://home-fonta.fr
time_namelookup: 0.004
time_connect: 0.012
time_appconnect: 0.043
time_pretransfer: 0.043
time_redirect: 0.000
time_starttransfer: 10.234 # 10 secondes !
time_total: 15.123
Le problème : Flask servait les fichiers statiques !
Solution architecturale : NGINX + Flask
Il fallait séparer :
- NGINX : Sert les fichiers statiques (CSS, JS, images)
- Flask : Gère uniquement le dynamique
Nouvelle architecture avec NGINX
# Nouveau Dockerfile avec NGINX + Gunicorn
FROM python:3.12-slim
# Installer NGINX et Supervisor
RUN apt-get update && apt-get install -y nginx supervisor
WORKDIR /app
# Flask app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
COPY templates/ templates/
# Fichiers statiques pour NGINX
COPY static/ /app/static/
# Config NGINX
COPY nginx/default.conf /etc/nginx/sites-enabled/default
# Config Supervisor (pour lancer NGINX + Gunicorn)
COPY supervisor.conf /etc/supervisor/conf.d/app.conf
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
Configuration NGINX :
server {
listen 80;
# Fichiers statiques - NGINX direct
location /static/ {
alias /app/static/;
expires 7d;
add_header Cache-Control "public, immutable";
gzip on;
}
# Dynamique - Proxy vers Gunicorn
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}
}
Résultat : SPECTACULAIRE !
| Métrique | Avant (Flask seul) | Après (NGINX+Flask) |
|---|---|---|
| Temps HTML | 10-15s | 15ms |
| Temps CSS | 504 timeout | 50ms |
| Temps JS | 504 timeout | 50ms |
| Images | 504 timeout | 100ms |
100x plus rapide ! 🚀
Automatisation avec Ansible
J'ai créé des playbooks Ansible pour tout automatiser :
# Structure
ansible-k3s/
├── inventory.yml
├── roles/
│ ├── common/ # Config de base des Pi
│ ├── k3s-master/ # Installation master
│ ├── k3s-worker/ # Installation workers
│ └── k3s-apps/ # Déploiement apps
└── playbooks/
└── site.yml # Full installation
Une commande pour tout installer :
ansible-playbook -i inventory.yml playbooks/site.yml
Leçons apprises avec K3s
- Architecture matters : NGINX pour statique = game changer
- CNI debugging est complexe : NodeSelector peut sauver
- ARM64 != x86 : Build sur la bonne architecture
- Health probes : Toujours vérifier les ports
- K3s est léger : Parfait pour les RPi
État après migration K3s
✅ Cluster 4 nodes opérationnel
✅ 3 replicas du site (haute dispo)
✅ SSL automatique avec cert-manager
✅ Performance 100x meilleure
⚠️ Problème CNI contourné mais pas résolu
Le site tournait magnifiquement sur K3s, mais je voulais aller plus loin... Mais c'est là que j'ai tout cassé, on vera ça dans les prochaines parties.
Suite : Partie 5 - L'aventure Helm et les derniers défis