Maintenant que l’ensemble des serveurs dispose du nécessaire pour y déployer un cluster Kubernetes, on va pouvoir passer au traitement des load balancer.
Pour mémoire, comme indiqué dans la présentation de l’architecture, nous allons utiliser deux instances de gestion de charge séparées. Le première va servir à traiter les flux en provenance de l’extérieur vers les applications conteneurisées sur les nœuds du LAN, la seconde va traiter les flux en provenance de l’extérieur vers les applications conteneurisées en DMZ.
C’est un choix assumé au départ d’avoir un seul cluster pour différentes zones d’hébergement. C’est un des avantages de Kubernetes, de pouvoir dédier des nodes à tel ou tel type de fonction.
Cela nécessite aussi d’avoir une bonne compréhension du fonctionnement réseau de Kubernetes.
Il est très important de savoir que par défaut, toutes les applications déployées sur K8S peuvent échanger les unes avec les autres.
Lorsqu’on initie un cluster, on doit choisir un fournisseur réseau, le fameux CNI (Container Network Interface). Celui-ci détermine les fonctionnalités réseau supportées avec des capacités plus ou moins avancées qu’on pourra mettre en œuvre au sein du cluster.
Mais peu importe le choix du CNI (Container Network Interface), par défaut, tout un réseau interne à K8S va se monter, permettant aux applications de se joindre les unes les autres, peu importe les IPS réelles des nœuds et leur emplacement réseau. C’est pourquoi il sera nécessaire d’employer des éléments tels que des "network policie" pour réguler le trafic en interne, surtout lorsque, comme c’est le cas ici, des nœuds sont situés en DMZ et en LAN.
Néanmoins ceci est valable uniquement de l’intérieur du cluster, de l’extérieur, aucune application n’est exposée naturellement. Pour cela il faudra se baser sur toute une logique particulière, qu’on peut appréhender de différentes manières.
Dans l’architecture proposée, le premier point d’entrée pour accéder à une application du cluster est une adresse de loadbalancer qui varie en fonction de l’hébergement de l’application cible : en LAN ou en DMZ.
Ces loadbalancer auront pour mission de rerouter le trafic vers l’un des nodes qui correspond à la zone réseau voulue.
Pour ça on va exploiter HAproxy et Keepalived.
Cliquez sur l'image pour l'agrandir.
HAproxy est une solution open source de référence pour le loadbalancing. Il fait partie des logiciels de répartition de charge et de proxy les plus utilisés au monde, notamment pour les applications TCP et HTTP.
Cliquez sur l'image pour l'agrandir.
HAProxy a été créé en 2000 par Willy Tarreau, un développeur français (qui est également un contributeur historique et majeur du noyau Linux). Il a conçu cet outil pour répondre aux besoins critiques de performance et de stabilité des infrastructures web qui commençaient à faire face à des pics de trafic importants au début des années 2000.
C’est donc un logiciel robuste, toujours en activité (la 3.4.3 a été publiée le 29 juillet 2026) avec un fort historique et une base de connaissance très importante.
Le risque principal autour de l’usage d’un serveur HAproxy reste le SPOF, pour Single Point of Failure, ou point de défaillance unique. Si toutes les connexions doivent transiter par ce dernier, sa défaillance entraine une coupure générale de tous les flux.
C’est pourquoi, dans l’architecture cible (et contrairement à mon ancien cookbook), ce n’est pas un serveur qui va être déployé, mais deux, par environnement (LAN/DMZ).
Chaque paire de HAproxy s’occupera d’une VIP pour Virtual IP. Cette IP va pouvoir « transiter » d’une instance HAproxy à une autre fonction de l’état des serveurs.
Si l’une des VMs rattachées à une paire est défaillante, alors qu’elle portait la gestion de l’IP virtuelle, la seconde VM va pouvoir le détecter et reprendre la gestion de l’IP en question pour continuer d’assurer le trafic.
Ce mécanisme va pouvoir se faire via VRRP
VRRP (Virtual Router Redundancy Protocol) est un protocole réseau conçu pour augmenter la disponibilité et la fiabilité des chemins de routage.
Bien qu'il ait été créé à l'origine pour les routeurs physiques, il est aujourd'hui massivement utilisé dans les infrastructures web pour sécuriser les répartiteurs de charge, notamment HAProxy.
L'idée centrale du VRRP est de regrouper plusieurs instances de routage (ici HAproxy) pour qu'elles apparaissent comme un seul point d’entrée logique (virtuelle) aux yeux du réseau et des clients.
Voici les concepts clés :
Pour implémenter VRRP, on a retenu l’usage de Keepalived
Cliquez sur l'image pour l'agrandir.
Keepalived est également un logiciel de routage open-source écrit en C, conçu pour fournir des fonctionnalités simples et robustes de haute disponibilité et de répartition de charge spécifiquement pour les infrastructures Linux.
Ici aussi, l’origine est française, puisqu' il a été créé en 2001 par Alexandre Cassen. Le projet est profondément lié à l'écosystème Linux et évolue en étroite collaboration avec les développements de la pile réseau du noyau (notamment Netfilter et IPVS).
Keepalived s'articule autour de trois modules principaux qui en font un outil redoutable pour la résilience réseau :
Concernant l’implémentation de l’ensemble, l’objectif est résumé dans le tableau suivant :
| Zone | Noeud | VIP | VRID | Backend |
|---|---|---|---|---|
| LAN | prdnlbhap501 / 502 | 192.168.10.187/24 | 51 | workers LAN, NodePorts 30080-30443 80/443 |
| DMZ | prdnlbhap510 / 511 | 192.168.5.187/24 | 52 | workers DMZ, NodePorts 31080-31443 80/443 |
Keepalived assurera la disponibilité de l’adresse virtuelle extérieure (quel nœud porte la VIP). HAProxy assurera la répartition du trafic (quel worker K8S reçoit la requête).
À noter que l'API Kubernetes (TCP 6443) n’est pas dans le scope. Elle sera traitée par kube-vip sur les control planes (voir l’architecture présentée en étape 0). Nos load balancer HAProxy ne doivent servir qu’au trafic applicatif.
Un mot aussi sur la notion de VRID qui apparait dans le tableau. Il s’agit du « Virtual Router ID ». C’est l’identifiant du routeur virtuel au sens VRRP. Chaque instance qui participe au routage doit être rattachée à ce même identifiant. Comme dans notre cas, on a deux pairs de loabalancer à gérer, on retient un VRID par pair. Étant donné qu’un pare-feu existe entre chacune de ces paires, on aurait pu choisir un VRID identique, mais c’est plus simple de les différentier pour une meilleure compréhension.
Voici également quelques spécificités associées à la configuration cible
Il est important aussi de comprendre les mécanismes qu’on va mettre en place pour sécuriser les paires HA.
Plusieurs niveaux de surveillance vont être appliqués.
Un premier niveau via VRRP. Chaque membre d’une paire va émettre une annonce à destination de son voisin (unicast). Si aucune des trois annonces ne répond, l’instance émettrice va considérer son pair comme hors service. Dans le cas du serveur HAproxy de BACKUP, il passera MASTER. A noter qu'en production, il n'est pas rare d'avoir des liens de heartbeat dédiés.
Ce premier niveau va couvrir les cas de panne suite à un nœud éteint, un kernel panic, une coupure réseau et autre panne franche.
Un second niveau plus avancé va être mis en place à l’aide d’un script. Celui-ci va permettre de surveiller l’état du service HAproxy sur le nœud lui-même. Cela permettra d’établir la perte du service et de couvrir le cas où un nœud est UP (réponse à l’annonce VRRP), mais avec un service HAproxy en erreur.
Ce second niveau permettra de traiter les problèmes liés à HAproxy.
Enfin, les serveurs HAproxy, eux-mêmes surveilleront leur backend. Ce n’est pas lié aux bascules de VIP, mais à l’envoi des paquets reçu uniquement au nœud du cluster K8S capable de traiter la demande. Ce contrôle se fera via un simple check sur les ports TCP des nodes K8S.
En résumé
| Panne | Détection | VIP déplacé après |
|---|---|---|
| Nœud MASTER (HAproxy) coupé | Silence VRRP | 3,6s environ |
| HAproxy HS | Via script chk_haproxy (2 tentative toutes les 2s) | 4s environ |
| Worker Traefik HS | Tcp check | Pas de bascule de la VIP, mais éjection du nœud HS au bout de 6s environ |
Cela pourrait être amélioré par la suite, par exemple le check de HAproxy qui ne s’attarde que sur l’état du service HAproxy et pas forcément sur sa saturation ou autres problèmes plus subtil. Mais c’est déjà un bon début.
Comme pour les étapes précédentes, le but va être d’utiliser Ansible pour déployer et configurer les briques HAproxy et Keepalived
Avant de continuer, je vous suggère de jeter un œil, ou de relire, l’article de l’étape 3 sur Ansible et la logique sous-jacente.
Concernant ansible, la première chose est de faire appel au playbook xkub_20_haproxy.yml dont voici le contenu:
---
# Paires HAProxy + keepalived (VIP LAN et VIP DMZ).
- name: Load balancers applicatifs LAN et DMZ
hosts: nlb_haproxy_lan:nlb_haproxy_web
become: true
roles:
- xkub_haproxy_keepalived
playbooks/xkub_20_haproxy.yml
L’objectif est d’associer le rôle xkub_haproxy_keepalived aux serveurs de l’inventaire présent dans les groupes nlb_haproxy_lan et nlb_haproxy_web.
En effet, contrairement aux étapes précédentes, ici, ce ne sont que les serveurs correspondant à la nomenclature prdnlbhap qui nous intéressent et qui ne sont pas membre du cluster K8S.
Attardons-nous sur le rôle xkub_haproxy_keepalived
Comme d’habitude dans le dossier default, au niveau du fichier main.yml, on va retrouver les variables propres à ce rôle.
---
# Rôle xkub_haproxy_keepalived — valeurs par défaut.
# Les variables de zone (haproxy_zone, haproxy_vip, haproxy_backend_*,
# haproxy_peer_group, xkub_haproxy_keepalived_router_id) sont définies dans
# inventory/group_vars/nlb_haproxy_{lan,web}.yml.
xkub_haproxy_keepalived_packages:
- haproxy
- keepalived
- psmisc # fournit killall, utilisé par le check keepalived
# Interface portant la VIP. Sur Rocky Linux 10 virtualisé XCP-ng, les cartes
# xen-netfront sont nommées enX0 — à vérifier sur les VMs (`ip -br a`).
xkub_haproxy_keepalived_vrrp_interface: enX0
# Le MASTER de chaque paire est le premier hostname (tri alphabétique) du
# groupe : prdnlbhap501 (LAN) et prdnlbhap510 (DMZ).
xkub_haproxy_keepalived_is_master: "{{ inventory_hostname == (groups[haproxy_peer_group] | sort | first) }}"
xkub_haproxy_keepalived_priority: "{{ 150 if xkub_haproxy_keepalived_is_master | bool else 100 }}"
# Proxy protocol vers Traefik (send-proxy) : désactivé tant que Traefik n'est
# pas configuré pour l'accepter — activer les deux côtés ensemble.
xkub_haproxy_keepalived_use_proxy_protocol: false
# Timeouts repris de l'existant PhotonOS
xkub_haproxy_keepalived_timeout_client: 30s
xkub_haproxy_keepalived_timeout_server: 30s
xkub_haproxy_keepalived_timeout_connect: 30s
roles/xkub_haproxy_keepalived/defaults/main.yml
L’enjeu principal est d’installer les paquets, notamment haproxy et keepalived, via la variable xkub_haproxy_keepalived_packages. Étant donné qu’on opère sur une infrastructure XCP-ng, le nom des interfaces à utiliser sur les VMs est enX0.
Petite particularité, on n’activera pas de suite le suivi des instances Traefik coté HAProxy, puisqu’elles n’existent pas encore.
Côté dossier handlers maintenant, le contenu du fichier main est le suivant
---
- name: Reload haproxy
ansible.builtin.systemd_service:
name: haproxy
state: reloaded
- name: Restart keepalived
ansible.builtin.systemd_service:
name: keepalived
state: restarted
roles/xkub_haproxy_keepalived/handlers/main.yml
Ce sont les commandes de redémarrage pour HAproxy et keepalived. Rien de spécial ici.
On peut enchainer sur le dossier task, ou le fichier main.yml ne fait qu’afficher l’enchainement des taches à réaliser en faisant référence aux autres yamls.
---
# Rôle xkub_haproxy_keepalived — paires de load balancers applicatifs LAN et DMZ.
# Frontends 80/443 sur la VIP → NodePorts Traefik des workers de la zone.
- name: Installation des paquets
ansible.builtin.import_tasks: 01_install.yml
tags:
- xkub
- xkub.haproxy_keepalived
- xkub.haproxy_keepalived.install
# Le pare-feu est ouvert AVANT le démarrage des services : dans l'autre sens,
# le premier déploiement laisse une fenêtre où keepalived échange du VRRP encore
# bloqué, ce qui produit une double élection MASTER transitoire (les deux nœuds
# se croient seuls). Ouvrir d'abord puis démarrer est aussi le sens sûr du point
# de vue sécurité : on n'expose jamais un service avant sa règle.
- name: Politique firewalld
ansible.builtin.import_tasks: 04_firewalld.yml
tags:
- xkub
- xkub.haproxy_keepalived
- xkub.haproxy_keepalived.firewalld
- name: Configuration HAProxy
ansible.builtin.import_tasks: 02_haproxy.yml
tags:
- xkub
- xkub.haproxy_keepalived
- xkub.haproxy_keepalived.haproxy
- name: Configuration keepalived (VIP)
ansible.builtin.import_tasks: 03_keepalived.yml
tags:
- xkub
- xkub.haproxy_keepalived
- xkub.haproxy_keepalived.keepalived
roles/xkub_haproxy_keepalived/tasks/main.yml
Notez que l'ordre des tâches est important. On commence par installer les paquets, puis on ouvre le pare-feu, ensuite on configure HAproxy et enfin Keepalived. C'est pourquoi finalement j'ai du basculer l'éxécution du fichier 04_firewalld.yml, intialement prévu pour etre lancé à la fin, avant 02_haproxy.yml
Il faudrait procéder à une rénumerotation des fichiers pour être cohérent
Le premier fichier 01_install.yml traite de l’installation des composants
---
- name: Installer haproxy, keepalived et psmisc
ansible.builtin.dnf:
name: "{{ xkub_haproxy_keepalived_packages }}"
state: present
update_cache: true
# Le BACKUP doit pouvoir binder la VIP qu'il ne porte pas encore : sans ce
# sysctl, haproxy refuse de démarrer sur le nœud passif.
- name: Autoriser le bind sur une IP non locale (VIP)
ansible.posix.sysctl:
name: net.ipv4.ip_nonlocal_bind
value: "1"
sysctl_file: /etc/sysctl.d/90-haproxy.conf
state: present
reload: true
# SELinux reste ENFORCING (règle 11) : on autorise explicitement haproxy à se
# connecter aux ports non standards (NodePorts 30000-32767) via le booléen dédié.
- name: Autoriser haproxy à joindre les NodePorts (SELinux)
ansible.posix.seboolean:
name: haproxy_connect_any
state: true
persistent: true
roles/xkub_haproxy_keepalived/tasks/01_install.yml
On installe Haproxy et Keepalived sur chaque serveur ciblé.
Il est nécessaire d’intervenir sur le systctl des VMs, car sans cela, un serveur BACKUP coté HAproxy ne pourrait démarrer son service, car la VIP auquelle il serait rattaché ne serait pas encore disponible (VRRP associe l'IP (la VIP) uniquement sur le MASTER, on autorise donc le kernel à exécuter HAproxy même si l'IP qu'il doit utiliser n'est pas disponible sur le serveur de BACKUP).
Pour des questions de sécurité, on ne retire pas le support de SElinux, on autorise par contre HAproxy à exploiter toute sorte de ports, autres que ceux autorisés par défaut par SElinux.
On passe à 02_haproxy.yml
---
# Un seul template pour les deux zones : la zone est portée par les variables
# de groupe (nlb_haproxy_lan / nlb_haproxy_web) — plus de variantes _lan/_dmz.
- name: Déployer la configuration HAProxy
ansible.builtin.template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
owner: root
group: root
mode: "0644"
validate: haproxy -c -f %s
notify: Reload haproxy
- name: Activer et démarrer HAProxy
ansible.builtin.systemd_service:
name: haproxy
state: started
enabled: true
roles/xkub_haproxy_keepalived/tasks/02_haproxy.yml
Ici les choses sont basiques, puisqu’on demande simplement à récupérer le template de configuration HAproxy (qu’on va voir juste après) pour le copier sur les serveurs et redémarrer le service HAproxy (via les commandes définies dans le handler) pour prendre en compte cette nouvelle config.
On passe à 03_keepalived.yml
---
# VRRP en unicast entre les deux membres de la paire (pas d'auth_pass : le
# mécanisme VRRPv2 est faible et déconseillé ; l'unicast + le filtrage
# firewalld limitent la surface). MASTER = prdnlbhap501 / prdnlbhap510.
- name: Déployer la configuration keepalived
ansible.builtin.template:
src: keepalived.conf.j2
dest: /etc/keepalived/keepalived.conf
owner: root
group: root
mode: "0600"
notify: Restart keepalived
- name: Activer et démarrer keepalived
ansible.builtin.systemd_service:
name: keepalived
state: started
enabled: true
roles/xkub_haproxy_keepalived/tasks/03_keepalived.yml
Même combat pour keepalived, on récupère la conf (qu’on va voir également juste après) et redémarrage du service dans la foulée.
Enfin on termine par 04_firewalld.yml
---
# firewalld reste actif : on ouvre 80/443 (frontends) et le
# protocole VRRP entre les membres de la paire.
- name: S'assurer que firewalld est actif
ansible.builtin.systemd_service:
name: firewalld
state: started
enabled: true
- name: Ouvrir les services HTTP/HTTPS
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
immediate: true
state: enabled
loop:
- http
- https
# VRRP est restreint aux SEULES IP des pairs de la paire, jamais ouvert à
# l'ensemble du sous-réseau . C'est la
# contrepartie du choix de ne pas utiliser d'`auth_pass` : l'authentification
# VRRPv2 étant faible par conception, le filtrage réseau est la vraie mesure
# de protection. Sans `source address`, n'importe quel hôte du même L2 pourrait
# annoncer une priorité supérieure, s'emparer de la VIP et intercepter le
# trafic applicatif de la zone. La liste des pairs est dérivée du même groupe
# d'inventaire que les `unicast_peer` de keepalived.conf.j2 : les deux restent
# donc cohérents par construction.
- name: Autoriser le protocole VRRP depuis les pairs de la paire uniquement
ansible.posix.firewalld:
rich_rule: >-
rule family="ipv4"
source address="{{ hostvars[item].ansible_host }}/32"
protocol value="vrrp" accept
permanent: true
immediate: true
state: enabled
loop: "{{ groups[haproxy_peer_group] | difference([inventory_hostname]) | sort }}"
# Nettoyage de l'ancienne règle non restreinte, laissée en place par les runs
# antérieurs au durcissement : sans cette tâche, elle survivrait indéfiniment
# dans la configuration permanente de firewalld et continuerait d'autoriser
# VRRP depuis n'importe quelle source.
- name: Retirer l'ancienne règle VRRP sans restriction de source
ansible.posix.firewalld:
rich_rule: rule protocol value="vrrp" accept
permanent: true
immediate: true
state: disabled
roles/xkub_haproxy_keepalived/tasks/04_firewalld.yml
Le but est d’ouvrir les ports nécessaires sur les serveurs et d’autoriser le trafic propre à HAproxy et au protocole VRRP.
C’est ici qu’on joue sur le côté unicast pour que les annonces VRRPs ne se fassent qu’entre les IPS des serveurs HAproxy appartenant à la même paire. On joue sur les variables d’inventaire pour obtenir les bonnes règles.
On passe au dossier le plus intéressant, soit le dossier templates
On y retrouve deux fichiers
haproxy.cfg.j2
# {{ ansible_managed }}
# HAProxy zone {{ haproxy_zone | upper }} — VIP {{ haproxy_vip }}
# Frontends 80/443 -> NodePorts Traefik {{ haproxy_zone }} (décision D2).
# L'API Kubernetes (6443) n'est pas frontée ici : kube-vip (décision D1).
global
log /dev/log local0
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode tcp
log global
option tcplog
option dontlognull
timeout client {{ xkub_haproxy_keepalived_timeout_client }}
timeout server {{ xkub_haproxy_keepalived_timeout_server }}
timeout connect {{ xkub_haproxy_keepalived_timeout_connect }}
frontend fe_traefik_{{ haproxy_zone }}_http
bind {{ haproxy_vip }}:80
default_backend bk_traefik_{{ haproxy_zone }}_http
frontend fe_traefik_{{ haproxy_zone }}_https
bind {{ haproxy_vip }}:443
default_backend bk_traefik_{{ haproxy_zone }}_https
backend bk_traefik_{{ haproxy_zone }}_http
option tcp-check
{% for host in groups[haproxy_backend_group] | sort %}
server {{ host.split('.')[0] }} {{ hostvars[host].ansible_host }}:{{ haproxy_backend_port_http }} check
{% endfor %}
backend bk_traefik_{{ haproxy_zone }}_https
option tcp-check
{% for host in groups[haproxy_backend_group] | sort %}
server {{ host.split('.')[0] }} {{ hostvars[host].ansible_host }}:{{ haproxy_backend_port_https }} check{{ ' send-proxy' if xkub_haproxy_keepalived_use_proxy_protocol | bool else '' }}
{% endfor %}
roles/xkub_haproxy_keepalived/templates/haproxy.cfg.j2
C’est le template associé à la configuration de HAproxy. Il va s’appuyer sur les variables du rôle et sur le nom des hosts récupéré dans l’inventaire Ansible.
La conf est assez simple. On retrouve l’usage du mode « tcp » comme expliqué précédemment.
Ensuite on définit les interfaces de frontend, soit l’interface qui va exploiter la VIP VRRP gérée par Keealived.
Ensuite on définit les serveurs de backend, soit les nodes K8S vers lesquels renvoyer les requêtes.
Grâce à la notion de variable et d’inventaire coté Ansible le contenu de ce fichier sera dynamiquement rempli à la copie vers les serveurs cibles en tenant compte de l’emplacement réseau du serveur (LAN/DMZ)
C’est au tour de la configuration de Keepalived keepalived.conf.j2:
# {{ ansible_managed }}
# keepalived zone {{ haproxy_zone | upper }} — VIP {{ haproxy_vip }}/{{ haproxy_vip_prefix }}
global_defs {
router_id {{ inventory_hostname.split('.')[0] }}
}
# La VIP ne reste sur ce nœud que si haproxy tourne
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
fall 2
rise 2
}
vrrp_instance VI_{{ haproxy_zone | upper }} {
state {{ 'MASTER' if xkub_haproxy_keepalived_is_master | bool else 'BACKUP' }}
interface {{ xkub_haproxy_keepalived_vrrp_interface }}
virtual_router_id {{ xkub_haproxy_keepalived_router_id }}
priority {{ xkub_haproxy_keepalived_priority }}
advert_int 1
unicast_src_ip {{ ansible_host }}
unicast_peer {
{% for host in groups[haproxy_peer_group] | sort if host != inventory_hostname %}
{{ hostvars[host].ansible_host }}
{% endfor %}
}
virtual_ipaddress {
{{ haproxy_vip }}/{{ haproxy_vip_prefix }}
}
track_script {
chk_haproxy
}
}
roles/xkub_haproxy_keepalived/templates/keepalived.conf.j2
Ici aussi elle est rendue dynamique grâce aux variables du rôle et de l’inventaire.
On détermine l’identifiant du routeur (router_id) à l’intérieur de la paire en se basant sur le nom du serveur qui hébergera la configuration.
La section « vrrp_script chk_haproxy » permet de définir les commandes de contrôles à jouer pour checker l’état du service HAproxy. On appel pas un script externe on définit directement les commandes à utiliser:
"/usr/bin/killall -0 haproxy"
interval 2
fall 2
rise 2
Puis on définit le VRID avec la notion de MASTER et de BACKUP pour indiquer à VRRP quel doit être la priorité pour chaque serveur dans la paire HA. Cette notion est importante, car par défaut, on va attribuer une priorité plus élevée au premier serveur de chaque pair pour définir qui est MASTER en situation nominal.
Le tour d’horizon est bouclé, il ne reste plus qu’à appliquer
Pour ça on se rend dans WSL puis dans l’arborescence Ansible pour lancer l’exécution du playbook
ansible-playbook playbooks/xkub_20_haproxy.yml
Cliquez sur l'image pour l'agrandir.
Chaque serveur parmi les quatre qui nous intéresse va être traité et les différents composants installés avec leur configuration associés.
(Comme d’habitude on n’hésitera pas à relancer le playbook plusieurs fois pour s’assurer qu’il est rejouable à souhait)
Cliquez sur l'image pour l'agrandir.
Si tout va bien, on devrait pouvoir retrouver sur chacun des serveurs la configuration HAproxy et Keepalived en place.
Vérifions sur les serveurs associé à la paire du lan, soit prdnlbhap501/prdnlbhap502
Voici la configuration HAproxy sur chaque node (/etc/haproxy/haproxy.cfg)
# Ansible managed
# HAProxy zone LAN — VIP 192.168.10.187
# Frontends 80/443 -> NodePorts Traefik lan (décision D2).
# L'API Kubernetes (6443) n'est pas frontée ici : kube-vip (décision D1).
global
log /dev/log local0
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode tcp
log global
option tcplog
option dontlognull
timeout client 30s
timeout server 30s
timeout connect 30s
frontend fe_traefik_lan_http
bind 192.168.10.187:80
default_backend bk_traefik_lan_http
frontend fe_traefik_lan_https
bind 192.168.10.187:443
default_backend bk_traefik_lan_https
backend bk_traefik_lan_http
option tcp-check
server prdk8smin501 192.168.10.171:30080 check
server prdk8smin502 192.168.10.172:30080 check
backend bk_traefik_lan_https
option tcp-check
server prdk8smin501 192.168.10.171:30443 check
server prdk8smin502 192.168.10.172:30443 check
prdnlbhap501 — /etc/haproxy/haproxy.cfg
# Ansible managed
# HAProxy zone LAN — VIP 192.168.10.187
# Frontends 80/443 -> NodePorts Traefik lan (décision D2).
# L'API Kubernetes (6443) n'est pas frontée ici : kube-vip (décision D1).
global
log /dev/log local0
maxconn 4000
user haproxy
group haproxy
daemon
defaults
mode tcp
log global
option tcplog
option dontlognull
timeout client 30s
timeout server 30s
timeout connect 30s
frontend fe_traefik_lan_http
bind 192.168.10.187:80
default_backend bk_traefik_lan_http
frontend fe_traefik_lan_https
bind 192.168.10.187:443
default_backend bk_traefik_lan_https
backend bk_traefik_lan_http
option tcp-check
server prdk8smin501 192.168.10.171:30080 check
server prdk8smin502 192.168.10.172:30080 check
backend bk_traefik_lan_https
option tcp-check
server prdk8smin501 192.168.10.171:30443 check
server prdk8smin502 192.168.10.172:30443 check
prdnlbhap502 — /etc/haproxy/haproxy.cfg
En fait pour HAproxy, la configuration est identique, puisque chaque serveur doit pouvoir opérer les mêmes fonctions. C'est davantage au niveau de Keepalived que ça se joue pour savoir qui doit porter la VIP.
Voici le contenu de la configuration (/etc/keepalived/keepalived.conf)
# Ansible managed
# keepalived zone LAN — VIP 192.168.10.187/24
global_defs {
router_id prdnlbhap501
}
# La VIP ne reste sur ce nœud que si haproxy tourne
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
fall 2
rise 2
}
vrrp_instance VI_LAN {
state MASTER
interface enX0
virtual_router_id 51
priority 150
advert_int 1
unicast_src_ip 192.168.10.185
unicast_peer {
192.168.10.186
}
virtual_ipaddress {
192.168.10.187/24
}
track_script {
chk_haproxy
}
}
prdnlbhap501 — /etc/keepalived/keepalived.conf
# Ansible managed
# keepalived zone LAN — VIP 192.168.10.187/24
global_defs {
router_id prdnlbhap502
}
# La VIP ne reste sur ce nœud que si haproxy tourne
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
fall 2
rise 2
}
vrrp_instance VI_LAN {
state BACKUP
interface enX0
virtual_router_id 51
priority 100
advert_int 1
unicast_src_ip 192.168.10.186
unicast_peer {
192.168.10.185
}
virtual_ipaddress {
192.168.10.187/24
}
track_script {
chk_haproxy
}
}
prdnlbhap502 — /etc/keepalived/keepalived.conf
La différence est subtile, mais on peut remarquer que le nœud MASTER (501) dispose d’une priorité de 150 alors que le BACKUP (502) dispose d’une priorité de 100. C’est de cette manière que, par défaut, l'IP de la VIP 192.168.10.187 va être portée et active sur prdnlbhap501.
On remarquera aussi les adresses d’unicast utilisées pour diffuser le heartbeat qui correspondent aux adresses IP réelless des serveurs.
Je ne vais pas détailler les configurations en DMZ sur les serveurs prdnlbhap510/prdnlbhap511
C’est exactement la même chose, mais avec la plage d’IP du réseau de DMZ (192.168.5.x).
Maintenant que tout cela est fonctionnel.
C’est le moment de tester.
Déjà, utilisons Ansible pour lister l’état des IP sur les interfaces. Pour cela on peut utiliser la commande
ansible nlb_haproxy_lan:nlb_haproxy_web -b -m shell -a "ip -br a show enX0"
Cliquez sur l'image pour l'agrandir.
On remarque en sortie que seuls les nodes MASTER disposent de deux IPs puisqu’ils ont leur IP réelle et l’IP de la VIP
Ce qui correspond bien à notre situation nominale.
C’est quelque chose qu’on peut vérifier directement sur les serveurs (ip addr)
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
Testons maintenant la résilience de l’ensemble.
On commence par faire un arrêt brutal du serveur « prdnlbhap501 » soit le MASTER sur la paire LAN.
Pour évaluer la coupure, on ping en permanence l’IP de VIP. On s’aperçoit alors qu’un paquet est perdu, juste le temps que le BACKUP, soit prdnlbhap502, récupère la gestion de la VIP
Cliquez sur l'image pour l'agrandir.
On peut le confirmer en faisant un « ip addr » sur ce serveur pour se rendre compte que maintenant la VIP est bien rattaché à ce dernier.
Cliquez sur l'image pour l'agrandir.
Utilisons maintenant la paire en DMZ pour simuler l’autre scénario, soit la perte de HAproxy, mais sans éteindre le node.
Cliquez sur l'image pour l'agrandir.
Pour cela on stoppe simplement le service HAproxy sur prdnlbhap510. Ici aussi, via un ping continu, on observe une perte d'un paquet.
Cliquez sur l'image pour l'agrandir.
C’est bien le serveur prdnlbhap511 qui a récupéré la VIP
Cliquez sur l'image pour l'agrandir.
Dans cette situation, l’interface réelle de prdnlbhap510 est toujours active et répond. Cependant, puisque HAProxy n’est plus opérationnel, le contrôle de keepalived surveillant HAProxy a incité prdnlbhap510 à abandonner l’adresse IP virtuelle (VIP), qui a alors été transférée vers prdnlbhap511 (en réalité, les priorités ont été modifiées pour que prdnlbhap511 devienne MASTER).
Si maintenant on rétablit le service HAproxy, sur prdnlbhap510 alors la VIP va revenir sur ce dernier.
Cliquez sur l'image pour l'agrandir.
C’est d’ailleurs un point qu’on n’a pas précisé, mais la configuration est en mode rollback. Si les conditions permettent à nouveau d’activer le MASTER, alors la VIP sera automatiquement rétablie vers celui-ci.
Nous voilà arrivés au bout de cet article. L’usage de Ansible nous a permis d’automatiser une fois de plus la configuration des composants nécessaires, tout en me servant de base pour expliquer le fonctionnement et la logique derrière HAproxy/Keepalived. Là aussi l’IA telle que décrit en début de cookbook m’a permis d’accélérer l’écriture des yamls et des configurations, mais je n’ai pas pour autant perdu la compréhension du sujet.
Kubernetes est une chose, mais il est important de savoir que le plus souvent des composants tiers sont tout de même nécessaires pour traiter de la mise en ligne d’un cluster K8S. Ces éléments sont aussi critiques que les nodes Kubernetes eux-mêmes et il faut les déployer en connaissance de cause avec le bon niveau de résilience.
Disposer de plusieurs serveurs au sein d’un cluster K8S pour une bonne disponibilité n’est pas efficace si les composants externes utiles à l’accès aux applications du cluster ne sont pas résilients eux-mêmes.
Le duo HAproxy/Keepalived est un standard reconnu et robuste, il faut juste les implémenter intelligemment et en lien avec nos besoins.
Maintenant qu’on a traité de ces problématiques, on va pouvoir poursuivre sur le déploiement de Kubernetes à proprement parler dans le prochain article à paraitre.