Dans cet article, nous allons mettre en place le premier service fourni dans notre infrastructure KVM : le routage vers et depuis l’extérieur.
Bah oui, avoir des VMs, c’est cool, qu’elles puissent à minima se mettre à jour et pourquoi pas, fournir d’autres services externe, c’est encore mieux !
Cet article s’intègre dans la série New Box 2022. Vous y trouverez le pourquoi du comment, les prérequis, paquets à installer, etc…
I – Explication
Nous allons donc mettre en place non pas un mais deux routeurs !
En effet, dans mes dernières infra, ce restait un sérieux SPOF (Single Point Of Failure), où en bon français, « Si ça pète, c’est la merde »…
Pour rappel, voila ce que nous allons mettre en place, nous concentrant dans ce tutoriel sur Extrout1 et Extrout2.
Par défaut, comme toutes mes VMS, ces deux routeurs ont un lien dans le VLAN 99 (lien VMs <-> Hyperviseur) et dans le VLAN 250 ( lien administration inter-vm, utilisé pour le monitoring, etc…). Histoire de ne pas complexifier inutilement, ces deux VLANs ne sont pas représentés sur le schéma.
Il leurs faudra également un lien pour communiquer avec l’extérieur via le bridge br0 et une lien dans le VLAN 10 pour servir de Gateway à mes machines qui causent en direct avec le net (FrontWeb, ….)
Via un mécanisme de Haute Dispo, le routeur Master aura deux IPs flottantes, une IP 10.10.1.254 servant de Gateway pour mes VMs et l’IP externe.
Puis ensuite, du NAT, du FW, bref, du classique…
Dans mon contexte, je rappelle que je suis sur une Dédibox, chez Online et que j’ai au préalable commandé une IP failover et ai généré une MAC correspondante.
Dans un contexte @home, peu d’importance, mais chez Online, attention, sinon coupure !
II – Mise en place
A – Créations des VMs
On va créer nos deux routeurs à l’aide des scripts présentés ici : Tuto Virtualisation avec KVM, partie VIII : Scripts d’automatisation
Au niveau ressources systèmes, dans le script create.sh, je les configure ainsi :
# Options size_swap=1 size_var=3 size=3 ram=256 cpu=1
Tout comme Maxwell, pas la peine d’en rajouter.
On lance la création des VMs :
hyperv# ./create_vm.sh -n extrout1 -i 1 -s 1 hyperv# ./create_vm.sh -n extrout2 -i 2 -s 0
Extrout1 sur le VG Master, id 1 et Extrout2, sur le VG Slave, id 2.
Pour vérifier :
hyperv# virsh list --all
doit répondre :
Id Name State --------------------------- - extrout1 shut off - extrout2 shut off
B – Ajout des nouvelles interfaces
Pour le moment, nos machines sont uniquement branchées sur les VLANs 99 et 250 avec comme adresses :
- extrout1 : 10.99.1.1 et 10.250.1.1
- extrout2 : 10.99.1.2 et 10.250.1.2
C’est bien mais pas top… En effet, pour communiquer avec le réseau externe et avec nos futurs VM, il va en falloir plus !
Et comme je l’ai déjà mentionné, il faut deux nouvelles interfaces : une pour le lien externe via br0, l’autre pour le VLAN 10.
On va ajouter les interfaces réseaux sur extrout1 :
# virsh attach-interface --domain extrout1 --type network --source intern-vlans --target extrout1.1 --model virtio --config # virsh attach-interface --domain extrout1 --type network --source ovs-front --target extrout1.2 --model virtio --config --mac 52:54:00:ff:ff:ff
La première, extrout1.1, sera utilisée pour communiquer sur le VLAN 10.
La seconde, extrout1.2, sera utilisée pour communiquer sur le réseau internet. Bien évidement dans le contexte « Chez Scaleway/Online », on fait attention à la MAC, pour ma part, c’est celle virtuelle associée à mon IP Fail Over. Pour info le préfixe 52:54:00 est dédié à KVM.
La même chose pour extrout2 :
# virsh attach-interface --domain extrout2 --type network --source intern-vlans --target extrout2.1 --model virtio --config # virsh attach-interface --domain extrout2 --type network --source ovs-front --target extrout2.2 --model virtio --config --mac 52:54:00:ff:ff:ff
La aussi, en prenant soin de mettre la MAC correcte, si besoin.
Et pour vérifier tout ceci ? Et bien :
hyperv# virsh domiflist extrout1
Qui doit nous donner un joli :
Interface Type Source Model MAC ------------------------------------------------------------------- extrout1.0 network intern-vlans virtio 52:54:00:aa:bb:cc extrout1.1 network intern-vlans virtio 52:54:00:dd:ee:ff extrout1.2 network ovs-front virtio 52:54:00:ff:ff:ff
C – Correction de la configuration
Un petit défaut, c’est qu’ajouter une interface de la sorte (en CLI) ne permet pas de spécifier le vlan associé. On doit donc éditer la configuration pour y remédier.
J’aurais pu vous montrer de suite la technique d’ajout d’interface par injection de fichier xml, mais on verra cette méthode par la suite.
Pour éditer la configuration d’une VM :
hyperv# virsh edit extrout1
Vous vous retrouvez alors avec un fichier xml décrivant la configuration de votre VM. Si vous descendez un peu, vous trouverez la partie dédiée au réseau :
... <interface type='network'> <mac address='52:54:00:aa:bb:cc'/> <source network='intern-vlans' portgroup='vlansadmin'/> <target dev='extrout1.0'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </interface> <interface type='network'> <mac address='52:54:00:dd:ee:ff'/> <source network='intern-vlans' portgroup='vlan10'/> <target dev='extrout1.1'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x09' slot='0x00' function='0x0'/> </interface> <interface type='network'> <mac address='52:54:00:ff:ff:ff'/> <source network='ovs-front'/> <target dev='extrout1.2'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x0a' slot='0x00' function='0x0'/> </interface> ...
Vous ajoutez les éléments en gras.
En quittant, on sauvegarde et on doit avoir :
Domain 'extrout1' XML configuration edited.
et pour extrout2, d’une manière identique :
hyperv# virsh edit extrout1
Pour éditer de la sorte :
... <interface type='network'> <mac address='52:54:00:ab:ab:ab'/> <source network='intern-vlans' portgroup='vlansadmin'/> <target dev='extrout2.0'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </interface> <interface type='network'> <mac address='52:54:00:cd:cd:cd'/> <source network='intern-vlans' portgroup='vlan10'/> <target dev='extrout2.1'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x09' slot='0x00' function='0x0'/> </interface> <interface type='network'> <mac address='52:54:00:ff:ff:ff'/> <source network='ovs-front'/> <target dev='extrout2.2'/> <model type='virtio'/> <driver name='vhost'/> <address type='pci' domain='0x0000' bus='0x0a' slot='0x00' function='0x0'/> </interface> ...
Et on sait qu’on a bien bossé en quittant avec :
Domain 'extrout2' XML configuration edited.
D – Configuration réseau des routeurs
Avant de lancer les VMs, on va éditer leur configuration réseau.
On va tout d’abord créer un répertoire pour le montage des partitions :
hyperv# mkdir /root/mountlvm
On monte le volume correspondant au / (racine) de la vm extrout1
hyperv# mount /dev/mapper/vg1-extrout1p1 /root/mountlvm/
Note: si le volume à monter est par exemple extrout, sans le 1, la partition ne sera pas extrout1p1 comme dans l’exemple présent, mais simplement extrout1.
On édite le fichier /root/mountlvm/etc/network/interfaces en ajoutant à la fin :
auto lo iface lo inet loopback auto eth0.99 iface eth0.99 inet static address 10.99.1.1 netmask 255.255.255.0 auto eth0.250 iface eth0.250 inet static address 10.250.1.1 netmask 255.255.255.0 auto eth1 iface eth1 inet static address 10.10.1.1 netmask 255.255.255.0
On démonte :
hyperv# umount /root/mountlvm
Et on refait une manipulation similaire pour le second routeur :
hyperv# mount /dev/mapper/vg0-extrout2p1 /root/mountlvm/
On édite le fichier /root/mountlvm/etc/network/interfaces en ajoutant à la fin :
auto lo iface lo inet loopback auto eth0.99 iface eth0.99 inet static address 10.99.1.2 netmask 255.255.255.0 auto eth0.250 iface eth0.250 inet static address 10.250.1.2 netmask 255.255.255.0 auto eth1 iface eth1 inet static address 10.10.1.2 netmask 255.255.255.0
Et on démonte :
hyperv# umount /root/mountlvm
E – Mise en route
On démarre nos machines
hyperv# virsh start extrout1 hyperv# virsh start extrout2
Pour mémoire, pour lancer en mode console :
hyperv# virsh start extrout1 --console
Et pour accéder à la console d’une VM :
hyperv# virsh console extrout1
Mais dans notre cas, on passera par SSH.
On vérifie tout d’abord, dans l’hyperviseur, que les ports sont branchés comme il faut :
hyperv# ovs-vsctl show 4ed3127c-77c6-4843-87eb-f1e753e8f8e9 Bridge brint Port brint tag: 99 Interface brint type: internal Port "extrout1.1" tag: 10 Interface "extrout1.1" Port "extrout2.1" tag: 10 Interface "extrout2.1" Port "extrout1.0" trunks: [99, 250] Interface "extrout1.0" Port "extrout2.0" trunks: [99, 250] Interface "extrout2.0" Bridge "br0" Port "br0" Interface "br0" type: internal Port "extrout2.2" Interface "extrout2.2" Port "eno1" Interface "eno1" Port "extrout1.2" Interface "extrout1.2"
Et si tout est bien fait, on doit pouvoir y accéder par SSH :
hyperv# ssh user@10.99.1.1
et
hyperv# ssh user@10.99.1.2
ou bien :
hyperv# ssh user@extrout1
Jusque la, tout est bon ? Parfait alors, continuons !
III – Configuration réseau
On se connecte donc au routeur 1 et on va va lui ajouter de quoi causer à travers l’interface eth2, qui je rappelle est branchée sur br0, avec la mac virtuelle fournie par Online.
Si j’étais chez moi, sur mon réseau 192.168.1.0/24, mon hyperviseur pourrait être en .250 et mon IP FO pour mes routeurs en .100 par exemple
A – Extrout1
Au préalable, il faut passer root :
extrout1$ su - root
Puis :
extrout1# ip addr add 123.123.123.123 dev eth2 extrout1# ip link set eth2 up extrout1# ip route add 62.210.0.1 dev eth2 extrout1# ip route add 0.0.0.0/0 via 62.210.0.1 dev eth2 extrout1# echo 1 > /proc/sys/net/ipv4/ip_forward
Vous remplacez bien évidement 123.123.123.132 et 62.210.0.1 (la GW d’Online).
Dans l’ordre, on configure l’IP, on monte l’interface, on ajoute les routes qui vont bien (comment adresser la GW et ensuite comment l’utiliser) et on termine en activant le forward.
On test en effectuant un ping depuis une machine autre :
machineexterne# ping IP.FO
et depuis le routeur 1, un ping vers l’extérieur doit fonctionner aussi
extrout1# ping 8.8.4.4
B – Extrout2
Sur le routeur 2 :
On passe root :
extrout2$ su - root
Puis :
extrout2# ip route add 0.0.0.0/0 via 10.10.1.1 dev eth1 src 10.10.1.2
On ajoute simplement la route pour sortir via le routeur 1 sur le réseau 10.10.0.0/8.
C – Rapide FW avec NFTables
Mais il manque encore le NAT sur le routeur 1 pour que le routeur 2 puisse « sortir » sur le net. On va faire une rapide configuration de nftables.
NB : NAT, ça ne vous parle pas ? Vous voulez creuser le sujet Nftables, le remplaçant d’Iptables ? Alors, rendez vous ici : Nftables, la série de tutos
Sur les deux routeurs, nous allons créer :
Un premier fichier /srv/firewallup.sh
#!/usr/sbin/nft -f flush ruleset define external_ip = 123.123.123.123 define if_v10 = "eth1" table ip filter { chain input { type filter hook input priority 0; policy accept; } chain output { type filter hook output priority 0; policy accept; } chain forward { type filter hook forward priority 0; policy accept; } } table ip nat { chain prerout { type nat hook prerouting priority -100; policy accept; } chain postrout { type nat hook postrouting priority 100; policy accept; iifname $if_v10 snat $external_ip } }
Vous remplacez bien évidement l’IP 123.123.123.123.
Le point important dans cette configuration est la règle de POSTROUTING qui effectue un SNAT sur les paquets sortants (remplacement de l’adresse source en 10. par l’IP externe du routeur).
Puis un fichier /srv/firewalldown.sh, toujours sur les deux routeurs :
#!/usr/sbin/nft -f flush ruleset define if_v10 = "eth1" table ip filter { chain input { type filter hook input priority 0; policy accept; } chain output { type filter hook output priority 0; policy accept; } }
On chmod :
# cd /srv # chmod +x firewall*
Sur le routeur1 on le lance :
# ./firewallup.sh
Et le routeur2 peut communiquer avec les réseaux externes.
Concernant le FW, nous reviendrons dessus dans un article à suivre.
IV – Haute Disponibilité avec Keepalived
Maintenant que nos deux routeurs sont prêts, nous allons passer à la mise en œuvre de la haute disponibilité du service.
Pour se faire, j’utilise Keepalived (https://www.keepalived.org/). Pour résumer brièvement, Keepalived permet d’assurer une Haute Disponibilité par la fourniture d’IPs « flottantes ». Un serveur est déclaré Maitre (en fonction de son poids, de l’état de ses pairs, etc…). Les IPs virtuelles (abrégés IPV) sont alors affectées à cet hôte. En cas de bascule, elles seront déplacées sur le nouveau Master. Je parle dans mon cas au futur car il y en aura plusieurs, mais bien évidement, qui peut le plus, peut le moins….
A – Installation et configuration
On l’installe sur les deux routeurs :
extroutX# apt install keepalived
Puis, sur le routeur 1,dans un fichier /etc/keepalived/keepalived.conf (le fichier n’existe pas, il faut le créer) :
global_defs { shutdown_script /srv/keepdown.sh } vrrp_instance ROUTEUR_1 { state MASTER preempt interface eth1 virtual_router_id 1 priority 200 advert_int 1 authentication { auth_type PASS auth_pass password } notify /srv/keep.sh unicast_src_ip 10.10.1.1 unicast_peer { 10.10.1.2 } virtual_ipaddress { 10.10.1.254 } }
Sur le routeur 2, fichier /etc/keepalived/keepalived.conf :
global_defs { shutdown_script /srv/keepdown.sh } vrrp_instance ROUTEUR_1 { state BACKUP interface eth1 virtual_router_id 1 priority 50 advert_int 1 authentication { auth_type PASS auth_pass password } notify /srv/keep.sh unicast_src_ip 10.10.1.2 unicast_peer { 10.10.1.1 } virtual_ipaddress { 10.10.1.254 } }
B – Le script de changement d’état
Maintenant, passons au script qui sera appelé à chaque changement d’état.
Sur les deux routeurs, créez un fichier /srv/keep.sh :
#!/bin/bash TYPE=$1 NAME=$2 STATE=$3 case $STATE in "MASTER") /srv/keepup.sh exit 0 ;; "BACKUP") /srv/keepdown.sh exit 0 ;; "FAULT") /srv/keepdown.sh exit 0 ;; *) echo "unknown state" exit 1 ;; esac
C – Script UP
Quand un routeur prend l’état MASTER, il faut donc qu’il se connecte via le br0 à Internet.
On va faire ainsi, avec, sur les deux routeurs, un fichier /srv/keepup.sh :
#!/bin/bash ip addr add 123.123.123.123 dev eth2 ip link set eth2 up ip route add IP.GATEWAY dev eth2 ip route del default ip route add 0.0.0.0/0 via IP.GATEWAY dev eth2 echo 1 > /proc/sys/net/ipv4/ip_forward /srv/firewallup.sh sleep 1 ping -c 1 IP.GATEWAY
Vous remplacez bien sur 123.123.123.123 par l’IP réelle et IP.GATEWAY par l’IP de votre GW.
Le script configure l’IP, monte l’interface et refait une nouvelle route pour 0.0.0.0/0. Il termine en activant le forward et en chargeant une configuration de firewall.
Ha ! Et pourquoi un ping sur la GW pour terminer ?
Lors de mes tests, il apparaissait que le temps de bascule était… aléatoire… La méthode était simple, une machine externe, un ping en boucle et je regardais combien j’en perdais. Au mieux, un, au pire… plein…. trop !
La raison se trouve dans les tables CAM d’Openvswitch qui ne se mettent à jour que si la VM initie une nouvelle connexion vers l’extérieur…
Et la méthode à Dudul, c’est un p’tit ping ! Sans ce dernier, on est dépendant du temps que va mettre un hypothétique service à faire une requête vers l’externe…
D – Script DOWN
Script appelé quand un routeur prend l’état SLAVE.
Fichier /srv/keepdown.sh, différent sur chaque routeur.
Sur le routeur 1 :
#!/bin/bash ip link set eth2 down ip addr del 123.123.123.123/32 dev eth2 ip route add 0.0.0.0/0 via 10.10.1.2 dev eth1 src 10.10.1.1 /srv/firewalldown.sh
Sur le routeur 2 :
#!/bin/bash ip link set eth2 down ip addr del 123.123.123.123/32 dev eth2 ip route add 0.0.0.0/0 via 10.10.1.1 dev eth1 src 10.10.1.2 /srv/firewalldown.sh
Dans les deux cas, vous remplacez 123.123.123.123 par l’IP réelle.
On termine en chmodant (oui, oui, verbe du premier groupe, vous ne connaissiez pas ? Je chmode, tu chmodes…)
# cd /srv # chmod +x keep*
V – Tests
Maintenant, on va passer à la phase de validation en testant tout ceci.
On va tout d’abord arrêter Keepalived sur les deux routeurs :
extroutX# service keepalived stop
Sur les deux routeurs, on peut zieuter le log en parallèle pour voir ce qui se passe :
extroutX# tail /var/log/syslog -f
Puis on le relance sur extrout1 :
extrout1# service keepalived start
Dans le log, on doit voir ceci :
Apr 14 10:57:16 extrout1 Keepalived_vrrp[3072]: (ROUTEUR_1) Entering BACKUP STATE (init) Apr 14 10:57:19 extrout1 Keepalived_vrrp[3072]: (ROUTEUR_1) Entering MASTER STATE
En parallèle, on va lancer un ping depuis une machine externe :
autremachineexterne# ping IP.FO
On lance maintenant sur extrout2 :
extrout2# service keepalived start
Dans le log, on voit qu’il est Slave :
Apr 14 10:59:55 extrout2 Keepalived_vrrp[1700]: (ROUTEUR_1) Entering BACKUP STATE (init)
On coupe sur extrout1:
extrout1# service keepalived stop
Extrout2 passe Master :
Apr 14 13:08:43 extrout2 Keepalived_vrrp[1700]: (ROUTEUR_1) Backup received priority 0 advertisement Apr 14 13:08:44 extrout2 Keepalived_vrrp[1700]: (ROUTEUR_1) Entering MASTER STATE
On relance le 1
extrout1# service keepalived start
Extrout1 reprend le Master
Apr 14 13:21:16 extrout1 Keepalived_vrrp[3467]: (ROUTEUR_1) Entering BACKUP STATE (init) Apr 14 13:21:16 extrout1 Keepalived_vrrp[3467]: (ROUTEUR_1) received lower priority (50) advert from 10.10.1.2 - discarding Apr 14 13:21:17 extrout1 Keepalived_vrrp[3467]: (ROUTEUR_1) received lower priority (50) advert from 10.10.1.2 - discarding Apr 14 13:21:18 extrout1 Keepalived_vrrp[3467]: (ROUTEUR_1) received lower priority (50) advert from 10.10.1.2 - discarding Apr 14 13:21:19 extrout1 Keepalived_vrrp[3467]: (ROUTEUR_1) Entering MASTER STATE
Extrout2 repasse Slave
Apr 14 13:21:19 extrout2 Keepalived_vrrp[1700]: (ROUTEUR_1) Master received advert from 10.10.1.1 with higher priority 200, ours 50 Apr 14 13:21:19 extrout2 Keepalived_vrrp[1700]: (ROUTEUR_1) Entering BACKUP STATE
Et à chaque fois, le routeur Master récupère bien l’IP externe et l’IP interne flottantes. TADA !
# ip a ---> (sortie simplifiée) 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 inet 127.0.0.1/8 scope host lo 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 10.10.1.1/24 brd 10.10.1.255 scope global eth1 inet 10.10.1.254/32 scope global eth1 4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 inet 123.123.123.123/32 scope global eth2 5: eth0.99@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 inet 10.99.1.1/24 brd 10.99.1.255 scope global eth0.99 6: eth0.250@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 inet 10.250.1.1/24 brd 10.250.1.255 scope global eth0.250
et tout ceci avec peu de ping perdus.
On peut également pousser le test en coupant, non pas Keepalived mais carrément le routeur et d’une manière « violente » :
hyperv# virsh destroy extrout1
Destroy ne détruit pas la machine, je vous rassure, mais simule ce qu’on appelle dans le jargon : le pied dans la prise…
Bref, une fois extrout1 coupé, exrout2 devient bien Master, comme prévu.
Une fois validé, on peut relancer pour revenir en configuration de production en relançant extrout1
hyperv# virsh start extrout1
Bref, on y est, notre gateway en HA est opérationnelle, on peut revenir à notre feuille de route.