29 mars 2024

Tuto Redis en Haute Dispo, partie I : La théorie

Tuto révisé en décembre 2022

 

Dans cette première partie de la série sur Redis, je vais vous expliquer la solution que j’ai retenue, et surtout, le pourquoi du comment

Mais avant tout, regardons ce qu’il est possible de faire.

I – Solutions possibles

Pour mettre Redis en haute disponibilité, nous avons deux possibilités. L’une ayant bien évidement mes faveurs pour les raisons que je vous conte ci dessous :

A – Redis Cluster

Redis propose depuis sa version 3 un mode cluster. Alors, j’ai testé, ça marche, mais voila pourquoi je ne retiens pas cette solution.

Déjà, pour que cela soit optimum il faut un minimum de trois Masters et trois Slaves.
Pourquoi ?
Car le mode cluster fonctionne par défaut avec un sharding, c’est à dire, une répartition des clés sur les Masters, chacun étant associé à un Slave au cas ou le Master tombe. On peut optimiser en croisant les slaves sur les masters et réduire ainsi à trois serveurs seulement. On peut aussi désactiver le sharding et au final, n’avoir qu’un master et un slave, qui bascule master, du classique, mais cette solution est bien plus simple à mettre en place avec Redis Sentinel que j’aborde après.

Mais bon, ce n’est pas tout…

Admettons que l’on parte sur une solution avec trois masters et trois slaves repartis sur juste trois serveurs , histoire d’être radin.

Ce qui donnerait ceci :

  • Serv1 : Master A – Slave C
  • Serv2 : Master B – Slave A
  • Serv3 : Master C – Slave B

Si on veut interroger le cluster, il nous faut une VIP (bon, ça, ce n’est pas un soucis), par contre ensuite, ça se corse.

Quand on interroge un serveur, il peut répondre directement, s’il a la clé, ou alors, répondre que la clé est sur un autre serveur avec une réponse MOVE suivi de l’IP du serveur en question.

En ligne de commande, avec l’outil redis-cli, pas de problèmes : si on passe l’argument -c, on active le suivi des MOVE et on a notre réponse quelque soit le serveur où elle est présente.
Mais côté logiciel, cette fonctionnalité n’est pas souvent prise en compte (Logstach et Filebeat ne gèrent pas, Rspamd non plus… et je n’ai pas tout testé, mais à mon avis, y’a pas grand chose qui gère pour le moment…)

Bref, c’est une solution élégante, qui fonctionne et qui a l’avantage d’être très flexible. Mais à la condition que les logiciels en accès dessus soient capables de le supporter nativement. Pour le moment, on passe…

B – Redis Sentinel

On va donc partir sur une solution plus « ancienne », mais tout aussi efficace en terme de haute disponibilité dans un cadre « classique » avec Redis Sentinel.

Il s’agit d’un programme qui surveille les nœuds en déclarant qui est master et qui est slave et  il s’active simplement en créant un fichier de configuration et en lançant une instance de Redis avec l’argument -sentinel.

Cela marche au poil, et c’est rapide à mettre en œuvre.

 

II – Architecture

Je pars sur une solution « basique » avec un serveur Redis1 en Master, et un serveur Redis2 en  Slave, qui passe Master si le Master original n’est plus.

A – Redis Master/Slave

Déclarer un serveur redis en Slave d’un autre, c’est très simple, on le verra dans la partie mise en place. En gros, en une commande, c’est prêt.

Par contre, la suite demande un peu plus d’explication.

B – Sentinel

Pour la partie Redis Sentinel, généralement, on voit conseiller d’avoir en amont des deux serveurs Redis, au moins trois autres serveurs Sentinels, Sent1, Sent2, Sent3, etc…

Il est important d’avoir un nombre impair de Sentinels pour le quorum (majorité des votes).

Et de plus, il faut que plus de la majorité des Sentinels soient UP  au moment d’un vote.

  • Si un Sentinel est down et que Redis1 est down, les deux autres Sentinels votent.
  • Si deux Sentinels sont down et que Redis1 est down, bien qu’un Sentinel soit UP, pas de vote…

