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.sh
Le 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.
Intéressons-nous aux 2 dernières parties.

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.
Juste au cas où, auth_pass doit être strictement identique sur toutes les machines impliquées. De cette façon, il faudra définir, pour chaque membre étant ou pouvant être impliqué potentiellement dans la bascule les caractéristiques de sa vrrp_instance.

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 definition
Beaucoup 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 ms
En 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.

Conclusion

Quelques détails suffisent à changer la donne, et coupler Keepalived avec une infrastructure bien étudiée permet de balayer assez facilement les petits soucis relatifs à des pannes de service. De plus c'est un outil simple, intuitif, riche en terme d'options et paramétrable à souhait. Pourquoi s'en passer ?
Vus : 7855
Publié par K-Tux : 59