Équilibrage de charge avec IPVS

Magazine
Marque
Linux Pratique
HS n°
Numéro
55
Mois de parution
octobre 2022
Spécialité(s)


Résumé

IP Virtual Server (IPVS) est un équilibreur de charge agissant au niveau 4 du modèle OSI. Il est implémenté sous forme d’un module noyau s’appuyant sur le framework Netfilter, ce qui le rend efficace sur l’équilibrage des services par rapport à leurs ports TCP/UDP, mais totalement agnostique aux protocoles applicatifs transportés (LDAP, HTTP, etc.).


Body

Un service réseau est un processus à l’écoute sur une interface attendant des requêtes depuis des clients distants. Un exemple typique est le serveur web. Il se lie aux ports TCP/80 pour accepter le trafic en clair (HTTP) et TCP/443 pour le trafic chiffré (HTTPS). Ces services réseaux étaient historiquement localisés sur une machine serveur physique. Ces serveurs physiques ont peu à peu laissé la place aux machines virtuelles. Cette évolution a grandement rationalisé l’attribution des ressources de machines de plus en plus puissantes, donc disproportionnées par rapport à l’utilisation que pourrait en faire un service réseau isolé. Enfin, la tendance actuelle est la conteneurisation pour encore redécouper les machines virtuelles si besoin (on peut évidemment également faire tourner des conteneurs sur des machines physiques directement). Pour ce qui nous intéresse, cette flexibilité dans l’attribution des ressources favorise grandement le placement de plusieurs instances du même service réseau pour en accroître la disponibilité. Pour répartir les requêtes entre ces différentes instances du service et vérifier leur disponibilité, il faut mettre en place un équilibreur de charge en frontal de ceux-ci. On parle alors d’instances de services équilibrés.

IPVS (IP Virtual Server) est un équilibreur de charge niveau 4, ce qui correspond à la couche transport du modèle OSI. La couche 3, réseau, s’occupe d’acheminer le paquet sur la bonne interface réseau avec, évidemment, le concours des couches inférieures : liaison (2) et physique (1). La couche transport démultiplexe le trafic reçu et livre le paquet au bon processus en écoute sur le réseau en l’adressant par le port avec lequel il s’est lié à l’interface. Le rôle d’IPVS est d’organiser les instances de services réseaux en groupes et de rediriger le trafic du client vers une des instances du service demandé. La disponibilité de l’équilibreur de charge lui-même est donc critique, car la disponibilité d’un ou plusieurs services dépendent de lui. En général, ces systèmes d’équilibrage sont redondés.

La redondance d’un équilibreur de charge fonctionne sur le principe de l’IP virtuelle. Les équilibreurs de charge vont se « partager » une adresse IP virtuelle. Un équilibreur de charge sera maître du lien tandis que les autres seront positionnés en secours. Lorsque le maître tombe, un des secours prend le relai (par ordre de priorité). Le protocole le plus répandu de gestion d’IP virtuelle est VRRP (Virtual Router Redudancy Protocol). Il s’appuie sur ARP pour annoncer l’IP virtuelle sur le lien maître ou un des liens de secours. Je vais rentrer dans le détail du fonctionnement de ces dispositifs en les prenant un par un.

1. Redondance de l’équilibreur de charge

Dans cette section, nous allons voir comment mettre en place une redondance au niveau de l’équilibreur de charge. Nous allons traverser les couches de la plus basse à la plus haute en commençant par le protocole ARP qui va être manipulé par VRRP pour associer l’IP virtuelle à l’équilibreur de charge maître ou en secours. Nous manipulerons une IP virtuelle avec le service vrrpd qui implémente VRRP en espace utilisateur et nous verrons le comportement avec ARP. Enfin, nous préparerons le terrain pour la seconde partie de l’article dédiée à IPVS en installant un service keepalived qui gère l’IP virtuelle, mais propose aussi de surveiller les instances de services redondées pour renseigner IPVS en temps réel.

Notre laboratoire se compose de trois machines. Une machine nommée frontal en 172.16.0.1 avec l’adresse MAC 08:00:27:f4:11:cd, une machine nommée node1 en 172.16.0.11 avec l’adresse MAC 08:00:27:22:36:5a qui sera maître sur le lien VRRP et une machine nommée node2 en 172.16.0.12 avec l’adresse MAC 08:00:27:42:c9:c0 qui sera secours sur le lien VRRP. L’IP virtuelle sera en 172.16.0.13. J’ai choisi de mettre toutes les machines sur le même réseau IP pour éviter de surcharger l’article avec les notions de routage.

schema1-s 0

Fig. 1 : Laboratoire de test.

1.1 ARP

ARP (Address Resolution Protocol) est le protocole qui fait le lien entre la couche 2 (liaison) et la couche 3. Les machines qui sont sur un même segment LAN (Local Area Network), aussi appelé domaine de broadcast Ethernet, commutent les trames encapsulant les paquets IP grâce aux adresses MAC. Un segment LAN peut être défini : 1) physiquement par un commutateur voire des commutateurs cascadés les uns avec les autres ou 2) logiquement par des VLAN (Virtual LAN) [1]. MAC signifie Media Access Control, c’est-à-dire que c’est une adresse d’accès au media (sous-entendu physique). Ici, le media est l’interface physique connectée au commutateur avec un câble. Une fois l’interface physique localisée, le paquet IP est extrait de la trame Ethernet par le noyau (précisément la pile réseau) du système destinataire et livré à la bonne interface. Voyons avec un exemple, où la machine 172.16.0.1 (frontal) envoie une requête ICMP echo request à la machine 172.16.0.11 (node1). Nous lançons d’abord une capture sur 172.16.0.1 avec tcpdump qui ne récupère que le trafic ARP :