Pour cela que plus y’a de Sentinels, mieux c’est… Mais ici, on va rester avec un cas simple à trois Sentinels.

Ceci dit, j’aimerais optimiser le nombre de VM et si on pouvait essayer de caser ces trois services sur les deux serveurs Redis ce serait pas mal. Le process n’est pas lourd et on aurait besoin que de deux VMs au lieu de cinq…

Dans les cas suivants, je parle de Down complet du serveur, c’est à dire Redis ET les Sentinels (crash du serveur quoi…)

En premier lieu, essayons en positionnant deux Sentinels (Sent1 et Sent2) sur le Redis1 et une (Sent3) sur Redis2 :

  • Si Redis1 tombe, Sent3 sur Redis2 le détecte, mais ne peut pas voter, il n’y a pas la majorité des Sentinelles UP, raté…
  • Si Redis 2 tombe, c’est détecté par Sent1 et Sent2, mais pas vraiment utile…

Bon, essayons le contraire, Sent1 sur Redis1, et Sent2 et Sent3 sur Redis2 :

  • Si Redis1 tombe, Sent2 et Sent3 sur Redis2 le détectent, votent, et Redis2 passe Master.
  • Si Redis 2 tombe, Sent1 le détecte, ne fait rien au final, car n’a rien à faire, mais en même temps, il ne sait pas quoi faire s’il avait à faire (vous suivez ?) car la majorité des Sentinels est down.

Bon, ceci-dit, c’est bien mieux car en cas de perte du Master, le Slave devient Master et c’est ce qu’on veut.

Mais sachez qu’on peut faire encore plus simple. Vous vous souvenez que j’ai dis qu’il fallait un nombre impair de Sentinels pour que cela fonctionne ?

Et bien, je me suis bêtement dis :  et si avec un Sentinel, ça marchait ? Bah oui, apres tout, un est impair non ?

Et bien, ça marche si et seulement si le Sentinel est sur le serveur Redis2.

  • Si Redis1 tombe, Sentinel sur Redis2 le détecte,peut voter, Redis2 devient Master.
  • Si Redis2 tombe, Redis1 détecte juste la perte du backup, mais rien ne change. Lorsque le backup sera de nouveau UP, il se synchronisera.

Impeccable !

Alors oui, cela reste « fragile » dans le sens ou si Sentinel sur Redis2 tombe ET si le Redis Master tombe, le Redis Slave reste Slave… En même temps, ces deux incidents simultanément, c’est assez peu probable et si mon Sentinel tombe, je serais alerté et aurais le temps d’agir avant un éventuel Down de Redis1.

Pour moi, cela répond surtout à un cas plus probable, si la VM Redis1 plante, tout se passe comme il faut. Redis2 change de status,  la VIP suit et mon service reste assuré.

Après, libre à vous de rajouter des Sentinels si vous voulez, mais sachez qu’au minimum, on peut faire ainsi.

 

C – Point d’entrée et VIP

Maintenant, il s’agit de faire en sorte que nos clients puissent s’adresser toujours au serveur Redis Master, peu importe qu’il se trouve sur Redis1 ou Redis2.

Sentinel propose une solution : interroger son service pour connaitre l’adresse du master. Rspamd gère cette façon de faire, mais c’est plutôt une exception, Filebeat et Logstash eux, doivent communiquer avec un port Redis, point. Il nous faut donc une solution « passe-partout ».

On va donc faire du classique et utiliser une VIP avec l’installation de Keepalived. Mais Keepalived de base ne sera pas suffisant et je vous explique pourquoi.

1 – Si Redis plante

Keepalived doit monitorer Redis. Mais comment ? Un simple check du process ou un PING n’est pas suffisant, il nous faut aussi savoir qui est le Redis Master.

