Keepalived : entrée en matière
Le down d'un service peut avoir d'étranges conséquences sur le milieu professionnel. En effet, cela peut passer totalement inaperçu ou bien cela va générer une crise sans précédent, au cours de laquelle vous n'aurez jamais assez bien fait en aval que ce que vous auriez dû prévoir en amont.
L'appréhension de ce genre d'incident permet l'évaluation du besoin et des SLA (Service Level Agreements) et, en fonction, le déploiement d'architecture dites HA (High Availability). Dans ce type d'architecture, typiquement, l'objectif est de pouvoir assurer un service quelque soit les incidents possibles et imaginables.
Techniquement, cela se traduit par la mise en place de n machines, capables, toutes de délivrer le service, et de savoir ordonner qui prend la main et quand. Ce dernier point implique la présence d'un maître d'orchestre, et donc d'un Single Point Of Failure.
Dans ce cas, qui monitore et assure la disponibilité du service de dispatchage des rôles ?
KeepAlived ?
Keepalived est un daemon tournant en Userland qui a pour ambition d'adresser ces 2 problématiques : d'une part gérer la vérification de l'état des intervenants d'un cluster de serveurs et d'autre part assurer, en fonction de ces états, la distribution des rôles aux membres de ce regroupement. En clair, il s'agit de monitorer les états des différents serveurs impliqués dans la délivrance du service et d'en gérer la disponibilité via l'attribution dynamique de l'IP par laquelle les clients sollicite ce service. La clé repose sur l'exploitation des fonctionnalités du projet LVS (Linux Virtual Server) et sur le concept d'IP virtuelle. Le tout fonctionne sur une infrastructure de type cluster de serveur / load balancer, généralement contenu dans une DMZ. Afin de mieux comprendre son fonctionnement, je vous propose de rentrer directement dans la configuration de la bête, car comme dit le vieux proverbe : "il faut avoir les manches retroussées pour comprendre complètement la mécanique !" Par la suite, je vais donc lâchement supposer que votre infrastructure est en place, et qu'il ne reste plus que le point Keepalived à traiter. Si vous avez besoin d'un petit coup de pouce niveau LVS, jetez un coup d'oeil sur le LVS HowTo de Joseph Mack.Installation depuis les sources
L'installation la plus nette est toujours celle utilisant git, c'est d'ailleurs elle que j'ai choisi, mais rien ne vous empêche d'utiliser les dépôts !$ git clone http://master.formilux.org/git/people/alex/keepalived.git /usr/local/src/ $ ll /usr/local/src/ total 12 drwxr-xr-x 3 bux bux 4096 2011-02-16 12:08 . drwxr-xr-x 20 bux bux 4096 2011-02-16 12:08 .. drwxr-xr-x 9 bux bux 4096 2011-02-16 12:08 keepalived $ cd keepalived $ ./configure [...] Keepalived configuration ------------------------ Keepalived version : 1.1.20 Compiler : gcc Compiler flags : -g -O2 Extra Lib : -lpopt -lssl -lcrypto Use IPVS Framework : No IPVS sync daemon support : No Use VRRP Framework : Yes Use Debug flags : No $ make && make install [...] install -d /usr/local/sbin install -m 700 ../bin/keepalived /usr/local/sbin/ install -d /usr/local/etc/rc.d/init.d install -m 755 etc/init.d/keepalived.init /usr/local/etc/rc.d/init.d/keepalived install -d /usr/local/etc/sysconfig install -m 755 etc/init.d/keepalived.sysconfig /usr/local/etc/sysconfig/keepalived install -d /usr/local/etc/keepalived/samples install -m 644 etc/keepalived/keepalived.conf /usr/local/etc/keepalived/ install -m 644 ../doc/samples/* /usr/local/etc/keepalived/samples/ install -d /usr/local/share/man/man5 install -d /usr/local/share/man/man8 install -m 644 ../doc/man/man5/keepalived.conf.5 /usr/local/share/man/man5 install -m 644 ../doc/man/man8/keepalived.8 /usr/local/share/man/man8 make[1]: quittant le répertoire « /usr/local/src/keepalived/keepalived » make -C genhash install make[1]: entrant dans le répertoire « /usr/local/src/keepalived/genhash » install -d /usr/local/bin install -m 755 ../bin/genhash /usr/local/bin/ install -d /usr/local/share/man/man1 install -m 644 ../doc/man/man1/genhash.1 /usr/local/share/man/man1 make[1]: quittant le répertoire « /usr/local/src/keepalived/genhash »Installation cleared !
Configuration
Comme on peut le déduire des traces laissées par le make de keepalived, les fichiers de configuration se situent sous /usr/local/etc/keepalived/, en sachant qu'un fichier est déjà fourni de base et qu'on peut également s'appuyer sur les samples (exemples, extraits) fournis :$ ll /usr/local/etc/keepalived/ total 16 drwxr-xr-x 3 root root 4096 2011-02-16 12:13 . drwxr-xr-x 5 root root 4096 2011-02-16 12:13 .. -rw-r--r-- 1 root root 3562 2011-02-16 12:13 keepalived.conf drwxr-xr-x 2 root root 4096 2011-02-16 12:13 samples $ ll samples/ total 104 drwxr-xr-x 2 root root 4096 2011-02-16 12:13 . drwxr-xr-x 3 root root 4096 2011-02-16 12:13 .. -rw-r--r-- 1 root root 1745 2011-02-16 12:13 client.pem -rw-r--r-- 1 root root 245 2011-02-16 12:13 dh1024.pem -rw-r--r-- 1 root root 433 2011-02-16 12:13 keepalived.conf.fwmark -rw-r--r-- 1 root root 684 2011-02-16 12:13 keepalived.conf.HTTP_GET.port -rw-r--r-- 1 root root 746 2011-02-16 12:13 keepalived.conf.inhibit -rw-r--r-- 1 root root 550 2011-02-16 12:13 keepalived.conf.misc_check -rw-r--r-- 1 root root 538 2011-02-16 12:13 keepalived.conf.misc_check_arg -rw-r--r-- 1 root root 2467 2011-02-16 12:13 keepalived.conf.quorum -rw-r--r-- 1 root root 919 2011-02-16 12:13 keepalived.conf.sample -rw-r--r-- 1 root root 2760 2011-02-16 12:13 keepalived.conf.SMTP_CHECK -rw-r--r-- 1 root root 1587 2011-02-16 12:13 keepalived.conf.SSL_GET -rw-r--r-- 1 root root 842 2011-02-16 12:13 keepalived.conf.status_code -rw-r--r-- 1 root root 735 2011-02-16 12:13 keepalived.conf.track_interface -rw-r--r-- 1 root root 887 2011-02-16 12:13 keepalived.conf.virtualhost -rw-r--r-- 1 root root 1087 2011-02-16 12:13 keepalived.conf.virtual_server_group -rw-r--r-- 1 root root 1425 2011-02-16 12:13 keepalived.conf.vrrp -rw-r--r-- 1 root root 3019 2011-02-16 12:13 keepalived.conf.vrrp.localcheck -rw-r--r-- 1 root root 1083 2011-02-16 12:13 keepalived.conf.vrrp.lvs_syncd -rw-r--r-- 1 root root 888 2011-02-16 12:13 keepalived.conf.vrrp.routes -rw-r--r-- 1 root root 1146 2011-02-16 12:13 keepalived.conf.vrrp.scripts -rw-r--r-- 1 root root 591 2011-02-16 12:13 keepalived.conf.vrrp.static_ipaddress -rw-r--r-- 1 root root 1742 2011-02-16 12:13 keepalived.conf.vrrp.sync -rw-r--r-- 1 root root 802 2011-02-16 12:13 root.pem -rw-r--r-- 1 root root 323 2011-02-16 12:13 sample.misccheck.smbcheck.shLe fichier de configuration en question est généré tel quel :
! Configuration File for keepalived global_defs { notification_email { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc smtp_server 192.168.200.1 smtp_connect_timeout 30 router_id LVS_DEVEL } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.200.16 192.168.200.17 192.168.200.18 } } virtual_server 192.168.200.100 443 { delay_loop 6 lb_algo rr lb_kind NAT nat_mask 255.255.255.0 persistence_timeout 50 protocol TCP real_server 192.168.201.100 443 { weight 1 SSL_GET { url { path / digest ff20ad2481f97b1754ef3e12ecd3a9cc } url { path /mrtg/ digest 9b3a0c85a887a256d6939da88aabd8cd } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } } virtual_server 10.10.10.2 1358 { delay_loop 6 lb_algo rr lb_kind NAT persistence_timeout 50 protocol TCP sorry_server 192.168.200.200 1358 real_server 192.168.200.2 1358 { weight 1 HTTP_GET { url { path /testurl/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl2/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl3/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.200.3 1358 { weight 1 HTTP_GET { url { path /testurl/test.jsp digest 640205b7b0fc66c1ea91c463fac6334c } url { path /testurl2/test.jsp digest 640205b7b0fc66c1ea91c463fac6334c } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } } virtual_server 10.10.10.3 1358 { delay_loop 3 lb_algo rr lb_kind NAT nat_mask 255.255.255.0 persistence_timeout 50 protocol TCP real_server 192.168.200.4 1358 { weight 1 HTTP_GET { url { path /testurl/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl2/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl3/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.200.5 1358 { weight 1 HTTP_GET { url { path /testurl/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl2/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } url { path /testurl3/test.jsp digest 640205b7b0fc66c1ea91c463fac6334d } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } }
Grâce à lui, on a déjà un aperçu de la manière dont KeepAlived organise les informations : tout passe par la définition de bloc.
Epluchage de conf
On peut voir que la configuration est scindée en trois :- une partie globale (le bloc global_def), qui n'est ni plus ni moins que la définition des adresses mail, de routes statiques et d'identifiant.
- une partie définissant les instances et groupes de synchronisation VRRP,
- une partie propre aux paramétrages des LVS, qui peut contenir un ou plusieurs blocs de définition de serveurs virtuels, eux-même encapsulant des serveurs réels caractérisés par des paramétrage en terme de poids, d'IP, et de process de vérification.
VRRP
vrrp_sync_group
Les VRRP synchronization groups sont des groupes qui rassemblent les instances VVRP.#string, name of group of IPs that failover together vrrp_sync_group VG_1 { group { VI_1 # name of vrrp_instance (below) VI_2 # One for each moveable IP. ... } # notify scripts and alerts are optional # filenames of scripts to run on transitions can be unquoted (if just filename) or quoted (if has parameters) to MASTER transition notify_master /path/to_master.sh # to BACKUP transition notify_backup /path/to_backup.sh # FAULT transition notify_fault "/path/fault.sh VG_1" # for ANY state transition. # "notify" script is called AFTER the # notify_* script(s) and is executed # with 3 arguments provided by keepalived (ie don't include parameters in the notify line). # arguments # $1 = "GROUP"|"INSTANCE" # $2 = name of group or instance # $3 = target state of transition # ("MASTER"|"BACKUP"|"FAULT") notify /path/notify.sh # Send email notifcation during state transition, # using addresses in global_defs above. smtp_alert }
vrrp_instance
La partie vrrp_instance est définie dans la configuration créée telle que :vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.200.16 192.168.200.17 192.168.200.18 } }Alors, c'est quoi au juste cette vrrp_instance ? Commençons par le commencement. VRRP, pour Virtual Router Redondancy Protocol, est un protocole d'abstraction de serveur, qui permet via la création d'un groupe de serveurs réels et la gestion d'adresses IP réelles et virtuelles, d'attribuer dynamiquement le rôle de réponse aux requêtes client à un des serveurs en état de le faire. En un mot, il assure le monitoring des instances capables de délivrer le service et donc sa disponibilité. La configuration par défaut définit donc notre machine comme l'instance VRRP maître et y associe :
- un identifiant (virtual_router_id 51),
- une interface liée à VRRP (interface eth0),
- une priorité très haute (priority 100), en sachant que plus cette priorité est haute et plus la machine a de chance d'être élue,
- des paramètres d'authentification
- des adresses IP virtuelles à rajouter / supprimer lors des bascules.
A noter que le template généré est loin d'être exhaustif. En effet, pas mal d'options permettent de mieux travailler la bascule.
LVS
De la même manière qu'il est possible de faire des groupes d'instance vrrpd, il est possible de grouper les instances virtual_server, mais la Communauté ne semble envisager son utilisation que dans le cas où vous êtes en présence de très grands LVS , ce ne sera donc pas abordé ici.
virtual_server
Notre bloc concernant un virtual_server a cette tête-là dans la conf générée. Les commentaires sont là pour éviter une répétition laborieuse et peu utile de chacune des instructions pour détail :)virtual_server 192.168.200.100 443 { # delay timer for service polling delay_loop 3 # LVS scheduler lb_algo rr # LVS forwarding method lb_kind NAT nat_mask 255.255.255.0 # LVS persistence timeout, sec persistence_timeout 50 protocol TCP # RS to add when all realservers are down sorry_server 192.168.200.200 443 # one entry for each realserver real_server 192.168.201.100 443 { # relative weight to use, default: 1 weight 1 # pick one healthchecker # HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK SSL_GET { url { path / digest ff20ad2481f97b1754ef3e12ecd3a9cc } url { path /mrtg/ digest 9b3a0c85a887a256d6939da88aabd8cd } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } # End HTTP_GET section } # End real_server definition } # End virtual_server definitionBeaucoup plus intuitif que les sections VRRP, cette configuration est très simple à manier et se passe assez facilement d'explication. Même remarque, les options ne sont pas exhaustives et une véritable mine de paramètres vous attendent au détour d'un man. Il ne reste plus qu'à paufiner la conf et à lancer un petit keepalived -D (detailed logs).
La preuve par l'exemple
Jouons un peu avec les serveurs DNS. Nous avons un serveur maître, et un esclave. Ces deux serveurs ont chacun une IP réelle (192.168.0.2 pour le Maître et 192.168.0.3 pour l'esclave). En revanche, le service est accédé l'IP virtuelle 192.168.0.10.$ ssh root@dns1 ip addr sh eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:50:56:a3:00:0d brd ff:ff:ff:ff:ff:ff inet 192.168.0.2/24 brd 192.168.0.255 scope global eth0 inet 192.168.0.10/32 scope global eth0 inet6 fe80::250:56ff:fea3:d/64 scope link valid_lft forever preferred_lft forever $ ssh root@dns2 ip addr sh eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:50:56:a3:00:0c brd ff:ff:ff:ff:ff:ff inet 192.168.0.3/24 brd 192.168.0.255 scope global eth0 inet6 fe80::250:56ff:fea3:c/64 scope link valid_lft forever preferred_lft forever $ ping dns PING dns.k-tux.com (192.168.0.10) 56(84) bytes of data. 64 bytes from dns.k-tux.com (192.168.0.10): icmp_seq=1 ttl=63 time=0.499 ms ^C --- dns.k-tux.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 535ms rtt min/avg/max/mdev = 0.499/0.499/0.499/0.000 msEn terme de configuration Keepalived, nous aurons donc 2 types d'instances vrrp. Ici, nous effectuons une vérification toutes les 6 secondes qui, selon le résultat du check, pourra potentiellement faire basculer l'IP virtuelle 192.168.0.10 d'un serveur à l'autre. Pour cela nous devons définir un vrrp_script, qui fera en temps et en secondes les traitements voulus.
Sur le master :
vrrp_script check_named { # Requires keepalived-1.1.13 script "killall -0 named" # cheaper than pidof interval 6 # check every 6 seconds weight -60 # add 60 points of prio if OK } vrrp_instance dns_on_master { state MASTER interface eth0 virtual_router_id 91 priority 200 advert_int 1 smtp_alert authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.0.10 } track_script { check_named } notify_master "/etc/rc.d/init.d/named reload" notify_backup "/etc/rc.d/init.d/named reload" }
Et sur le slave :
vrrp_script check_named { # Requires keepalived-1.1.13 script "killall -0 named" # cheaper than pidof interval 6 # check every 6 seconds weight -60 # add 60 points of prio if OK } vrrp_instance dns_on_slave { state SLAVE interface eth0 virtual_router_id 91 priority 150 smtp_alert advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.0.10 } track_script { check_named } notify_master "/etc/rc.d/init.d/named reload" notify_backup "/etc/rc.d/init.d/named reload" }Le test le plus probant est d'arrêter un des deux serveurs dns, au hasard le master (celui qui a la plus haute priorité détient logiquement l'IP virtuelle) : on doit observer une bascule de l'IP virtuelle 192.168.0.10 de dns1 à dns2. Le smtp_alert nous rajoute une couche d'information en nous mailant la bascule.