root@frontal:~# tcpdump -nei enp0s8 arp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes

Dans une autre fenêtre, lançons les requêtes ICMP avec l’utilitaire ping :

root@frontal:~# ping 172.16.0.11
PING 172.16.0.11 (172.16.0.11) 56(84) bytes of data.
64 bytes from 172.16.0.11: icmp_seq=1 ttl=64 time=0.281 ms
64 bytes from 172.16.0.11: icmp_seq=2 ttl=64 time=0.250 ms
...

On constate que le paquet IP est bien livré, car notre « echo request » est bien acquitté par un « echo reply ». Or si cela fonctionne, c’est que les trames contenant les paquets ont bien été commutées et donc que les machines se sont trouvées au niveau des adresses MAC. Or, nous n’avons pas précisé d’adresses MAC. C’est ici qu’ARP intervient. Revenons sur la fenêtre du tcpdump :

13:02:00.840237 08:00:27:f4:11:cd > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 172.16.0.11 tell 172.16.0.1, length 28
13:02:00.840405 08:00:27:22:36:5a > 08:00:27:f4:11:cd, ethertype ARP (0x0806), length 60: Reply 172.16.0.11 is-at 08:00:27:22:36:5a, length 46

Deux lignes sont apparues. La première est une requête « who-has » (qui possède ?) envoyée par 08:00:27:f4:11:cd qui est l’adresse MAC de 172.16.0.1 à destination de toutes les machines du segment LAN (en gros, toutes les machines connectées au commutateur). En effet, une trame envoyée à l’adresse MAC de broadcast ff:ff:ff:ff:ff:ff touche toutes les machines connectées au segment LAN. Cette requête demande quelle adresse MAC est derrière 172.16.0.11 et d’envoyer la réponse à 172.16.0.1. La deuxième ligne est la réponse en question. Elle vient de 08:00:27:22:36:5a qui est l’adresse MAC de 172.16.0.11 et elle est adressée à 08:00:27:f4:11:cd qui est bien l’adresse MAC de 172.16.0.1. La réponse est un « is-at » (localisée à) 08:00:27:22:36:5a (adresse MAC de 172.16.0.11). On notera que peu importe le nombre de requêtes ICMP qu’on peut envoyer, une fois l’échange who-has / is-at effectué, tcpdump ne bouge plus. La raison est que les systèmes intègrent un cache ARP que l’ont peut simplement consulter avec la commande nommée arp. Par exemple sur 172.16.0.1 :

root@frontal:~# arp -a
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8
? (10.0.2.2) at 52:54:00:12:35:02 [ether] on enp0s3

On y retrouve bien la machine 172.16.0.11 avec son adresse MAC 08:00:27:22:36:5a. En conséquence, une adresse IP virtuelle est une adresse qui se balade entre plusieurs adresses MAC. Nous allons simuler une permutation d’adresse MAC pour une IP en forgeant manuellement une trame « gratuitous ARP ».

Une trame « gratuitous ARP » est une réponse ARP sans qu’aucune requête n’ait été formulée. Nous allons utiliser Scapy [2] pour forger une telle requête. Disons que nous voulions que la machine node2 en 172.16.0.12 porte l’IP 172.16.0.11. Il s’agit juste ici de mettre à jour le cache ARP des machines pour que les trames destinées à 172.16.0.11 n’arrivent plus sur node1 possédant l’adresse MAC 08:00:27:22:36:5a, mais sur node2 possédant l’adresse MAC suivante :

root@node2:~# ip a s enp0s3 | grep ether
    link/ether 08:00:27:42:c9:c0 brd ff:ff:ff:ff:ff:ff

Affichons le cache ARP de la machine frontal :

root@frontal:~# arp -a
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8
? (10.0.2.2) at 52:54:00:12:35:02 [ether] on enp0s3

Il possède l’IP 172.16.0.11 qui pointe sur l’adresse MAC 08:00:27:22:36:5a qui est bien node1. Depuis une machine quelconque du segment LAN (par exemple node2), utilisons Scapy :

root@node2:~# scapy
>>> packet = Ether(dst=ETHER_BROADCAST) / ARP(op=2, psrc="172.16.0.11", pdst="0.0.0.0", hwdst=ETHER_BROADCAST)
>>> sendp(packet)
.
Sent 1 packets.
>>> exit

Les caractéristiques d’une telle trame sont qu’au niveau Ethernet son adresse MAC de destination est ff:ff:ff:ff:ff:ff (Ether(dst=ETHER_BROADCAST)) et au niveau ARP on envoie une réponse (op=2) en demandant de mettre à jour l’IP 172.16.0.11 (psrc="172.16.0.11") dans tous les caches ARP (pdst="0.0.0.0") pour toutes les machines (hwdst=ETHER_BROADCAST). Vérifions à nouveau le cache ARP de frontal :

root@frontal:~# arp -a
? (172.16.0.11) at 08:00:27:42:c9:c0 [ether] on enp0s8
? (10.0.2.2) at 52:54:00:12:35:02 [ether] on enp0s3

Voici le truchement par lequel l’IP virtuelle se promène d’une machine à une autre. Évidemment, il y a d’autres choses à configurer. Nous n’avons vu que la permutation d’adresse MAC permettant à la trame d’arriver sur la machine ayant récupéré l’IP virtuelle. Encore faut-il qu’elle accepte la trame et la traite. Allons plus en avant en manipulant VRRP.

1.2 VRRP