Dans les checks, on intègre donc cette notion de vérification du rôle (je passe l’explication technique pour ne pas alourdir…) et on obtient Redis1 en Master sur la VIP car Redis sur Redis1 est le Master. Et Redis2 en Slave sur la VIP.

Si le serveur Redis1 complet tombe ou si juste le service redis plante, Redis2 prend le Master sur Redis grâce à Sentinel, le check Keepalived de Redis2 devient bon, il passe Master sur la VIP, tout va bien.

Bon, premier petit défaut. Si sur Redis1, seul Redis tombe, il se trouve qu’avec les temps de détections côté Sentinel et Keepalived, il peut se produire un IP Flapping le temps que le Slave passe définitivement Master sur la VIP.

Bon en soit, ce n’est pas bloquant et en creusant, on peut optimiser, mais il se pose encore un second défaut, plus contraignant.

2 – Si Keepalived plante

Imaginons un instant que sur Redis1, seul Keepalived plante (après tout, ça peut aussi arriver, ou un besoin pour une raison X de le couper…)

Keepalived sur Redis2 va vouloir alors prendre la VIP, mais en même temps, il ne pourra pas, son check indiquant que le Redis présent ici est Slave. Ce qui est bien le cas, Redis sur Redis1 tournant toujours…

Il faut donc en plus, penser à gérer ce cas de figure dans les checks de Keepalived… Mouais, ça commence à devenir compliqué …

Mais allez, on est un peu maso et on continue de voir ou ça va nous emmener en ayant des scripts de plus en plus alambiqués et donc soumis à un risque probable de ce qu’on nomme dans le milieu un « foirage »…

Car oui, vous l’aurez deviné, il y a encore un « truc qui coince »…

3 – Mais le slave répond…

On a notre Keepalived sur Redis2 capable de prendre la VIP si Keepalived sur Redis1 plante, et ce même si Redis sur Redis2 est slave…, c’est bien beau, mais du coup, c’est un slave qui répond.

Et si pour les lecture, ça ne gêne pas, il en est autrement pour les écritures…

La, on est face à deux choix :

On se lance donc dans la création de script pour couper Redis sur Redis1 si Keepalived sur Redis1 plante mais que la synchro est bonne, etc… Holala, mais vers où on va la…

Ou alors, on se dit qu’il y a plus simple. Et c’est la que le loadbalancing vient à la rescousse.

4 – Loadbalancer

Si sur chaque serveur, entre Keepalived pour la VIP et Redis, on intercale un loadbalancer chargé, en fonction de l’état des Redis, de dire qui prend la requête, on solutionne notre problématique.

A un instant T, tout tourne correctement, Redis1 en Master, Redis2 en Slave, les deux loadbalancers sur chaque serveur indiquant chacun que Redis1 est master et la VIP sur Redis1.

Sur un down du service redis sur Redis1, Redis2 passe master grâce à Sentinel, les deux loadbalancers détectent le down de Redis1, le fait que Redis2 soit master et roule ma poule. Ce sera toujours le loadbalancer de Redis1 qui répondra car lui est opérationnel.

Sur un down de Keepalived sur Redis1, ce sera le loadbalancer de Redis2 qui prendra la main, et comme ses checks indiqueront que c’est Redis sur Redis1 qui est master, pas de soucis !

Et dans le cas d’un plantage d’une machine au complet, par exemple Redis1, le serveur Redis sur Redis2 passe master, la VIP suit et le LB2 envoie bien ou il faut.

 

5 – Mais Keepalived, il check quoi du coup ?

Keepalived peut surveiller maintenant deux choses au final :

  • Soit le status de Redis et c’est un peu pénible, comme je vous l’ai expliqué.
  • Soit le status du loadbalancer et c’est plus simple.

 

6 – Ce fameux, Loadbalancer, qui peut il être ?

Keepalived permet de gérer ce loadbalancing en introduisant le principe de serveur virtuel.

Par contre, vu que sa configuration devenait vraiment alambiquée, j’ai préféré bien séparer les services (et gérer dans keepalived les checks pour la partie LB, ça devenait l’enfer…).

