Partie 12: Bascule de Passbolt en production sur K3s
Contexte
Après une semaine de tests réussis sur le sous-domaine passbolt-test.home-fonta.fr, j'ai décidé de basculer Passbolt en production sur son domaine définitif passbolt.home-fonta.fr. Cette bascule a révélé un problème latent sur MySQL qui démontre parfaitement la résilience de Kubernetes.
Étapes de la bascule
1. Validation de la période de test
Durée de test : 7 jours
Résultat : Tous les utilisateurs et mots de passe accessibles, aucun problème fonctionnel détecté
# Vérification de l'état avant bascule
kubectl get pods -n passbolt
kubectl get pods -n blog # MySQL externe
curl -k https://passbolt-test.home-fonta.fr/healthcheck/status.json
2. Modification de la configuration
Changements dans values.yaml:
# Avant
passbolt:
url: https://passbolt-test.home-fonta.fr
ingress:
host: passbolt-test.home-fonta.fr
tls:
secretName: passbolt-test-tls
# Après
passbolt:
url: https://passbolt.home-fonta.fr
ingress:
host: passbolt.home-fonta.fr
tls:
secretName: passbolt-tls
3. Premier problème : Conflit d'Ingress
Erreur rencontrée :
admission webhook denied the request: host "passbolt.home-fonta.fr" and path "/"
is already defined in ingress home-fonta/passbolt-external-ingress
Cause : Les anciens manifests Kubernetes qui pointaient vers le Passbolt du NAS existaient encore dans le chart external-services j'ai oublié de les supprimer du coup il y a conflit.
Solution : Suppression des fichiers obsolètes
# Fichiers supprimés du dépôt Git
helm-charts/external-services/templates/passbolt-endpoints.yaml
helm-charts/external-services/templates/passbolt-ingress.yaml
helm-charts/external-services/templates/passbolt-service.yaml
Résultat : ArgoCD a détecté la suppression et a nettoyé les ressources Kubernetes correspondantes.
4. Arrêt de l'ancien Passbolt sur le NAS
# Arrêt du conteneur Docker Passbolt sur le NAS
docker stop 21ae69153f7a
5. Vérification de la bascule
# Test du nouveau domaine
curl -k https://passbolt.home-fonta.fr/healthcheck/status.json
# {"header":{"status":"success"},"body":"OK"}
# Vérification du certificat SSL (Let's Encrypt)
kubectl get certificate -n passbolt
# NAME READY SECRET AGE
# passbolt-tls True passbolt-tls 5m
Résultat : Bascule réussie, certificat SSL automatiquement généré par cert-manager.
Incident MySQL : La force de kubernetes avec sa résiliance
Découverte de l'incident
Juste après la bascule en production, l'interface Passbolt a affiché une erreur :
An Internal Error Has Occurred
Logs Passbolt :
Connection to Mysql could not be established: SQLSTATE[HY000] [2002] Connection refused
Investigation
# Vérification de l'état du pod MySQL
kubectl get pods -n blog
# NAME READY STATUS RESTARTS AGE
# mysql-56c9f59c6b-6hvds 1/1 Running 5 10d
Observation importante : RESTARTS: 5 - Le pod MySQL avait redémarré plusieurs fois.
# Analyse des événements du pod
kubectl describe pod -n blog mysql-56c9f59c6b-6hvds
# Événements pertinents
Warning Unhealthy 2m32s (x30 over 13d) kubelet Liveness probe failed: command "mysqladmin ping -h localhost" timed out
Warning Unhealthy 2m20s (x171 over 19d) kubelet Readiness probe failed: command "mysqladmin ping -h localhost" timed out
Normal Killing 2m20s (x5 over 10d) kubelet Container mysql failed liveness probe, will be restarted
Analyse du problème
Découverte : MySQL avait un problème de configuration des health probes depuis 19 jours !
- 171 échecs de readinessProbe sur 19 jours
- 30 échecs de livenessProbe sur 13 jours
- 5 redémarrages automatiques sur 10 jours
Configuration problématique dans deployment.yaml :
readinessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 1 # Trop court visiblement
Explication trouvée : Sur un Raspberry Pi 4 sous charge, la commande mysqladmin ping peut mettre plus de 1 seconde à répondre. Le timeout trop court causait des échecs intermittents.
Solution
Modification du timeout de la readinessProbe :
readinessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 5 # Le temps sera suffisement long pour éviter les timeout
Il ne reste qu'à faire un commit et ArgoCD me refera le déploiment avec la correction.
La puissance de Kubernetes démontrée
Ce qui s'est passé réellement
Sans Kubernetes (ancien setup sur NAS) :
- MySQL qui timeout = service indisponible
- Nécessite intervention manuelle pour redémarrer
- Temps d'indisponibilité : plusieurs minutes voire heures
Avec Kubernetes :
- MySQL timeout détecté par la liveness probe
- Redémarrage automatique du conteneur
- Service rétabli en ~30 secondes
- Aucune intervention manuelle nécessaire
Le timing "chanceux"
La bascule en production est tombée exactement au moment d'un redémarrage de MySQL. Cela aurait pu être catastrophique, mais grâce à Kubernetes :
- Auto-healing : MySQL a redémarré automatiquement
- Init container : Passbolt a attendu que MySQL soit prêt
- Readiness probes : Passbolt n'a servi du trafic qu'une fois MySQL disponible
- Temps d'indisponibilité : <2 minutes
Ce qui a été invisible
Pendant 19 jours, MySQL avait des problèmes intermittents :
- 171 échecs de readiness probe
- 5 redémarrages automatiques
- Je n'y ai vu que du feu !
Kubernetes a masqué le problème en assurant la continuité de service. Sans monitoring actif, ce bug aurait pu passer inaperçu indéfiniment.
Architecture de résilience
┌─────────────────────────────────────────────┐
│ Auto-healing K3s │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Passbolt Pod │ │
│ │ - Init: wait-for-mysql │ │
│ │ - Readiness: /healthcheck │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ MySQL Pod (namespace: blog) │ │
│ │ - Liveness: mysqladmin ping (5s) │ │
│ │ - Readiness: mysqladmin ping (5s) │ │
│ │ - Auto-restart on failure │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ PVC: mysql-data (local-path) │ │
│ │ - Node: rpi4-master (SSD) │ │
│ │ - Data persisted across restarts │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Éléments clés de la résilience :
- Liveness probes : Détection automatique des problèmes
- Auto-restart : Correction automatique sans intervention
- Persistent volumes : Données préservées lors des redémarrages
- Init containers : Garantie de dépendances prêtes
- Readiness probes : Pas de trafic vers pods non-prêts
Leçons apprises
1. Kubernetes cache bien les problèmes (en bien et en mal)
Avantage : Haute disponibilité automatique
Inconvénient : Les problèmes sous-jacents peuvent passer inaperçus
Action : Mettre en place du monitoring (Prometheus + Grafana) pour détecter :
- Nombre de restarts anormaux
- Échecs de probes répétés
- Latence des requêtes
2. Importance des timeouts adaptés au matériel
Sur Raspberry Pi :
- CPU ARM moins puissant qu'un serveur x86
- I/O parfois lentes (même avec SSD)
- Timeouts de 1 seconde = trop courts
Bonne pratique :
- Liveness probe :
timeoutSeconds: 5-10 - Readiness probe :
timeoutSeconds: 5-10 - Initial delay : laisser le temps au service de démarrer
3. GitOps facilite les rollbacks
En cas de problème, revenir en arrière est trivial :
git revert HEAD
git push
# ArgoCD rollback automatique en ~30s
4. Les périodes de transition révèlent les failles
La bascule en production a forcé un moment de stress sur l'infrastructure, révélant un bug qui existait depuis 3 semaines. C'est une opportunité d'amélioration.
Améliorations futures
Court terme
- Corriger les timeouts MySQL (fait)
- Surveiller les restarts pendant 7 jours
- Nettoyer le DNS (supprimer passbolt-test.home-fonta.fr)
- Désactiver définitivement le Passbolt NAS
Moyen terme (Prévu pour la partie 15)
- Déployer Prometheus + Grafana
- Créer des alertes sur :
- Nombre de restarts > 1 en 24h
- Échecs de probes > 10 en 1h
- Latence MySQL > 200ms
- Auditer tous les deployments pour vérifier les timeouts
Long terme
- Envisager un cluster multi-nœuds pour MySQL (réplication)
- Backups automatiques (j'ai entendu parler de Velero)
- Tester les scénarios de "disaster recovery"
Conclusion
Cette bascule en production a été un succès, malgré le timing "malchanceux" du redémarrage MySQL. L'incident a révélé :
- La robustesse de Kubernetes : Auto-healing transparent qui a masqué un bug pendant 19 jours
- L'importance du monitoring : Sans logs, impossible de détecter les problèmes silencieux
- La valeur de GitOps : Traçabilité complète et rollback facile
Le plus impressionnant : Pendant 19 jours, MySQL a eu des problèmes intermittents, mais grâce à l'auto-healing de K3s, je n'y ai vu que du feu. Le service est resté disponible sans aucune intervention manuelle.
C'est exactement ce genre de résilience qui justifie la migration d'un Docker Compose classique vers Kubernetes.