VRRP (Virtual Router Redundancy Protocol) est un protocole de redondance de lien. Son rôle est de réglementer les échanges entre plusieurs machines se partageant une IP virtuelle en mode maître/secours. Le maître porte l’adresse IP virtuelle, c’est-à-dire que c’est lui qui répond aux sollicitations ARP des clients souhaitant établir un lien avec cette adresse. L’élection et les réélections maître/secours se font sur une adresse multicast définie dans le protocole VRRP, la 224.0.0.18. Cette section se compose donc de deux parties : la configuration du multicast et la configuration du lien VRRP sur node1 et node2.

1.2.1 Configuration du multicast

En introduction rapide au multicast, on peut dire que c’est un système de diffusion de niveau 3. Les machines souscrivent à une adresse de multicast. Lorsqu’un paquet est envoyé à cette adresse de multicast, tous les clients ayant souscrit le reçoive. Le multicast est donc bien adapté pour tout ce qui est télévision IP, webradios et même diffusion d’images système sur un réseau. On notera que la duplication des paquets envoyés aux abonnés d’une adresse multicast est assurée par les équipements de commutation/routage. VRRP se repose donc sur le multicast pour l’élection du maître portant l’IP virtuelle. La première chose à faire sur node1 et node2 est d’activer IGMP (Internet Group Management Protocol) qui pilote la table de diffusion du trafic multicast sur chaque machine participante. Ça se passe dans le fichier /.etc/sysctl.conf :

root@node1:~# cat /.etc/sysctl.conf
...
net.ipv4.icmp_echo_ignore_broadcasts = 0

Et reproduire la manipulation sur node2. Après le redémarrage des deux machines node1 et node2, nous allons tester l’adresse 224.0.0.1 sur laquelle toutes les machines configurées en multicast doivent répondre :

root@node1:~# ping 224.0.0.1 -c 3
PING 224.0.0.1 (224.0.0.1) 56(84) bytes of data.
64 bytes from 172.16.0.11: icmp_seq=1 ttl=64 time=0.018 ms
64 bytes from 172.16.0.12: icmp_seq=1 ttl=64 time=0.166 ms
64 bytes from 172.16.0.11: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 172.16.0.12: icmp_seq=2 ttl=64 time=0.203 ms
64 bytes from 172.16.0.11: icmp_seq=3 ttl=64 time=0.032 ms

On retrouve bien nos deux machines node1 et node2. Nous pouvons maintenant les abonner au groupe multicast VRRP en 224.0.0.18. Commençons par installer le paquet smcroute sur les deux machines :

root@node1:~# apt-get install smcroute

Ensuite, nous allons abonner les deux machines au groupe multicast en modifiant le fichier /.etc/smcroute.conf :

root@node1:~# cat /etc/smcroute.conf
mgroup from enp0s3 group 224.0.0.18

Ces deux manipulations sont évidemment à reproduire sur les deux machines. Après un redémarrage, testons l’adresse de multicast VRRP :

root@node1:~# ping 224.0.0.18 -c 3
PING 224.0.0.18 (224.0.0.18) 56(84) bytes of data.
64 bytes from 172.16.0.11: icmp_seq=1 ttl=64 time=0.014 ms
64 bytes from 172.16.0.12: icmp_seq=1 ttl=64 time=0.215 ms
64 bytes from 172.16.0.11: icmp_seq=2 ttl=64 time=0.034 ms
64 bytes from 172.16.0.12: icmp_seq=2 ttl=64 time=0.270 ms
64 bytes from 172.16.0.11: icmp_seq=3 ttl=64 time=0.033 ms

Nos deux machines répondent. Nous pouvons passer à la configuration de VRRP.

1.2.2 Configuration de VRRP

Pour illustrer le fonctionnement de VRRP, nous allons utiliser un démon en espace utilisateur qui implémente ce protocole. Il faut donc commencer par installer le paquet vrrpd sur node1 et node2 :

root@node1:~# apt-get install vrrpd

Nous allons maintenant exécuter vrrpd sur node1 qui est le maître du lien :

root@node1:~# vrrpd -D -n -i enp0s3 -p 100 -a none -v 1 172.16.0.13

Cette commande lance le démon vrrpd en arrière-plan (-D), sans gérer l’adresse MAC virtuelle (-n). Étant donné que l’IP virtuelle est sur le même réseau, elle peut tout à fait répondre sur l’adresse MAC originale de la carte, la convergence en cas de perte du lien n’en est que plus rapide. L’interface à gérer via VRRP (-i) est enp0s3 (qui est la seule interface de node1 et node2). La priorité (-p) est fixée à 100. La machine ayant la plus haute priorité est le maître. On n’authentifie pas les machines participant au lien VRRP (-a none). Le groupe VRRP possède l’identifiant 1 (-v 1). En effet, vrrpd peut gérer une multitude de liens, donc il faut les identifier sans ambiguïtés. Enfin, on renseigne l’IP virtuelle, ici 172.16.0.13. Allons voir ce qui s’est passé dans les logs :

root@node1:~# grep vrrpd /var/log/syslog
Aug 9 12:10:19 node1 vrrpd[556]: Starting (adver_int: 1000000, vrid: 1, use virtual mac: no)
Aug 9 12:10:19 node1 vrrpd[556]: VRRP ID 1 on enp0s3 (prio: 100) : we are now a backup router.
Aug 9 12:10:23 node1 vrrpd[556]: VRRP ID 1 on enp0s3 (prio: 100): we are now the master router.

On voit qu’on passe du statut de backup router à celui de maître en 4 secondes. Effectivement :

root@node1:~# ip a
...
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:22:36:5a brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.11/19 brd 172.16.31.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet 172.16.0.13/32 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe22:365a/64 scope link
       valid_lft forever preferred_lft forever

L’adresse virtuelle est bien portée par l’interface enp0s3 avec l’adresse MAC originale. Essayons de toucher l’IP virtuelle en ICMP depuis la machine frontal :

root@frontal:~# ping 172.16.0.13 -c 3
PING 172.16.0.13 (172.16.0.13) 56(84) bytes of data.
64 bytes from 172.16.0.13: icmp_seq=1 ttl=64 time=0.195 ms
64 bytes from 172.16.0.13: icmp_seq=2 ttl=64 time=0.242 ms
64 bytes from 172.16.0.13: icmp_seq=3 ttl=64 time=0.207 ms

Ça fonctionne. Regardons le cache ARP :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:22:36:5a [ether] on enp0s8
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8

On voit que la machine node1 en 172.16.0.11 et l’IP virtuelle 172.16.0.13 sont derrière la même adresse MAC. Lançons maintenant vrrpd sur le node2 avec une priorité un peu inférieure :

root@node2:~# vrrpd -D -n -i enp0s3 -p 90 -a none -v 1 172.16.0.13

Et allons voir dans les logs :

root@node2:~# grep vrrpd /var/log/syslog
Aug 9 12:34:18 node2 vrrpd[536]: Starting (adver_int: 1000000, vrid: 1, use virtual mac: no)
Aug 9 12:34:18 node2 vrrpd[536]: VRRP ID 1 on enp0s3 (prio: 90) : we are now a backup router.

Ce second lien s’est bien positionné en secours. Supprimons l’association dans le cache ARP de la machine frontal :

root@frontal:~# arp -d 172.16.0.13

Relançons quelques requêtes ICMP :

root@frontal:~# ping 172.16.0.13 -c 3
PING 172.16.0.13 (172.16.0.13) 56(84) bytes of data.
64 bytes from 172.16.0.13: icmp_seq=1 ttl=64 time=0.302 ms
64 bytes from 172.16.0.13: icmp_seq=2 ttl=64 time=0.232 ms
64 bytes from 172.16.0.13: icmp_seq=3 ttl=64 time=0.235 ms

Et vérifions à nouveau le cache ARP :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:22:36:5a [ether] on enp0s8
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8

C’est bien toujours node1 qui sert l’IP virtuelle. Regardons ce qui se passe en cas de rupture de lien. Lançons un ping en continu depuis le frontal sur l’IP virtuelle et cassons le lien en envoyant un SIGTERM au service vrrpd sur node1. On récupère le PID :

root@node1:~# pidof vrrpd
556

Et on tue le processus :

root@node1:~# kill -TERM 556

Examinons le ping que nous avons lancé en continu depuis le frontal :

root@frontal:~# ping 172.16.0.13
...
64 bytes from 172.16.0.13: icmp_seq=102 ttl=64 time=0.216 ms
64 bytes from 172.16.0.13: icmp_seq=103 ttl=64 time=0.228 ms
64 bytes from 172.16.0.13: icmp_seq=106 ttl=64 time=0.229 ms
64 bytes from 172.16.0.13: icmp_seq=107 ttl=64 time=0.248 ms
...

On constate que deux paquets ICMP sont perdus entre les séquences 103 et 106 le temps qu’on permute de node1 à node2. Regardons le cache ARP de frontal :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:42:c9:c0 [ether] on enp0s8
? (172.16.0.12) at 08:00:27:42:c9:c0 [ether] on enp0s8

L’IP virtuelle est maintenant portée par node2.

root@node2:~# grep vrrp /var/log/syslog
Aug 9 12:50:31 node2 vrrpd[536]: VRRP ID 1 on enp0s3 (prio: 90): 172.16.0.11 is down, we are now the master router.

Et si on fait revenir le master :

root@node1:~# vrrpd -D -n -i enp0s3 -p 100 -a none -v 1 172.16.0.13

Dans les logs de node1 :

root@node1:~# grep vrrpd /var/log/syslog
Aug 9 12:57:37 node1 vrrpd[601]: Starting (adver_int: 1000000, vrid: 1, use virtual mac: no)
Aug 9 12:57:37 node1 vrrpd[601]: VRRP ID 1 on enp0s3 (prio: 100) : we are now a backup router.
Aug 9 12:57:41 node1 vrrpd[601]: VRRP ID 1 on enp0s3 (prio: 100): 172.16.0.12 is down, we are now the master router.

Et dans ceux de node2 :

root@node2:~# grep vrrpd /var/log/syslog
Aug 9 12:57:40 node2 vrrpd[536]: VRRP ID 1 on enp0s3 (prio: 90) : 172.16.0.11 is up, we are now a backup router.

La perte et le retour de node1 se sont donc bien passés. On notera qu’il est sans doute préférable de remettre en ligne node1 manuellement, car la disponibilité du lien VRRP ne signifie pas forcément que tout va bien. Nous allons maintenant nettoyer nos configurations pour laisser la place à Keepalived :

root@node1:~# apt-get remove --purge vrrpd smcroute
root@node1:~# sed -i '/icmp_echo_ignore_broadcasts/d' /etc/sysctl.conf

Il faut bien redémarrer node1 et node2.

1.3 Keepalived

Le service keepalived est un démon qui réunit toutes les fonctionnalités que nous avons évoquées précédemment pour gérer des IP virtuelles partagées entre plusieurs nœuds. De plus, il va gérer la couche IPVS pour invalider des instances de services équilibrés qui seraient tombées. Dans cette section, nous allons juste mettre en place un service keepalived sur node1 et node2 pour assurer la disponibilité d’une adresse virtuelle pour recréer la configuration précédente. La section suivante fera le lien entre keepalived et IPVS. Commençons par node1 en installant le paquet keepalived :

root@node1:~# apt-get install keepalived

Et configurons-le via le fichier /.etc/keepalived/keepalived.conf :

root@node1:~# cat /.etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
        state MASTER
        interface enp0s3
        virtual_router_id 1
        priority 100
        advert_int 50
        authentication {
              auth_type PASS
              auth_pass toto
        }
        virtual_ipaddress {
              172.16.0.13/19
        }
}

Avec le travail précédent, nous sommes en terrain connu. Nous configurons une instance VRRP en état MASTER (state MASTER) attachée à l’interface enp0s3 avec l’id 1 et une priorité de 100. Ici, contrairement à la section précédente, nous authentifions le lien. Enfin, on spécifie l’IP virtuelle. Démarrons keepalived :

root@node1:~# systemctl start keepalived.service

Voyons le résultat :

root@node1:~# ip a
...
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:22:36:5a brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.11/19 brd 172.16.31.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet 172.16.0.13/19 scope global secondary enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe22:365a/64 scope link
       valid_lft forever preferred_lft forever

Effectivement, l’IP virtuelle est portée par l’interface enp0s3. Configurons maintenant le keepalived de node2 qui est en secours :

root@node2:~# apt-get install keepalived

Dans le fichier de configuration, seules deux choses changent, l’état qui sera BACKUP (state BACKUP) et la priorité qui passe à 90 :

root@node2:~# cat /.etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
        state BACKUP
        interface enp0s3
        virtual_router_id 1
        priority 90
        advert_int 50
        authentication {
              auth_type PASS
              auth_pass toto
        }
        virtual_ipaddress {
              172.16.0.13/19
        }
}

Démarrons le service keepalived sur node2 :

root@node2:~# systemctl start keepalived.service

Testons depuis le frontal :

root@frontal:~# ping 172.16.0.13 -c 3
PING 172.16.0.13 (172.16.0.13) 56(84) bytes of data.
64 bytes from 172.16.0.13: icmp_seq=1 ttl=64 time=0.185 ms
64 bytes from 172.16.0.13: icmp_seq=2 ttl=64 time=0.219 ms
64 bytes from 172.16.0.13: icmp_seq=3 ttl=64 time=0.184 ms
--- 172.16.0.13 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2048ms
rtt min/avg/max/mdev = 0.184/0.196/0.219/0.016 ms

Vérifions le cache ARP de frontal :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:22:36:5a [ether] on enp0s8
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8

L’IP virtuelle est bien servie par node1, le maître. Laissons tourner un ping sur le frontal et coupons le lien vers la machine node1 :

root@frontal:~# ping 172.16.0.13
64 bytes from 172.16.0.13: icmp_seq=18 ttl=64 time=0.200 ms
64 bytes from 172.16.0.13: icmp_seq=19 ttl=64 time=0.228 ms
64 bytes from 172.16.0.13: icmp_seq=21 ttl=64 time=0.219 ms
64 bytes from 172.16.0.13: icmp_seq=22 ttl=64 time=0.222 ms

On constate la perte d’un paquet entre la séquence 19 et 21. Examinons le cache ARP :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:42:c9:c0 [ether] on enp0s8
? (172.16.0.12) at 08:00:27:42:c9:c0 [ether] on enp0s8

La permutation de node1 vers node2 a bien fonctionné. Nous pouvons maintenant passer à la configuration d’IPVS.

2. IPVS

IPVS va venir se reposer sur les deux keepalived que nous avons installés dans la phase précédente. Keepalived va instruire IPVS des règles à créer pour équilibrer le trafic vers les instances de services équilibrés. IPVS va ensuite s’appuyer sur le framework Netfilter pour rediriger le trafic. C’est la raison pour laquelle on dit qu’IPVS est un équilibreur de charge de niveau 4 (transport), car Netfilter ne travaille que sur des sujets de type couple IP/Port. Nous allons ajouter deux serveurs web Nginx à notre laboratoire et les mettre en ferme derrière l’IP virtuelle déjà configurée. Le FQDN www.lab.local pointera sur l’IP virtuelle 172.16.0.13. Derrière cette IP, nous retrouverons www1 en 172.16.0.14 avec la MAC 08:00:27:ca:3b:eb et www2 en 172.16.0.15 avec la MAC 08:00:27:03:a8:e2.

2.1 IPVS en manuel

Les deux points importants à clarifier sont : 1) les algorithmes d’ordonnancement pour l’équilibrage de charge et 2) le mode d’accès réseau aux instances de services équilibrés. Les algorithmes d’ordonnancement sont très nombreux. IPVS intègre dix algorithmes. Ces algorithmes vont conditionner la façon dont les clients accèdent aux instances de services équilibrés. Nous allons en discuter deux : RR et SH. Pour les accès réseau, nous parlerons du mode routage direct. Commençons par les algorithmes d’ordonnancement.

RR signifie Round Robin. Avec cet algorithme, les accès aux instances de services équilibrés se font en mode tournant. Si on a trois serveurs, la première requête va sur le premier serveur, la seconde sur le second serveur, la troisième sur le troisième serveur et la quatrième sur le premier, etc. Cet algorithme est le plus simple en termes d’équilibrage de charge donc le plus performant. On notera qu’il existe une version pondérée (weighted) où il est possible d’ajouter un poids à chaque instance de services équilibrés pour moduler la répartition des requêtes clientes en fonction de la puissance des serveurs portant les instances de services équilibrés.

SH signifie Source Hashing. Dans cet algorithme, les accès aux instances de services équilibrés se font en fonction de la source de la requête. L’objectif est qu’un client accède toujours à la même instance de service équilibré à chacune de ses requêtes. Une empreinte (hash) de la source est mémorisée par IPVS pour lui servir toujours la même instance. Cet algorithme est utilisé lorsqu’il faut maintenir une session côté instance de service équilibré. Cependant, dans notre exemple nous n’allons considérer que le Round Robin.

Pour la partie mode d’accès réseau aux instances de services équilibrés, nous verrons le mode routage direct (aussi appelé DR pour Direct Routing), l’équilibreur de charge reçoit le paquet du client sur l’IP virtuelle. L’équilibreur de charge ne touche pas le paquet, il change juste l’adresse MAC de destination de la trame de transport pour que le paquet arrive sur l’une des instances du service équilibré. Par ce truchement, l’instance de services équilibrés répond directement au client. L’IP virtuelle doit donc être à la fois portée par l’équilibreur de charge et par chaque instance des services équilibrés. Le lecteur attentif relèvera qu’une telle configuration engendre des conflits d’IP. L’astuce est que l’IP virtuelle configurée sur les instances de services équilibrés est « neutralisée », c’est-à-dire qu’elle ne répondra pas aux sollicitations ARP. Nous allons commencer par configurer IPVS à la main et regarder ce qui se passe. L’outil ipvsadm interagit avec le noyau Linux pour configurer les règles d’équilibrage de charge. Commençons par examiner la configuration en place :

root@node1:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

Elle est vide. Déclarons l’IP virtuelle avec une organisation en Round Robin (rr) :

root@node1:~# ipvsadm --add-service --tcp-service 172.16.0.13:80 --scheduler rr

Examinons à nouveau la configuration :

root@node1:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP 172.16.0.13:http rr

Ajoutons les deux instances de services équilibrés :

root@node1:~# ipvsadm --add-server --tcp-service 172.16.0.13:80 --real-server 172.16.0.14:80 --gatewaying
root@node1:~# ipvsadm --add-server --tcp-service 172.16.0.13:80 --real-server 172.16.0.15:80 --gatewaying

Cette commande ajoute un serveur (--add-server), sur un service TCP (--tcp-service) sur une IP virtuelle et son port (172.16.0.13:80). On spécifie ensuite le couple IP / port du serveur réel (--real-server) en mode routage direct (--gatewaying). Examinons la configuration :

root@node1:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP 172.16.0.13:http rr
  -> 172.16.0.14:http             Route   1      0          0
  -> 172.16.0.15:http             Route   1      0          0

Testons les deux serveurs en direct. Nous avons modifié la page d’accueil du Nginx installé pour juste afficher le nom de la machine. Accédons à www1 :

root@frontal:~# curl http://172.16.0.14
www1

Et www2 :

root@frontal:~# curl http://172.16.0.15
www2

Nos deux serveurs sont fonctionnels. Fixons maintenant l’alias de l’IP virtuelle sur les instances de services équilibrés. Il faut ajouter cette section dans les fichiers /.etc/network/interfaces de www1 et www2 :

root@www1:~# cat /etc/network/interfaces
auto enp0s3:0
allow-hotplug enp0s3:0
iface enp0s3:0 inet static
        address 172.16.0.13/24

Pour ce qui est du blocage de l’ARP, il y a plusieurs écoles. Sur Internet, vous trouverez beaucoup de documentation qui propose de modifier les variables du noyau via sysctl et ignorer les requêtes ARP sur une interface donnée. Ici on ne peut pas faire ça, car l’interface porte également l’IP standard du serveur qui accueille le serveur Nginx. Nous allons donc créer une règle avec arptables pour bloquer les requêtes ARP concernant l’IP virtuelle :

root@www1:~# arptables -A INPUT -d 172.16.0.13 -j DROP

Rendons ça persistant au redémarrage :

root@www1:~# cat /.etc/network/if-up.d/arp-block
#!/bin/bash
 
/usr/sbin/arptables -F
/usr/sbin/arptables -A INPUT -d 172.16.0.13 -j DROP

Et mettons les droits d’exécution dessus :

root@www1:~# chmod +x /.etc/network/if-up.d/arp-block

Redémarrons et vérifions la présence de l’alias :

root@www1:~# ip a show dev enp0s3
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:ca:3b:eb brd ff:ff:ff:ff:ff:ff
    inet 172.16.0.14/24 brd 172.16.0.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet 172.16.0.13/24 brd 172.16.0.255 scope global secondary enp0s3:0
       valid_lft forever preferred_lft forever

L’IP virtuelle est bien portée par l’interface réseau de l’instance de service à équilibrer www1. Vérifions la règle arptables :

root@www1:~# arptables -L
Chain INPUT (policy ACCEPT)
-j DROP -d 172.16.0.13

On est bons. Il faut répéter la même manipulation sur www2. Une fois www2 configuré et redémarré, on peut tester :

root@frontal:~# curl http://172.16.0.13
www1
root@frontal:~# curl http://172.16.0.13
www2
root@frontal:~# curl http://172.16.0.13
www1
root@frontal:~# curl http://172.16.0.13
www2

On voit bien que notre requête touche à tour de rôle www1 puis www2 puis www1, etc. Si on capture les réponses HTTP 200 OK (code renvoyé par le serveur web lorsque la page est disponible) reçues par le client on voit les informations suivantes :

root@frontal:~# tcpdump -netti enp0s8 'port 80'
...
1660223139.652485 08:00:27:ca:3b:eb > 08:00:27:f4:11:cd, ethertype IPv4 (0x0800), length 305: 172.16.0.13.80 > 172.16.0.1.58028: Flags [P.], seq 1:240, ack 76, win 509, options [nop,nop,TS val 758492004 ecr 3701434763], length 239: HTTP: HTTP/1.1 200 OK
...
1660223084.219778 08:00:27:03:a8:e2 > 08:00:27:f4:11:cd, ethertype IPv4 (0x0800), length 305: 172.16.0.13.80 > 172.16.0.1.58026: Flags [P.], seq 1:240, ack 76, win 509, options [nop,nop,TS val 855001307 ecr 3701379330], length 239: HTTP: HTTP/1.1 200 OK

On constate que les paquets arrivent de l’IP virtuelle 172.16.0.13, mais avec une adresse MAC qui change. En premier, on trouve 08:00:27:ca:3b:eb (www1), puis 08:00:27:03:a8:e2 (www2). C’est conforme à ce qui a été avancé lors de l’explication du mode routé. Éteignons maintenant la machine www2, c’est là que le bât blesse :

root@frontal:~# curl http://172.16.0.13
curl: (7) Failed to connect to 172.16.0.13 port 80: Aucun chemin d'accès pour atteindre l'hôte cible
root@frontal:~# curl http://172.16.0.13
www1
root@frontal:~# curl http://172.16.0.13
curl: (7) Failed to connect to 172.16.0.13 port 80: Aucun chemin d'accès pour atteindre l'hôte cible
root@frontal:~# curl http://172.16.0.13
www1

Une fois sur deux, on tombe sur la machine défaillante. C’est pour cela que nous avons besoin de Keepalived. En plus de maintenir l’IP virtuelle via VRRP, keepalived surveille la disponibilité des instances de services équilibrés pour ajuster la configuration d’IPVS en temps réel.

2.2 Keepalived + IPVS

Nous allons modifier le fichier de configuration de keepalived pour y intégrer les déclarations d’instances de services à équilibrer. Vous retrouverez des directives très proches de la configuration manuelle. Voyons la section à insérer à la suite du fichier de configuration /.etc/keepalived/keepalived.conf :

root@node1:~# cat /.etc/keepalived/keepalived.conf
...
virtual_server 172.16.0.13 80 {
    delay_loop 8
    lb_algo rr
    lb_kind DR
    protocol TCP
 
    real_server 172.16.0.14 80 {
        TCP_CHECK {
                connect_timeout 3
        }
    }
    real_server 172.16.0.15 80 {
        TCP_CHECK {
                connect_timeout 3
        }
    }
}

Cette configuration concerne l’IP virtuelle 172.16.0.13 et pointe sur deux instances de services équilibrés (real_server) avec leur IP (172.16.0.14 et 172.16.0.15) et le port TCP (protocole TCP) concerné (80). Le service keepalived va contrôler les instances de services équilibrés toutes les 8 secondes (delay_loop) avec un test de connexion TCP (TCP_CHECK) avec un délai de garde fixé à 3 secondes (connect_timeout). L’algorithme d’ordonnancement est un Round Robin (lb_algo rr) et on est sur un mode d’accès aux instances de services équilibrés en mode routé (lb_kind DR). Regardons avec la commande ipvsadm la configuration générée par keepalived :

root@node1:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP 172.16.0.13:http rr
  -> 172.16.0.14:http             Route   1      0          0
  -> 172.16.0.15:http

La configuration est identique à la configuration manuelle que nous avions réalisée. Testons-la :

root@frontal:~# curl http://172.16.0.13
www1
root@frontal:~# curl http://172.16.0.13
www2
root@frontal:~# curl http://172.16.0.13
www1
root@frontal:~# curl http://172.16.0.13
www2

Ça fonctionne ! Éteignons maintenant www1 et regardons le résultat :

root@node1:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP 172.16.0.13:http rr
  -> 172.16.0.15:http             Route   1      0          0

Et node2 :

root@node2:~# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP 172.16.0.13:http rr
  -> 172.16.0.15:http             Route   1      0          0

Nos deux équilibreurs de charge ont bien détecté la panne, www1 a été retiré des instances de services équilibrés. Testons :

root@frontal:~# curl http://172.16.0.13
www2
root@frontal:~# curl http://172.16.0.13
www2
root@frontal:~# curl http://172.16.0.13
www2

Le load balancer ne nous sert plus que pour www2. Regardons la table ARP du frontal :

root@frontal:~# arp -a
? (172.16.0.13) at 08:00:27:22:36:5a [ether] on enp0s8
? (172.16.0.11) at 08:00:27:22:36:5a [ether] on enp0s8

Coupons maintenant node1 qui est l’équilibreur de charge maître et testons :

root@frontal:~# curl http://172.16.0.13
www2

Le service web répond toujours. Regardons le cache ARP :

root@frontal:~# arp -a
? (172.16.0.12) at 08:00:27:42:c9:c0 [ether] on enp0s8
? (172.16.0.13) at 08:00:27:42:c9:c0 [ether] on enp0s8

Nous avons donc monté notre infrastructure web en haute disponibilité avec Keepalived et IPVS.

Conclusion