Et donc, j’ai choisi d’utiliser un très classique HAproxy qui fait parfaitement l’affaire.

Le gros avantage par rapport au loadbalancing dans Keepalived, c’est que les deux configurations d’HAproxy sont identiques.

Et Keepalived se contentant simplement de monitorer Haproxy, sa configuration se simplifie grandement.

Au final, on a une brique en plus certes, mais les configurations sont bien plus claires et on arrive à un résultat stable bien plus rapidement.

 

III – On récapitule

On aura une configuration de la sorte :

Deux serveurs identiques avec Redis, Haproxy et Keepalived. Sentinel sur le serveur Redis2, et c’est tout.

Après des pavés d’explications, ça parait simple, résumé de la sorte, non ?

 

IV – Création des VM

Utilisation du script : Tuto Virtualisation avec KVM, partie VIII : Scripts d’automatisation

A – Création

Au niveau ressources :

size_swap=x
size_var=xx
size=x
ram=1024
cpu=1

Création :

hyperv# ./create.sh -n redis1 -i 101 -s 1
hyperv# ./create.sh -n redis2 -i 102 -s 0

 

B – Réseaux

On va créer les fichiers suivants :

  • net-vlan106-redis1.xml
<interface type='network'>
    <source network='intern-vlans' portgroup='106'/>
    <target dev='redis1.1'/>
    <model type='virtio'/>
    <driver name='vhost'/>
</interface>
  • net-vlan106-redis2.xml
<interface type='network'>
    <source network='intern-vlans' portgroup='106'/>
    <target dev='redis2.1'/>
    <model type='virtio'/>
    <driver name='vhost'/>
</interface>

On injecte :

hyperv# virsh attach-device redis1 net-vlan106-redis1.xml --config
hyperv# virsh attach-device redis2 net-vlan106-redis2.xml --config

Et pour le réseau interne au cluster :

On va créer les fichiers :

  • net-vlanprivate-redis1.xml
<interface type='network'>
    <source network='private-vlans' portgroup='vlan53'/>
    <target dev='redis1.2'/>
    <model type='virtio'/>
    <driver name='vhost'/>
</interface>
  • net-vlanprivate-redis2.xml
<interface type='network'>
    <source network='private-vlans' portgroup='vlan53'/>
    <target dev='redis2.2'/>
    <model type='virtio'/>
    <driver name='vhost'/>
</interface>

On injecte :

hyperv# virsh attach-device redis1 net-vlanprivate-redis1.xml --config
hyperv# virsh attach-device redis2 net-vlanprivate-redis2.xml --config

 

V – Gestion du VLAN 106

On va ajouter le VLAN 106 sur les deux routeurs internes.

Je vous renvois ici : Création d’un pool de serveurs dans le chapitre IV. Vous suivez en remplacant 109 par 106.

On s’assurera également que les Vlans privés sont opérationnels (50 à 59).

 

VI – Démarrage des VMs

On démarre les VMs :

hyperv# virsh start redis1
hyperv# virsh start redis2

 

Ne reste plus qu’à se connecter sur chacune pour mettre à jour la configuration réseau :

On édite le fichier /etc/network/interfaces pour y ajouter :

Pour Redis1

auto eth1
iface eth1 inet static
 address 10.100.6.101
 netmask 255.255.255.0
 gateway 10.100.6.254

auto eth2
 iface eth2 inet static
 address 10.50.0.101
 netmask 255.255.255.0

Pour Redis2

auto eth1
iface eth1 inet static
 address 10.100.6.102
 netmask 255.255.255.0
 gateway 10.100.6.254

auto eth2
iface eth2 inet static
 address 10.50.0.102
 netmask 255.255.255.0

 

Puis sur les deux serveurs :

redisx# service networking restart

ou bien

redisx# ifup eth1
redisx# ifup eth2

 

Allez, on se retrouve dans la deuxième partie pour installer Redis.

 


Envie de me soutenir et de me payer un café ? C’est sur la page Don !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *