Construire un cluster Docker Swarm Zero-SPOF sur maillage WireGuard : Quorum Raft, MTU VXLAN et filtrage
Assembler un cluster haute disponibilité sur plusieurs serveurs distants révèle très vite que la redondance applicative ne vaut rien si le consensus distribué, la taille maximale des paquets (MTU) sur le tunnel chiffré ou les règles pare-feu hôtes n’ont pas été pensés comme un tout cohérent.
La première erreur classique consiste à déployer deux nœuds managers en pensant obtenir de la haute disponibilité. Or le protocole Raft impose une majorité stricte de (N/2) + 1 votants : sur deux nœuds, la majorité vaut deux. Dès qu’un serveur redémarre pour appliquer un correctif de sécurité noyau, le second perd le quorum et fige toute reprogrammation des services. L’ajout d’un troisième manager léger déporté dans une autre région (configuré en disponibilité Drain pour ne porter aucun conteneur métier) restaure une tolérance de panne réelle à moindre coût.
Le second piège se cache dans la double encapsulation réseau. Lorsque le réseau overlay VXLAN de Docker Swarm circule à l’intérieur d’un maillage chiffré WireGuard ou Tailscale (MTU 1280 octets), l’en-tête VXLAN de 50 octets provoque des dépassements de trame silencieux : les pings et requêtes HTTP courtes passent, mais les réponses TLS volumineuses ou les synchronisations MySQL pendent indéfiniment. Déclarer explicitement une MTU de 1200 octets sur le réseau overlay ou activer le clamping TCP MSS élimine instantanément cette instabilité.
Enfin, côté exposition publique, coupler le cluster à des connecteurs sortants Cloudflare Tunnel répliqués sur les nœuds actifs permet de fermer les ports 80 et 443 sur les interfaces publiques des VPS tout en conservant un routage interne résilient via le maillage Swarm.
# MTU WireGuard/Tailscale = 1280 -> on fixe l'overlay VXLAN à 1200 octets docker network create \ --driver overlay \ --opt encrypted \ --opt com.docker.network.driver.mtu=1200 \ --attachable \ <reseau-overlay-chiffre> # Nœud arbitre distant : participe au quorum Raft mais ne reçoit pas les charges applicatives docker node update --availability drain <noeud-arbitre-distant>