Dans cet article, nous avons balayé à la fois les aspects théoriques et pratiques autour du couple Keepalived/IPVS. J’ai volontairement limité les configurations au couple routage direct et round robin avec beaucoup de détails pour que vous puissiez rapidement adapter cette configuration à vos besoins. De plus, dans les environnements actuels types cloud avec des SDN (Software Defined Network), il existe une multitude de couches d’abstraction au-dessus du réseau traditionnel pour servir les conteneurs. Or l’équilibrage de charge fait partie de l’ADN de ces environnements hautement élastiques et dynamiques. Il faut donc une solide compréhension des mécanismes sous-jacents à l’équilibrage de charge pour arriver à déboguer celui-ci en cas de problèmes.

Références

[1] N. GRENECHE, Y. ABDECHCHAFIQ, « Les réseaux logiques (VLANs) », GLMF198, novembre 2016 : https://connect.ed-diamond.com/GNU-Linux-Magazine/glmf-198/les-reseaux-logiques-vlans

[2] P. BIONDI, « Scapy, TCP et les automates », MISC52, novembre/décembre 2010 :
https://connect.ed-diamond.com/MISC/misc-052/scapy-tcp-et-les-automates



Article rédigé par

Par le(s) même(s) auteur(s)

Intégration d’ownCloud pour servir un partage de fichiers existant

Magazine
Marque
Linux Pratique
Numéro
136
Mois de parution
mars 2023
Spécialité(s)
Résumé

Le logiciel libre ownCloud est une solution de partage et de stockage des fichiers en mode web. Avec la profusion des plateformes commerciales de ce type (Google Drive, iCloud, Dropbox, etc.), l’accès web aux fichiers est devenu le nouveau standard. Dans cet article, nous allons explorer une méthode pour interfacer cette méthode d’accès aux fichiers avec un serveur NFS (Network File System) existant.

Libre-service de machines virtuelles avec OpenNebula

Magazine
Marque
Linux Pratique
Numéro
130
Mois de parution
mars 2022
Spécialité(s)
Résumé

Dans un article précédent, nous avons mis en place une infrastructure OpenNebula basique [1]. Nous allons maintenant configurer cette plateforme pour proposer aux utilisateurs un guichet de libre-service de machines virtuelles. L’idée est que les utilisateurs puissent créer eux-mêmes leurs machines virtuelles.

Monter sa plateforme Cloud avec OpenNebula

Magazine
Marque
Linux Pratique
Numéro
128
Mois de parution
novembre 2021
Spécialité(s)
Résumé

OpenNebula est un orchestrateur libre de machines virtuelles (KVM, VMWare) et conteneurs lourds (LXC). Son rôle est d’organiser et placer les machines virtuelles sur une infrastructure physique. C’est un peu le chaînon manquant entre le serveur physique et les orchestrateurs de conteneurs applicatifs types Kubernetes.

Les derniers articles Premiums

Les derniers articles Premium

La place de l’Intelligence Artificielle dans les entreprises

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

L’intelligence artificielle est en train de redéfinir le paysage professionnel. De l’automatisation des tâches répétitives à la cybersécurité, en passant par l’analyse des données, l’IA s’immisce dans tous les aspects de l’entreprise moderne. Toutefois, cette révolution technologique soulève des questions éthiques et sociétales, notamment sur l’avenir des emplois. Cet article se penche sur l’évolution de l’IA, ses applications variées, et les enjeux qu’elle engendre dans le monde du travail.

Petit guide d’outils open source pour le télétravail

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Ah le Covid ! Si en cette période de nombreux cas resurgissent, ce n’est rien comparé aux vagues que nous avons connues en 2020 et 2021. Ce fléau a contraint une large partie de la population à faire ce que tout le monde connaît sous le nom de télétravail. Nous avons dû changer nos habitudes et avons dû apprendre à utiliser de nombreux outils collaboratifs, de visioconférence, etc., dont tout le monde n’était pas habitué. Dans cet article, nous passons en revue quelques outils open source utiles pour le travail à la maison. En effet, pour les adeptes du costume en haut et du pyjama en bas, la communauté open source s’est démenée pour proposer des alternatives aux outils propriétaires et payants.

Sécurisez vos applications web : comment Symfony vous protège des menaces courantes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les frameworks tels que Symfony ont bouleversé le développement web en apportant une structure solide et des outils performants. Malgré ces qualités, nous pouvons découvrir d’innombrables vulnérabilités. Cet article met le doigt sur les failles de sécurité les plus fréquentes qui affectent même les environnements les plus robustes. De l’injection de requêtes à distance à l’exécution de scripts malveillants, découvrez comment ces failles peuvent mettre en péril vos applications et, surtout, comment vous en prémunir.

Bash des temps modernes

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Les scripts Shell, et Bash spécifiquement, demeurent un standard, de facto, de notre industrie. Ils forment un composant primordial de toute distribution Linux, mais c’est aussi un outil de prédilection pour implémenter de nombreuses tâches d’automatisation, en particulier dans le « Cloud », par eux-mêmes ou conjointement à des solutions telles que Ansible. Pour toutes ces raisons et bien d’autres encore, savoir les concevoir de manière robuste et idempotente est crucial.

Les listes de lecture

8 article(s) - ajoutée le 01/07/2020
Découvrez notre sélection d'articles pour faire vos premiers pas avec les conteneurs, apprendre à les configurer et les utiliser au quotidien.
11 article(s) - ajoutée le 02/07/2020
Si vous recherchez quels sont les outils du DevOps et comment les utiliser, cette liste est faite pour vous.
8 article(s) - ajoutée le 02/07/2020
Il est essentiel d'effectuer des sauvegardes régulières de son travail pour éviter de perdre toutes ses données bêtement. De nombreux outils sont disponibles pour nous assister dans cette tâche.
Voir les 60 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous