

























Redis est un systÚme de stockage de données en mémoire de type NoSQL orienté hautes performances. Il est souvent utilisée comme base de données, systÚme de cache ou de messages. Nous verrons dans cet article comment déployer un service hautement disponible grùce à Redis Replication.
1 Notre premier serveur Redis
Pour dĂ©marrer, commençons avec un mon serveur Redis. Lâinstallation sera prĂ©sentĂ©e pour Ubuntu 22.04 et devrait fonctionner sans trop dâadaptations pour Debian ou RHEL-like.
Redis est disponible dans les dĂ©pĂŽts classiques de la distribution. Une configuration minimale peut ĂȘtre faite en installant simplement les paquets et en activant le service systemd.
sudo apt -y install redis-server sudo systemctl enable --now redis-server
Vérifions maintenant que nous parvenons à nous connecter :
redis-cli 127.0.0.1:6379> ping PONG
La CLI permet Ă©galement de rĂ©cupĂ©rer avec la commande INFO un certain nombre dâĂ©lĂ©ments sur le fonctionnement du serveur. Celle-ci sâappelle seule ou avec un argument pour obtenir les informations relatives Ă une catĂ©gorie particuliĂšre.
127.0.0.1:6379> INFO Server # Server redis_version:6.0.16 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:a3fdef44459b3ad6 redis_mode:standalone os:Linux 5.15.0-73-generic x86_64 arch_bits:64 multiplexing_api:epoll atomicvar_api:atomic-builtin gcc_version:11.2.0 process_id:596 run_id:7ea0cf9f46b211a64874d7a1c0a115be78c42e98 tcp_port:6379 uptime_in_seconds:61483 uptime_in_days:0 hz:10 configured_hz:10 lru_clock:8771018 executable:/usr/bin/redis-server config_file:/etc/redis/redis.conf io_threads_active:0
Redis gĂšre plusieurs types de donnĂ©es. Il est possible dâutiliser des chaĂźnes de caractĂšres, des ensembles, des hashs, des listes ainsi que dâautres types de donnĂ©es. Pour procĂ©der Ă une premiĂšre vĂ©rification de bon fonctionnement, nous allons ainsi Ă©crire une donnĂ©e de type string, rĂ©cupĂ©rer la valeur en indiquant la clĂ© puis effacer cette donnĂ©e.
127.0.0.1:6379> set foo bar OK 127.0.0.1:6379> get foo "bar" 127.0.0.1:6379> del foo (integer) 1 127.0.0.1:6379> get foo (nil)
A ce stade, notre serveur est fonctionnel, toutefois sa configuration est trĂšs basique. Redis nâĂ©coute que sur lâinterface localhost ce qui est incompatible avec la notion de rĂ©plication que nous mettrons en place et il est prĂ©fĂ©rable que systemd prenne en charge le service de maniĂšre explicite. Nous allons entreprendre donc nos premiĂšres modifications du fichier de configuration, /etc/redis/redis.conf et remplacer les paramĂštres bind et supervised par les valeurs suivantes :
bind 0.0.0.0 supervised systemd protected-mode
Redémarrons le service redis et vérifions :
sudo systemctl restart redis-server sudo netstat -lataupen |grep ":6379" tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 115 39165 3267/redis-server 0
En pratique, la plupart des options de Redis peuvent ĂȘtre dĂ©finies avec la commande CONFIG SET. Câest peut-ĂȘtre moins classique ou confortable quâun fichier de configuration mais câest ce qui permet Ă Redis dâĂȘtre entiĂšrement reparamĂ©trĂ© sans quâil ne soit nĂ©cessaire de relancer le service et donc sans aucune interruption de service.
Redis est une base de donnĂ©es en mĂ©moire. Par consĂ©quent si le service est arrĂȘtĂ©, quelle quâen soit la raison, les donnĂ©es sont perdues. Pour assurer la persistance des donnĂ©es, Redis permet dâutiliser deux mĂ©canismes. Dans le premier, appelĂ© RDB, Redis va gĂ©nĂ©rer des snapshots Ă intervalles rĂ©guliers de la base de donnĂ©es. Cela se configure avec save suivi de deux indicateurs, le premier consiste en une sauvegarde aprĂšs n secondes si au moins un certain nombre dâĂ©critures ont eu lieu. Ce paramĂštre est en outre multivaluĂ© .La politique par dĂ©faut est celle-ci :
save 900 1 save 300 10 save 60 10000
GĂ©nĂ©ralement, on souhaite avoir au moins un snapshot rĂ©cent mĂȘme sâil y a peu dâĂ©critures, dâoĂč celui rĂ©alisĂ© aprĂšs 15 minutes Ă partir du moment oĂč il y a eu une Ă©criture. A lâinverse, en cas de forte charge, on souhaite Ă©galement avoir des snapshots frĂ©quents pour minimiser la perte de donnĂ©es, dâoĂč le snapshot toutes les minutes dĂšs 10000 clĂ©s modifiĂ©es. Ces seuils sont naturellement Ă adapter en fonction de lâactivitĂ© de la base et afin de minimiser la perte de donnĂ©es admissible.
Dans le second mĂ©canisme, AOF pour Append Only File, la persistance est gĂ©rĂ©e via lâutilisation dâun fichier de logs qui va historiser chaque Ă©criture. Pour un maximum de fiabilitĂ©, les deux modes peuvent ĂȘtre utilisĂ©s conjointement. Naturellement, cela a un impact sur la consommation mĂ©moire et les I/O disques car pour chaque Ă©criture en base, une seconde est faite pour le journal AOB.
Pour utiliser lâAOF, ajoutons ces deux lignes dans le fichier redis.conf :
appendonly yes appendfilename "appendonly.aof" appendfsync everysec
Par dĂ©faut, Redis nâest pas joignable via le rĂ©seau. Pour que la mise en Ćuvre de la rĂ©plication Ă suivre soit possible, le service Ă©coute dĂ©sormais sur lâensemble des interfaces rĂ©seau. Bien quâil soit possible de restreindre Ă une ou plusieurs interfaces, lâaccĂšs au service Ă des fins de test sâest rĂ©alisĂ© sans authentification. En dĂ©finissant un mot de passe sur le paramĂštre requirepass, toute opĂ©ration sur Redis nĂ©cessitera au prĂ©alable de sâauthentifier avec la commande AUTH suivie du mot de passe. Dans la configuration redis.conf nous ajoutons donc :
requirepass SECRET
Et nous pouvons vérifier :
# redis-cli 127.0.0.1:6379> AUTH SECRET OK
Pour assurer une meilleur sĂ©curitĂ©, du TLS devrait ĂȘtre mis en Ćuvre et les ACL Redis 6 devraient ĂȘtre utilisĂ©es.
Redis Replication est un systĂšme maĂźtre/esclave permettant de rĂ©pliquer de maniĂšre asynchrone les donnĂ©es du master sur les slaves. En cas de perte de connectivitĂ© avec le master, les slaves vont tenter de resynchroniser lâĂ©cart de donnĂ©es dĂšs que la connexion redevient disponible. Si cela nâest pas possible une resynchronisation complĂšte est rĂ©alisĂ©e. Un slave peut servir de source de rĂ©plication Ă un autre slave et les rĂ©pliquas peuvent servir de serveur en lecture pour rĂ©partir la charge et donc augmenter la performance.
En cas de perte du master, une procĂ©dure de failover doit ĂȘtre mise en Ćuvre pour promouvoir un slave comme master. Tant que cela nâest pas fait, le fonctionnement est en mode dĂ©gradĂ© si ce nâest totalement interrompu. Redis Sentinel utilisĂ© en complĂ©ment permet de superviser le fonctionnement de Redis et en cas de panne du master, de promouvoir un nouveau master et de reconfigurer les repliquas pour quâils se rĂ©pliquent depuis ce nouveau master. Dans ce type dâarchitecture, il est recommandĂ© de disposer dâau moins 3 serveurs pour gĂ©rer le quorum.
Avec Redis Replication il est fortement recommandĂ© dâactiver la persistance. En effet, sans persistance et en cas de redĂ©marrage du master, il se retrouvera vide de toute donnĂ©e. Dans un second temps, les slaves vont sâempresser de rĂ©pliquer cela les vidant de leurs donnĂ©es.
Pour assurer la montĂ©e en charge horizontale, Redis Cluster offre des possibilitĂ©s complĂ©mentaires. Dans ce mode, il sâagit toujours dâavoir une architecture master/slave mais avec gestion et failover automatique. Lâespace de clĂ©s est divisĂ© de sorte que chaque groupe de serveur ne gĂšre quâune partition de la base de donnĂ©es, cela sâappelle du sharding. La taille de la base de donnĂ©es peut ainsi ĂȘtre plus importante et la charge peut ĂȘtre rĂ©partie sur plus de nĆuds. Un minimum de 6 nĆuds est toutefois requis pour Redis Cluster ce qui prĂ©dispose ce mode Ă des infrastructures relativement consĂ©quentes.
De fait dans cet article jâaborderai uniquement Redis Replication.
La configuration mise en Ćuvre sur le premier serveur que nous appellerons db01 devra ĂȘtre reprise Ă lâidentique sur nos deux slaves. Le serveur DB01 sera le master par dĂ©faut. Pour que la suite soit aisĂ©ment comprĂ©hensible, voici les serveurs et adresses IP que je vais utiliser :
Sur chacun de nos serveurs, nous allons dĂ©finir la valeur de masterauth. En effet, nous avons exigĂ© prĂ©cĂ©demment un mot de passe via requirepass, il faut donc sâauthentifier pour avoir les droits pour rĂ©pliquer :
masterauth SECRET
Et enfin, sur chaque slave, on indique depuis quel serveur notre slave doit se répliquer :
replicaof db01.morot.local 6379
Il suffit de redĂ©marrer nos serveurs, il nây a rien de plus Ă faire.
Dans un premier temps, envoyons la commande INFO Replication sur notre master. Si la configuration est correcte, alors on doit pouvoir avoir un rÎle master, visualiser les deux slaves comme connectés ainsi que leurs adresses IP et enfin en fonctionnement nominal un statut online :
127.0.0.1:6379> AUTH SECRET OK 127.0.0.1:6379> INFO Replication # Replication role:master connected_slaves:2 slave0:ip=192.168.69.83,port=6379,state=online,offset=1106,lag=0 slave1:ip=192.168.69.82,port=6379,state=online,offset=1106,lag=0 master_replid:75b55df27cafe6bfd7aec3114bfd63e77d45a8cb master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1106 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1106
A lâinverse, sur un slave, le rĂŽle doit correspondre et le serveur doit ĂȘtre connectĂ© au slave (master_link_status). Si master_sync_in_progress est Ă 0 alors la rĂ©plication est terminĂ©e. La section master_replid indiquĂ© le dataset de ReplicationID qui doit ĂȘtre identique avec le master lorsque les masters et slaves sont synchronisĂ©s. Si une synchronisation Ă©tait en cours, nous aurions des champs master_sync supplĂ©mentaires pour suivre la volumĂ©trie Ă rĂ©pliquer et les performances de rĂ©plication.
127.0.0.1:6379> INFO Replication # Replication role:slave master_host:db01.morot.local master_port:6379 master_link_status:up master_last_io_seconds_ago:3 master_sync_in_progress:0 slave_repl_offset:1288 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:75b55df27cafe6bfd7aec3114bfd63e77d45a8cb master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1288 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1288
Voyons dĂ©sormais ce quâil se passe lors dâune panne du master. Jâai simulĂ© un crash du master en coupant violemment la VM qui porte le service. Sur chacun de mes slaves, la rĂ©plication indique que le lien avec le master est « down » avec le dĂ©lai depuis lequel le lien est rompu.
127.0.0.1:6379> INFO Replication # Replication role:slave master_host:db01.morot.local master_port:6379 master_link_status:down master_last_io_seconds_ago:-1 master_sync_in_progress:0 slave_repl_offset:3472 master_link_down_since_seconds:42
Sâil sâagit dâun simple redĂ©marrage du serveur master, la rĂ©plication devrait remonter dĂšs que le serveur sera up. En cas de panne majeure, le service rendu est interrompu. Il faut manuellement reconfigurer les slaves pour rĂ©tablir le service.
Sur db03, nous allons donc indiquer que sa source de réplication est db02 :
127.0.0.1:6379> replicaof db02.morot.local 6379 OK
Pour autant, cela ne fait pas de db02 un master par magie. Dâautant que Redis supporte la rĂ©plication dâun slave depuis un autre slave. Il faut donc indiquer que db02 est un master ce qui se configure en indiquant quâil est le rĂ©pliqua dâaucun serveur :
127.0.0.1:6379> replicaof no one OK 127.0.0.1:6379> info replication # Replication role:master connected_slaves:1 slave0:ip=192.168.69.83,port=6379,state=online,offset=3472,lag=0
Se pose ensuite la question de la remise en service du serveur db01 lorsquâil sera rĂ©parĂ©. Avec le failover prĂ©cĂ©dent il va se retrouver master sans slave et il sera aussi dĂ©synchronisĂ© des autres serveurs Redis. Il est donc indispensable de le remettre en synchronisation avec le master qui a Ă©tĂ© promu. Cela se fait de la mĂȘme façon que lors du failover prĂ©cĂ©dent :
127.0.0.1:6379> replicaof db02.morot.local 6379 OK
Et câest tout. Si toutefois vous tenez Ă remettre le serveur redis master « nominal » en tant que master, il faut alors :
Cela reste toutefois une opération à réaliser en période de maintenance car une courte rupture du service lors de la bascule est à prévoir.
Nous venons de le voir, la rĂ©plication est simple Ă mettre en Ćuvre ou Ă reconfigurer. Elle a toutefois un Ă©norme inconvĂ©nient en cas de perte du master, elle est manuelle. Nous allons donc mettre en Ćuvre un systĂšme complĂ©mentaire appelĂ© Redis Sentinel qui viendra gĂ©rer automatiquement la promotion dâun nouveau master et la reconfiguration des slaves.
Commençons par installer sentinel :
sudo apt -y install redis-sentinel
Et configurer le service pour quâil communique sur le rĂ©seau et non plus uniquement sur localhost. Au niveau firewall, le port 26379 devra autoriser les flux. Cela se fait via le fichier dĂ©diĂ© /etc/redis/sentinel.conf :
bind 0.0.0.0 port 26379
Ajoutons un mot de passe de connexion, il sâagit du mot de passe dâauthentification sur Sentinel :
requirepass SECRET
Enfin dĂ©marrons la configuration des rĂšgles de bascule. Nous monitorons lâadresse IP du master, 192.168.69. Un quorum de 2 serveurs est exigĂ©, câest Ă dire que si au moins deux slaves sâaccordent sur le fait que le master est injoignable alors une bascule est enclenchĂ©e.
sentinel monitor mymaster 192.168.69.81 6379 2 sentinel auth-pass mymaster SECRET
Le master est considéré comme injoignable aprÚs une minute :
sentinel down-after-milliseconds mymaster 60000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1
Une redĂ©marrage du service sentinel plus tard, vĂ©rifions lâĂ©tat de notre service en se connectant via redis-cli sur le port de Sentinel cette fois :
redis-cli -p 26379 127.0.0.1:26379> AUTH SECRET OK 127.0.0.1:26379> info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.69.81:6379,slaves=2,sentinels=4
Nous retrouvons bien le bon nombre de slaves. Si on souhaite des informations dĂ©taillĂ©es sur les slaves il est possible dâenvoyer la commande sentinel slaves mymaster et pour les instances sentinel : sentinel sentinels mymaster. La sortie Ă©tant peu lisible, il sâagit de la configuration en mode clĂ© valeur dans la base Redis, je ne vais pas lâinclure ici.
Sur notre master, simulons une panne en arrĂȘtant le service avec systemctl stop redis-server. En parallĂšle, regardons les logs de Sentinel dans /var/log/redis/redis-sentinel.log. Par lisibilitĂ©, je vais filtrer quelques peu les logs.
En premier lieu nous avons un event sdown, cela signifie que sentinel a dĂ©tectĂ© que le master nâĂ©tait plus joignable :
37873:X 18 Jun 2023 21:33:04.343 # +sdown master mymaster 192.168.69.81 6379
Ensuite, nous avons un odown, câest Ă dire que le serveur est vu comme injoignable par au moins le nombre de serveur du quorum, comme nous avons indiquĂ© 2, il faut au moins les deux serveurs pour quâune promotion dâun nouveau soit rĂ©alisĂ©e :
37873:X 18 Jun 2023 21:33:05.420 # +odown master mymaster 192.168.69.81 6379 #quorum 3/2
db03 a été élu comme nouveau master :
37873:X 18 Jun 2023 21:34:17.255 # +selected-slave slave 192.168.69.83:6379 192.168.69.83 6379 @ mymaster 192.168.69.81 6379 37873:X 18 Jun 2023 21:34:17.437 # +promoted-slave slave 192.168.69.83:6379 192.168.69.83 6379 @ mymaster 192.168.69.81 6379
db02 est reconfiguré comme slave de db03 :
37873:X 18 Jun 2023 21:34:17.453 * +slave-reconf-sent slave 192.168.69.82:6379 192.168.69.82 6379 @ mymaster 192.168.69.81 6379 37873:X 18 Jun 2023 21:34:17.510 * +slave-reconf-inprog slave 192.168.69.82:6379 192.168.69.82 6379 @ mymaster 192.168.69.81 6379 37873:X 18 Jun 2023 21:34:18.623 * +slave slave 192.168.69.82:6379 192.168.69.82 6379 @ mymaster 192.168.69.83 6379
Enfin, on peut le confirmer via la cli redis sur db03 :
127.0.0.1:6379> INFO REPLICATION # Replication role:master connected_slaves:1 slave0:ip=192.168.69.82,port=6379,state=online,offset=481996,lag=1FĂ©licitations, Redis sâest automatiquement reconfigurĂ© sans intervention. Que se passe-t-il dĂ©sormais pour notre ancien master ? Au redĂ©marrage de redis, il est devenu un slave. Cela se voit dâailleurs dans les logs sentinel des autres serveurs. Il est possible de forcer via la cli sentinel un failover vers un autre serveur :127.0.0.1:26379> SENTINEL failover mymaster OK 127.0.0.1:26379> INFO sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 sentinel_simulate_failure_flags:0 master0:name=mymaster,status=ok,address=192.168.69.82:6379,slaves=2,sentinels=4Il nâest toutefois pas possible dâindiquer expressĂ©ment quel serveur sera promu. Une maniĂšre de le contrĂŽler est dâutiliser la directive slave-priority pour favoriser un serveur avec une prioritĂ© plus faible.
4 HAProxy
Dans un monde idĂ©al, votre client Redis aura un support de Sentinel. Il saura alors contacter sentinel pour connaĂźtre le master et sây connecter. En rĂ©cupĂ©rant la liste des serveurs, il sera en mesure de distribuer les requĂȘtes en lecture sur les slaves pour rĂ©partir la charge. En cas dâanomalie du master, il saura enfin gĂ©rer le failover.
En pratique, on ne peut pas toujours connaĂźtre le niveau de support des clients notamment lorsque lâon fournit la plateforme Ă des dĂ©veloppeurs tiers. Dans ce cas prĂ©cis, je recommande dâutiliser le classique HAProxy qui sait tout faire en matiĂšre de rĂ©partition de charge que je vous laisse installer via les packages de la distribution.
Le cas particulier quâil faut nĂ©anmoins gĂ©rer câest la dĂ©tection du master. De maniĂšre simple, si le port de destination rĂ©pond, on considĂšre souvent que le service est fourni par le backend. Dans notre cas, il va falloir identifier le serveur dont le rĂŽle est le master, souvenez-vous nous avions lâinformation avec INFO Replication avec la CLI Redis. Il faut donc dĂ©finir un TCP Check qui rĂ©alisera une authentification, enverra cette mĂȘme commande est aura comme backend active celui dont le rĂŽle est master :
frontend ft_redis
bind 0.0.0.0:6379 name redis
default_backend bk_redisbackend bk_redis
option tcp-check
tcp-check send AUTH\ SECRET\r\n
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send INFO\ Replication\r\n
tcp-check expect string role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
server r1 db01:6379 check inter 10s
server r2 db02:6379 check inter 10s
server r3 db03:6379 check inter 10sConclusion
Câest en terminĂ© pour ce tour dâhorizon de la haute disponibilitĂ© dâun service Redis Replication avec Sentinel. Jâinvite le lecteur curieux Ă complĂ©ter ce parcours pour utiliser les ACL et ajouter ce niveau de sĂ©curitĂ©. Jâai volontairement Ă©cartĂ© ce point pour lĂ©gĂšrement simplifier la configuration et rester ciblĂ© sur le sujet.
Lorsquâune application ne peut tolĂ©rer dâindisponibilitĂ©s ou quand il devient nĂ©cessaire de fournir de la redondance au service de bases de donnĂ©es, il convient de mettre en place une architecture limitant les risques dâinterruption de service. De mĂȘme le besoin de performance peut se faire sentir avec la croissance du nombre dâutilisateurs. Galera est une extension pour MariaDB aidant Ă rĂ©soudre ces deux situations.
Galera est une extension pour Mariadb, MySQL et Percona XtraDB offrant des fonctionnalitĂ©s de clustering multi-master. La rĂ©plication classique fonctionne en mode maitre-esclave, nĂ©cessitant donc en cas de panne ou de maintenance sur le master de mettre en Ćuvre un mĂ©canisme de failover sur le slave pour le promouvoir. Cette opĂ©ration est potentiellement complexe et manuelle.
Avec Galera il est possible de disposer de plusieurs serveurs master et donc disponibles en lecture comme en Ă©criture. La rĂ©plication est opĂ©rĂ©e de maniĂšre synchrone et peut mĂȘme ĂȘtre gĂ©o-rĂ©pliquĂ©e.
Pour amĂ©liorer les performances, il devient possible dâaugmenter le nombre de nĆuds au sein du cluster, Ă©ventuellement complĂ©tĂ© dâune mise Ă lâĂ©chelle verticale, câest Ă dire par ajout de vCPU et de RAM.
La haute disponibilitĂ© du service de bases de donnĂ©es devient donc native et ce, sans nĂ©cessiter de mettre en Ćuvre une opĂ©ration de promotion du slave comme master. Le service est rendu tant quâil y a au moins un nĆud fonctionnel, toute question de capacitĂ© Ă tenir la charge mise Ă part.
Ensuite, Galera connaĂźt trĂšs peu de limitations Ă la mise en cluster mais deux en particulier. La premiĂšre a une portĂ©e en principe limitĂ©e, elle impose le moteur de stockage InnoDB, le moteur historique MyISAM est donc Ă oublier. La seconde, peut-ĂȘtre plus contraignante, est quâune table doit avoir obligatoirement au moins une clĂ© primaire. Cela peut donc demander du travail avec les dĂ©veloppeurs qui vont coder avec cette base de donnĂ©es. Enfin, derniĂšre limitation majeure mais ce nâen est pas une pour moi, Galera nâest disponible que sous Linux mais pas les autres systĂšmes supportĂ©s par MariaDB comme Windows.
Un cluster Galera doit ĂȘtre composĂ© dâun nombre impair de nĆuds. Il nâest techniquement pas interdit de crĂ©er un cluster Ă seulement deux nĆuds, ce nâest toutefois pas recommandĂ©. En effet, on risque dans ce cas une situation de split brain. Un split brain est une situation oĂč les nĆuds qui composent le cluster se retrouvent isolĂ©s, par exemple en cas de coupure du rĂ©seau. Avec seulement deux nĆuds, ils ne pourraient dĂ©terminer quel nĆud se retrouve isolĂ©. Cela impose donc au minimum de disposer de trois serveurs, sachant que lâajout de nĆuds au cluster est trĂšs aisĂ©.
Pour la suite de cet article, je disposerai de quatre serveurs sous Ubuntu 22.04, trois serveurs de bases de données et un load balancer utilisé dans la suite de cet article :
Installation de Mariadb
Dans un premier temps et sur chaque serveur composant le cluster, nous allons simplement y installer le service mariadb.
root@db01:~# apt -y install mariadb-server root@db01:~# systemctl enable mariadb
Par dĂ©faut, MariaDB sous Ubuntu Server nâĂ©coute que sur lâinterface loopback. Comme les nĆuds du cluster vont ĂȘtre accessibles et communiquer via le rĂ©seau, il faut faire en sorte que MariaDB Ă©coute sur une socket accessible depuis les autres nĆuds, idĂ©alement en restreignant depuis une interface. Pour faire simple, nous allons le faire Ă©couter sur toutes les interfaces :
root@db01:~# sed -i 's/^bind-address.*/bind-address = 0.0.0.0/g' /etc/mysql/mariadb.conf.d/50-server.cnf root@db01:~# systemctl restart mariadb
Ajout du module Galera
Toujours sur chaque nĆud, nous allons ensuite installer le module galera et Ă©teindre mariadb
root@db01:~# apt -y install galera-4 root@db01:~# systemctl stop mariadb
Il faut maintenant activer la rĂ©plication avec le provider wsrep et surtout dĂ©clarer lâensemble des nĆuds participants au cluster. La configuration se fait dans le fichier /etc/mysql/mariadb.conf.d/60-galera.cnf :
wsrep_on active tout simplement la rĂ©plication Galera. Le nom du cluster doit quant Ă lui ĂȘtre identique sur lâensemble des nĆuds. Enfin, wsrep_cluster_address doit contenir les IP de tous les membres du cluster, sĂ©parĂ©s par une virgule.
[galera] wsrep_on = ON wsrep_cluster_name = "MariaDB Galera Cluster" wsrep_cluster_address = gcomm://192.168.69.81,192.168.69.82,192.168.69.83 binlog_format = row default_storage_engine = InnoDB bind-address = 0.0.0.0 wsrep_slave_threads = 4 innodb_flush_log_at_trx_commit = 1 log_error = /var/log/mysql/error-galera.log
wsrep_slave_threads (wsrep_applier_threads) dans les versions plus rĂ©centes permet de gĂ©rer le nombre de threads pour traiter les write-sets, les transactions commitĂ©s lors de la rĂ©plication. GĂ©nĂ©ralement on souhaite avoir deux fois le nombre de cĆurs du serveur pour des performances correctes.
La configuration prĂ©cĂ©dente terminĂ©e, il est maintenant possible de dĂ©marrer le cluster. Un nĆud doit ĂȘtre utilisĂ© pour bootstraper le cluster avec la commande galera_new_cluster.
root@db01:~# galera_new_cluster
Cette commande initialise le Primary component du cluster et dĂ©marre Mariadb. Le primary Component est lâensemble des nĆuds qui forment le cluster, qui peuvent communiquer ensemble et assurer le commit dâune transaction. Chacun des nĆuds dĂ©marrĂ©s par la suite se connecteront Ă ce nĆud pour dĂ©marrer la rĂ©plication. En terminologie Galera, il sâagit du State Snapshot Transfert (SST).
Le journal de log définit plus tÎt doit nous confirmer à ce propos que le bootstrap est lancé et que le statut du serveur passe en Joined.
2023-03-05 22:34:39 0 [Note] WSREP: Server status change initializing -> initialized 2023-03-05 22:34:39 0 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification. 2023-03-05 22:34:39 1 [Note] WSREP: Bootstrapping a new cluster, setting initial position to 00000000-0000-0000-0000-000000000000:-1 2023-03-05 22:34:39 4 [Note] WSREP: Cluster table is empty, not recovering transactions 2023-03-05 22:34:39 1 [Note] WSREP: Server status change initialized -> joined
Les autres nĆuds du cluster peuvent ensuite ĂȘtre dĂ©marrĂ©s normalement. Ils vont se connecter au Primary Component, lancer un state transfert pour synchroniser la copie locale de la base de donnĂ©es afin dâĂȘtre synchronisĂ© au cluster.
root@db02:~# systemctl start mariadb
ContrĂŽlons de nouveau nos logs, et lâon peut constatĂ© que pas mal de lignes ont dĂ©filĂ©. Ce que lâon doit retenir câest que les nouveaux nĆuds ont initialisĂ© un state transfert depuis lâun des serveurs dĂ©jĂ synchronisĂ©s et que lâĂ©tat doit ĂȘtre passĂ© en « complete ».
2023-03-05 22:37:37 0 [Note] WSREP: Member 1.0 (db03) requested state transfer from '*any*'. Selected 0.0 (db02)(SYNCED) as donor. 2023-03-05 22:37:39 0 [Note] WSREP: (ea0f8bad-ad8c, 'tcp://0.0.0.0:4567') turning message relay requesting off 2023-03-05 22:37:39 0 [Note] WSREP: 0.0 (db02): State transfer to 1.0 (db03) complete. 2023-03-05 22:37:39 0 [Note] WSREP: Member 0.0 (db02) synced with group. 2023-03-05 22:37:42 0 [Note] WSREP: 1.0 (db03): State transfer from 0.0 (db02) complete. 2023-03-05 22:37:42 0 [Note] WSREP: Member 1.0 (db03) synced with group.
Chaque nĆud peut ensuite ĂȘtre dĂ©marrĂ© ou redĂ©marrĂ© indĂ©pendamment avec systemctl. Cependant, si chaque nĆud a Ă©tĂ© arrĂȘtĂ© proprement, le cluster nâest plus formĂ© et il est nĂ©cessaire Ă nouveau de bootstrap le cluster avec galera_new_cluster.
Vous lâaurez devinĂ©, en cas de maintenance, il conviendra de dĂ©marrer en premier le dernier nĆud qui a Ă©tĂ© arrĂȘtĂ© afin dâavoir des donnĂ©es cohĂ©rentes. Le problĂšme va se poser si lâon ignore lâordre dâarrĂȘt ou que le cluster sâest mal arrĂȘtĂ©. Il pourra dans ce cas ĂȘtre nĂ©cessaire dâindiquer que le nĆud sur lequel on souhaite intialiser le bootstrap du cluster est en mesure de le prendre en charge, cela se fait dans le fichier /var/lib/mysql/grastate.dat avec la variable safe_to_bootstrap Ă forcer Ă 1. Cette valeur est Ă zĂ©ro aprĂšs le bootstrap pour Ă©viter une initialisation accidentelle dâun cluster dĂ©jĂ bootstrapĂ©. De prĂ©fĂ©rence, il sâagit dâune opĂ©ration Ă utiliser en derniers recours.
root@db01:~# cat /var/lib/mysql/grastate.dat # GALERA saved state version: 2.1 uuid: ea14ca85-bba5-11ed-bf58-0b029f897fa2 seqno: -1 safe_to_bootstrap: 0
En thĂ©orie, notre cluster est en place. Pour le confirmer vĂ©rifions avec des donnĂ©es. Commençons par crĂ©er une base de donnĂ©es nommĂ©e galeratest qui ne comporte quâune seule table :
root@db01:~# mysql -u root -e "CREATE DATABASE galeratest;" root@db01:~# mysql -u root galeratest -e "CREATE TABLE test(ID int NOT NULL AUTO_INCREMENT, data varchar(255) NOT NULL, PRIMARY KEY (ID) );"
Peuplons maintenant cette table avec un millier dâenregistrements :
root@db01:~# for i in {1..1000}; do mysql -u root galeratest -e "INSERT INTO test (\`data\`) VALUES ('test$i');"; done
Et enfin, vérifions sur un autre serveur que nous retrouvons bien nos enregistrements :
root@db02:~# mysql -u root galeratest -e "SELECT COUNT(ID) FROM test;" +-----------+ | COUNT(ID) | +-----------+ | 1000 | +-----------+
Câest parfait, nous avons bien nos 1000 lignes dans la table, les donnĂ©es sont donc bien rĂ©pliquĂ©es.
Superviser lâĂ©tat du cluster
En environnement de production, il convient de vĂ©rifier que chaque nĆud est connectĂ© aux autres membres cluster et quâil est bien rĂ©pliquĂ©. Ces opĂ©rations doivent ĂȘtre vĂ©rifiĂ©es Ă minima manuellement et ce rĂ©guliĂšrement et idĂ©alement automatiquement, que ce soit via des scripts ou une intĂ©gration de ceux-ci avec une solution de supervision type Nagios.
Galera permet de rĂ©cupĂ©rer localement sur chaque nĆud lâĂ©tat du cluster avec un certain nombre de variables de statut. Les diffĂ©rentes variables wsrep du provider Galera sont accessibles au travers de SHOW STATUS, de maniĂšre similaire aux variables de statut de MariaDB.
A lâissue du dĂ©ploiement prĂ©cĂ©dent, wsrep_ready permet dĂ©jĂ de visualiser si le nĆud est en mesure dâaccepter les requĂȘtes. Si la rĂ©ponse est « OFF » le serveur renverra des erreurs aux clients.
MariaDB [mysql]> show status like 'wsrep_ready'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | wsrep_ready | ON | +---------------+-------+
Ensuite, wsrep_connected doit renvoyer ON. Si cette valeur renvoie OFF, alors le nĆud nâest pas connectĂ© au cluster. Cela peut ĂȘtre lâorigine dâune erreur de configuration ou bien dâune anomalie rĂ©seau.
MariaDB [mysql]> show status like 'wsrep_connected'; +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | wsrep_connected | ON | +-----------------+-------+
Enfin, wsrep_cluster_size doit renvoyer le nombre de nĆuds prĂ©sents dans le cluster. En Ă©tat nominal il sâagit logiquement du nombre de nĆuds total du cluster. Toutefois cette valeur permet Ă©galement de gĂ©rer les niveaux dâalertes pour lequel le nombre minimum de nĆuds pour rendre le service. En thĂ©orie, un nĆud suffit Ă rendre le service, toutes considĂ©rations de performances mises Ă part :
MariaDB [(none)]> show status like 'wsrep_cluster_size'; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | wsrep_cluster_size | 3 | +--------------------+-------+
Ensuite, il faut vĂ©rifier que chaque nĆud est connectĂ© au primary component et donc quâil est dans un Ă©tat sain et dans un Ă©tat oĂč il peut recevoir des mises Ă jour des autres nĆuds.
MariaDB [(none)]> show status like 'wsrep_cluster_status'; +----------------------+---------+ | Variable_name | Value | +----------------------+---------+ | wsrep_cluster_status | Primary | +----------------------+---------+
Pour aller plus loin dans la visualisation de lâĂ©tat du nĆud au sein du cluster, la variable wsrep_local_state_comment donne davantage dâindications sur lâĂ©tat du nĆud. Cette variable possĂšde quatre valeurs :
âą Joining : le nĆud est en train de rejoindre le cluster
âą Donor/Desynced : le nĆud est la source de rĂ©plication dâun autre nĆud en train de rejoindre le cluster
âą Joined : le nĆud a rejoint le cluster
âą Synced : le nĆud est synchronisĂ© avec le cluster
MariaDB [(none)]> show status like 'wsrep_local_state_comment'; +---------------------------+--------+ | Variable_name | Value | +---------------------------+--------+ | wsrep_local_state_comment | Synced | +---------------------------+--------+
Enfin, au-delĂ de lâĂ©tat du cluster, il convient de superviser les performances de rĂ©plication. La variable wsrep_local_recv_queue_avg donne la longueur moyenne de la file dâattente de rĂ©plication. Si elle est supĂ©rieure Ă 0 cela signifie que le node a un peu de latence dans lâapplication des write-sets. Au delĂ de 0,5 il faut vĂ©rifier quâil nây a pas de goulot dâĂ©tranglement. Sâagissant dâune valeur moyenne, il convient dâobserver dans ce cas lĂ les valeurs minimales et maximales, wsrep_local_recv_queue_min et wsrep_local_recv_queue_max afin dâavoir lâamplitude rĂ©elle.
MariaDB [(none)]> show status like 'wsrep_local_recv_queue_avg'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | wsrep_local_recv_queue_avg | 0.142857 | +----------------------------+----------+
Ces variables sont les principales Ă superviser. En pratique, il en existe une soixantaine visibles avec un « show status like âwsrep_%; ». La plupart ne seront dâaucune utilitĂ© mais celles prĂ©sentĂ©es restent celles quâil faut connaĂźtre impĂ©rativement.
Notre cluster Galera est montĂ©, les clients vont ensuite devoir sây connecter. Si on configure les accĂšs sur le nom dâhĂŽte ou lâadresse IP dâun des nĆuds, alors en cas dâindisponibilitĂ© de celui-ci les autres nĆuds seront fonctionnels mais inutilisĂ©s. Une mĂ©thode consiste Ă utiliser du round robin DNS. Câest Ă dire que pour un nom dâhĂŽte, on va dĂ©clarer trois enregistrements type A pointant vers chacuns des adresses IP des nĆuds du cluster. Cependant cette solution nâest pas idĂ©ale et peut renvoyer vers un nĆud non fonctionnel.
Une meilleure solution consiste Ă utiliser un load balancer qui va exposer le service MariaDB mais qui aura en backend les diffĂ©rents nĆuds qui composent le cluster Galera. Le load balancer permet en outre de tester la disponibilitĂ© des backends et ne prĂ©sente que les nĆuds fonctionnels. HAProxy est le load balancer libre le plus populaire et le plus aboutit pour gĂ©rer ces situations. Commençons pas lâinstallation sur une VM dĂ©diĂ©e.
root@lb:~# apt -y install haproxy root@lb:~# systemctl enable haproxy
En suite, nous aurons besoin dâun utilisateur non privilĂ©giĂ© qui permettra Ă HAProxy de sâauthentifier pour vĂ©rifier quâune connexion peut ĂȘtre Ă©tablie.
MariaDB [mysql]> CREATE USER 'haproxy'@'192.168.69.70';
Enfin nous allons configurer HAProxy dans le fichier /etc/haproxy/haproxy.cfg et ajouter ces lignes Ă la fin du fichier.
listen galera bind 192.168.69.70:3306 balance leastconn mode tcp option tcpka option mysql-check user haproxy option tcplog server db1 192.168.69.81:3306 check weight 1 server db2 192.168.69.82:3306 check weight 1 server db3 192.168.69.83:3306 check weight 1
Le service Galera est exposĂ© sur le port MariaDB classique, 3306/TCP via lâIP du load balancer 192.168.69.70. Balance leastconn est lâalgorithme de rĂ©partition de charge, dans ce cas il sâagit dâenvoyer les connexions vers le serveur qui en a le moins, nous pourrions tout autant utiliser du round robin ou du source IP. Le check mysql-check est utilisĂ© avec lâutilisateur haproxy créé prĂ©cĂ©demment. Haproxy enverra un paquet pour sâauthentifier et un second pour mettre fin proprement Ă la connexion. Un simple check tcp engendrerait dans les logs une fin prĂ©maturĂ©e de la connexion. Enfin, on dĂ©finit chaque nĆud vers lequel on renvoie les connexions. Check sert Ă indiquer quâon teste la disponibilitĂ© du backend et weight 1 que les connexions seront distribuĂ©es de maniĂšre Ă©quitable entre les backends.
Un redĂ©marrage du service plus tard et on peut contrĂŽler lâĂ©tat de nos backends avec hatop. Si au moins un backend apparaĂźt comme UP lâaccĂšs au travers de la VIP est possible.
root@lb:~# systemctl restart haproxy root@lb:~# hatop -s /run/haproxy/admin.sock
Idéalement et dans un contexte de production, il faudrait également mettre en haute disponibilité le load balancer via une paire de load balancer HAProxy partageant une VIP en VRRP avec Keepalived par exemple. Ceci afin de ne pas créer de nouveau point unique de panne. Mais cela dépasse le cadre de cet article.
Galera est un moyen simple pour monter une solution de Haute DisponibilitĂ© pour vos bases de donnĂ©es MariaDB avec peu de contraintes dâexploitation. Il nây a donc plus de raisons de rĂ©veiller les admins la nuit pour dĂ©panner un serveur MariaDB en mono serveur dĂšs lors que vous administrez des applications critiques. Pour aller plus loin, il sâagit dâun sujet qui se prĂȘte particuliĂšrement bien Ă des dĂ©ploiements orchestrĂ©s par Ansible ou Puppet.
Références
[BASEDOC] Base documentaire des Ăditions Diamond : https://connect.ed-diamond.com/
[1] Documentation officielle de Galera : https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/
[2] Aller plus loin avec MariaDB, LP 130 par Masquelin Mickaël
[3] Ă la dĂ©couverte dâHAProxy, LP HS 55 par Assmann Baptiste (bedis)
Vagrant est un logiciel libre permettant de dĂ©ployer rapidement des machines virtuelles. Câest un logiciel dĂ©veloppĂ© par la sociĂ©tĂ© Hashicorp, dĂ©jĂ connue pour dâautres logiciels comme Terraform, Packer ou Vault. Historiquement liĂ© Ă VirtualBox, Vagrant sâest dĂ©sormais largement ouvert Ă dâautres solutions de virtualisation comme Libvirt ou de conteneurisation comme Docker.
Vagrant permet de dĂ©ployer rapidement des environnements Ă partir de fichiers de description, les Vagrantfile. Sâagissant de fichiers textes, ils sâintĂšgrent de fait Ă toute la chaine GIT ou CI/CD et donc aux bonnes pratiques de dĂ©veloppement. Lâutilisation dâun outil automatisĂ© permet ainsi de gagner un temps prĂ©cieux et sans valeur ajoutĂ©e sur lâinstanciation de machines virtuelles.
Lâautomatisation du procĂ©dĂ© permet Ă©galement de produire des environnements similaires au sein dâune Ă©quipe et Ă chaque lancement de Vagrant. Les environnements de test sont donc reproductibles, gommant les diffĂ©rences quâil peut y avoir selon les habitudes de travail et la dĂ©rive qui peut se crĂ©er sur un environnement au fil du temps.
A titre individuel câest aussi un moyen de maquetter rapidement un projet ou un outil avant de se pencher sur une infrastructure de production et dâoutil type Terraform, plus appropriĂ©s.
Vagrant offre une compatibilitĂ© avec plusieurs hyperviseurs : vmware, hyper-v mais sait Ă©galement dĂ©ployer des ressources localement via VirtualBox ou libvirt. Câest aussi un bon moyen de fournir des environnements de tests qui ne monopolisent pas les prĂ©cieuses ressources des serveurs ou sans avoir Ă disposition dâinfrastructure dĂ©diĂ©e, qui plus est disponibles sans connexion rĂ©seau.
Sous ma Debian testing, vagrant est packagé par défaut donc un simple apt-get install suffit. Pour Ubuntu, Centos, Fedora, Hashicorp met à disposition des paquets adaptés à la distribution.
Pour les autres, un zip contient un binaire Ă pousser dans un rĂ©pertoire appelĂ© dans votre PATH. Vagrant, câest tout simplement ça, tout un workflow unifiĂ© autour dâune mĂȘme CLI.
Un Virtualbox fonctionnel sera nécessaire pour la suite, avec votre utilisateur dans le bon groupe pour éviter de devoir jouer du sudo.
Les boxes Vagrant sont des images de systĂšmes dĂ©ployables au sein de tout environnement Vagrant. Il sâagit en pratique dâune façon de packager une distribution afin de permettre un dĂ©ploiement uniforme quel que soit le systĂšme dâexploitation ou lâhyperviseur utilisĂ©. En pratique, toutes les box ne sont pas compatibles avec tous les hyperviseurs, Ă plus forte raison lorsquâil sâagit dâune image docker.
De nombreuses boxes sont disponibles sur le site https://app.vagrantup.com et certaines sont officiellement dĂ©veloppĂ©es par les distributions majeures ou certains Ă©diteurs. Les seules boxes officielles sont toutefois limitĂ©es aux boxes Hashicorp en Bento. Un systĂšme de versionning permet Ă©galement de sâassurer de dĂ©ployer sur une version connue et validĂ©e.
Pour tĂ©lĂ©charger une box, il suffit dâun vagrant box add suivi du nom de la box :
vagrant box add centos/7 ==> box: Loading metadata for box 'centos/7' box: URL: https://vagrantcloud.com/centos/7 This box can work with multiple providers! The providers that it can work with are listed below. Please review the list and choose the provider you will be working with. 1) hyperv 2) libvirt 3) virtualbox 4) vmware_desktop Enter your choice: 3 ==> box: Adding box 'centos/7' (v2004.01) for provider: virtualbox box: Downloading: https://vagrantcloud.com/centos/boxes/7/versions/2004.01/providers/virtualbox.box Download redirected to host: cloud.centos.org box: Calculating and comparing box checksum... ==> box: Successfully added box 'centos/7' (v2004.01) for 'virtualbox'!
Et pour lister les boxes installées :
vagrant box list centos/7 (virtualbox, 2004.01) ubuntu/focal64 (virtualbox, 20210513.0.0)
Vous lâaurez compris donc pour la logique sous jacente, il y a donc notamment les commandes update, remove (et dâautres) pour suivre le cycle de vie des boxes locales.
Vagrant va chercher à provisionner le type de machine en fonction de ce qui est décrit dans le fichier Vagrantfile du répertoire du projet. Un fichier Vagrantfile est un fichier Ruby mais qui ne nécessite aucune connaissance de ce langage.
Le fichier Vagrantfile sert Ă dĂ©crire tout ce qui constitue le dĂ©ploiement : le provider, le dimensionnement de la VM et Ă©ventuellement la post-installation. Sâagissant dâun fichier texte, il a toute sa place sur un dĂ©pĂŽt Git.
Voyons un premier exemple de Vagrantfile. A minima, Vagrant a besoin du nom du dĂ©ploiement et du type dâimage source. Le « 2 » est simplement la version du fichier de configuration.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" end
Vérifions ensuite que notre syntaxe est sans erreur :
vagrant validate Vagrantfile validated successfully.
Sâil nây a pas dâerreur, on peut lancer lâinstanciation de notre projet :
vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'ubuntu/focal64'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'ubuntu/focal64' version '20210513.0.0' is up to date... ==> default: Setting the name of the VM: 1simple_default_1621158383628_85938 ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key default: default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest... default: Removing insecure key from the guest if it's present... default: Key inserted! Disconnecting and reconnecting using new SSH key... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... ==> default: Setting hostname... ==> default: Mounting shared folders... default: /vagrant => /home/julien/vagrant/1simple
LâĂ©tat du dĂ©ploiement peut ĂȘtre visualisĂ© avec vagrant status :
vagrant status Current machine states: default running (virtualbox) [âŠ]
A lâinstanciation du projet, une paire de clĂ© SSH est créée. Elle est stockĂ©e dans lâarborescence du projet vagrant dans .vagrant/machines/NOM_BOX/virtualbox/private_key permettant de se connecter Ă lâinstance sans mot de passe.
vagrant ssh default Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64) [...] Last login: Sun May 16 09:54:12 2021 from 10.0.2.2 vagrant@vagrantbox:~$ logout
En fin de cycle, les VM sont ensuite arrĂȘtĂ©es et le projet dĂ©truit. Lorsque le dĂ©ploiement nâimplique quâune seule instance, il nâest pas nĂ©cessaire de prĂ©ciser le nom de la VM.
vagrant halt default ==> default: Attempting graceful shutdown of VM... vagrant destroy default: Are you sure you want to destroy the 'default' VM? [y/N] y ==> default: Destroying VM and associated drives...
Ainsi, le fait de relancer la commande vagrant up va permettre de provisionner un environnement neuf et propre Ă chaque cycle.
Pour ne pas partir de zĂ©ro et avoir un modĂšle de fichier Vagrant Ă personnaliser, nous aurions pu partir de la commande vagrant init. En effet, celle-ci créé un fichier Vagrantfile dans le dossier courant qui peut ĂȘtre ensuite personnalisĂ© selon ses souhaits.
Notre premier exemple servait Ă mettre le pied Ă lâĂ©trier sur le cycle de vie dâun environnement Vagrant. Cependant, il sera systĂ©matiquement ou presque nĂ©cessaire de configurer lâenvironnement Ă dĂ©ployer en fonction des ressources disponibles sur la machine qui hĂ©berge les machines virtuelles ou encore en fonction des besoins rĂ©els des applicatifs.
Pour cela, Vagrant expose certaines fonctionnalitĂ©s du provider ou un jeu de fonctionnalitĂ©s communes. Par exemple, je vais avoir besoin dâaccĂ©der Ă mon serveur Web de dĂ©veloppement donc je redirige le port local non privilĂ©giĂ© 8080 afin de pouvoir accĂ©der Ă ma VM via http://localhost:8080.
Plus spĂ©cifiquement pour Virtualbox, via lâattribut customize il est possible dâexposer nâimporte quel paramĂštre de la commande VboxManage. Dâun cĂŽtĂ© cela limite la qualitĂ© de la couche dâabstraction fournie par le provider Virtualbox pour Vagrant mais de lâautre celui-ci nâen limite pas les possibilitĂ©s.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" config.vm.network "private_network", ip: "192.168.56.10", virtualbox__intnet: "nat" config.vm.provider :virtualbox do |vbox| vbox.gui = false vbox.linked_clone = false vbox.memory = 2048 vbox.cpus = 2 vbox.customize ["modifyvm", :id, "--uart1", "0x3F8", 4] end end
Jâai Ă©galement tendance Ă utiliser les clones liĂ©s afin de limiter lâimpact sur la consommation des disques mais ce point est loin dâĂȘtre obligatoire.
Les quelques dĂ©ploiements prĂ©cĂ©dents Ă©taient tous tournĂ©s autour dâune unique VM. Cependant, les infrastructures de production vont plutĂŽt ĂȘtre dĂ©ployĂ©es sur plusieurs serveurs pour :
â RĂ©pondre Ă des schĂ©mas dâarchitecture multi-tiers
â Combiner plusieurs systĂšmes dâexploitation
â Distribuer la charge ou la disponibilitĂ© du service sur plusieurs machines
Reproduire ce type dâinfrastructure sur une seule machine reviendrait Ă construire un environnement dev/test diffĂ©rent de la production et donc ne pas couvrir les cas listĂ©s ci-dessus. Vagrant est capable de dĂ©ployer plusieurs VM au sein dâun mĂȘme projet avec le mĂȘme workflow. Il suffit pour cela de lister successivement les VM Ă dĂ©ployer :
Vagrant.configure("2") do |config| config.vm.define "revproxy" do |host| host.vm.box = "revproxy" host.vm.box = "ubuntu/focal64" end config.vm.define "www", primary: true do |host| host.vm.box = "www" host.vm.box = "ubuntu/focal64" end config.vm.define "db" do |host| host.vm.box = "db" host.vm.box = "ubuntu/focal64" end end
Pour provisionner lâinfrastructure, les mĂȘmes commandes sâappliquent. Cependant, si lâon souhaite simplement instancier lâun des serveurs de lâinfrastructure, par exemple le serveur de base de donnĂ©es, il est possible de prĂ©ciser celui-ci via vagrant up db puis vagrant ssh db pour sây connecter.
Ce listing de VM est un peu ennuyeux cependant, voyons comment factoriser cela en utilisant une variable Ruby de type Symbol :
hosts = [ { :hostname => 'revproxy', :ip => '192.168.56.10', :mem => 1024, :cpu => 1 }, { :hostname => 'www', :ip => '192.168.56.11', :mem => 2048, :cpu => 2 }, { :hostname => 'db', :ip => '192.168.56.12', :mem => 2048, :cpu => 2 }, ] Vagrant.configure("2") do |config| hosts.each do |host| config.vm.define host[:hostname] do |hostconfig| hostconfig.vm.box = "ubuntu/focal64" hostconfig.vm.hostname = host[:hostname] hostconfig.vm.network :private_network, ip: host[:ip] hostconfig.vm.provider :virtualbox do |vbox| vbox.gui = false vbox.linked_clone = true vbox.memory = host[:mem] vbox.cpus = host[:cpu] end end end end
Lorsque plusieurs projets comme ces deux versions du multi-instances sont démarrés, il est possible de les visualiser via la commande vagrant global-status. Cela donne également le chemin racine du projet Vagrant sur le systÚme de fichiers.
vagrant global-status id name provider state directory ------------------------------------------------------------------------- 36e198a revproxy virtualbox running /home/julien/vagrant/6multi 971634f www virtualbox running /home/julien/vagrant/6multi 5f4e571 db virtualbox running /home/julien/vagrant/6multi 9008b27 revproxy virtualbox running /home/julien/vagrant/7multi 88fcf71 www virtualbox running /home/julien/vagrant/7multi 177b8c7 db virtualbox running /home/julien/vagrant/7multi
Lorsque lâon provisionne un environnement, il peut ĂȘtre nĂ©cessaire de dĂ©clencher des actions avant ou aprĂšs le provisionnement dâune VM. Vagrant permet dâutiliser des triggers avant ou aprĂšs certains Ă©tats : up, destroy, reload, all, etc.
Les commandes peuvent ĂȘtre lancĂ©es aussi bien sur lâhĂŽte que sur lâinvitĂ©. Dans lâexemple ci-dessous Ă chaque fois que je provisionne ma VM, je rĂ©cupĂšre le code Ă jour depuis le dĂ©pĂŽt GIT et lorsque je supprime celle-ci, je rĂ©alise une sauvegarde de la base de donnĂ©es.
Bon Ă savoir, par dĂ©faut lâexĂ©cution de Vagrant Ă©choue si un trigger Ă©choue mais ce comportement peut ĂȘtre modifiĂ©.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" config.trigger.after :up do |trigger| trigger.run = {inline: "git clone https://github.com/WordPress/WordPress.git"} trigger.on_error = :continue end config.trigger.before :destroy do |trigger| trigger.run_remote = {inline: "mysqldump -u root wordpress > /vagrant/backup.sql"} end end
Pour permettre lâĂ©change de donnĂ©es entre lâhĂŽte et les VM invitĂ©es, Vagrant permet de monter un volume sur la VM. Le volume peut ĂȘtre aussi bien un dossier local quâun montage dâun systĂšme de fichiers rĂ©seau comme du NFS ou du CIFS.
Vagrant supporte Ă©galement les options de montage que lâon peut passer Ă la commande mount. Dans lâexemple ci-dessous, je monte le rĂ©pertoire html de mon home dans le rĂ©pertoire par dĂ©faut dâApache en remappant les droits. Si le dossier /home/julien/html nâexiste pas, il est créé.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" config.vm.synced_folder "/home/julien/html/", "/var/www/html", owner: "apache", group: "apache" create: true end
Provisionner des VM, câest sympa me direz-vous mais sâil reste tout le travail de personnalisation des systĂšmes, nous nâavons guĂšre fait plus que cloner une VM. Une fois que les VM sont provisionnĂ©es depuis un modĂšle de box, câest un OS vierge qui est fourni quand on arrive au vagrant SSH. Bien entendu rien nâexclue de faire lâinstallation Ă suivre Ă la main mais ce serait dommage.
Les provisionners sont appelĂ©s Ă deux moments par Vagrant, Ă lâinstanciation avec un vagrant up ou bien avec un vagrant provision pour une instance dĂ©jĂ lancĂ©e. Dans lâexemple-ci dessous, vagrant dĂ©ploie les mises Ă jour via le shell ainsi que le minimum de packages que jâattends sur mes instances.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" config.vm.network "private_network", ip: "192.168.56.10", virtualbox__intnet: "nat" config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.provider :virtualbox do |vbox| vbox.gui = false vbox.memory = 2048 vbox.cpus = 2 end config.vm.provision "shell", inline: <<-SHELL apt update apt -y dist-upgrade apt -y install screen htop nmap SHELL end
Bash nâest pas le seul provisionner supportĂ© par Vagrant. En pratique, le support est mĂȘme trĂšs complet car Vagrant permet dâutiliser la plupart des outils de gestion de configuration qui existent sous Linux : Chef, Puppet, Salt, CFEngine et Ansible ou mĂȘme cloud-init.
A titre personnel, je nâai dâexpĂ©rience quâavec Puppet et Ansible, je vous propose de partir sur Ansible celui-ci Ă©tant dĂ©sormais largement plus rĂ©pandu. Pour ansible, partons du petit playbook ci-dessous. Celui-ci dĂ©ploie les mises Ă jour et installe un serveur web apache, tout ce quâil y a de plus classique.
--- - hosts: all become: true gather_facts: true tasks: - name: apt upgrade apt: upgrade: dist update_cache: yes autoclean: yes autoremove: yes - name: install apache apt: name=apache2 state=present - name: Enable service Apache2 service: name: apache2 state: started enabled: yes
Le provisionner ansible nĂ©cessite lâinstallation dâansible sur la machine qui lance Vagrant. De la maniĂšre la plus simple, il suffit dâindiquer le nom du playbook Ă lancer. Mon playbook est volontairement succinct car ce nâest pas ici le propos mais rien nâinterdit des dĂ©ploiements plus complexes avec des rĂŽles ou lâutilisation dâun vault par exemple qui sont aussi plus reprĂ©sentatifs des usages rĂ©els dâansible.
Pour ceux qui ont lâhabitude dâansible, vous aurez remarquĂ© lâabsence notable de lâutilisation dâun inventaire. En pratique, il est possible dâen spĂ©cifier un dans la configuration mais Vagrant a lâintelligence lorsquâaucun inventaire nâest spĂ©cifiĂ© dâen auto-gĂ©nĂ©rer un pour nous.
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/focal64" config.vm.hostname = "vagrantbox" config.vm.network "private_network", ip: "192.168.56.10", virtualbox__intnet: "nat" config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.provider :virtualbox do |vbox| vbox.gui = false vbox.linked_clone = true vbox.memory = 1024 vbox.cpus = 1 end config.vm.provision "ansible" do |ansible| ansible.playbook = "playbook.yml" ansible.compatibility_mode = "2.0" end end
Utiliser Vagrant avec un autre provider a un premier intĂ©rĂȘt certain câest que le mĂȘme outil sera utilisĂ© quel que soit lâenvironnement mis Ă disposition ou le projet sous-jacent.
Ainsi en terme de formation ou de prise en main, la courbe dâapprentissage est plus faible que dâapprendre un autre outil.
Comme je disais au dĂ©part, Vagrant a pour objectif dâĂȘtre relativement agnostic vis Ă vis de la solution de virtualisation. Il existe par exemple un provider docker officiel. Dans le code ci-aprĂšs, je suis parti de lâexemple du Dockerhub pour instancier un cluster WordPress avec Docker compose mais remis Ă la sauce Vagrant.
Lâimage est ici automatiquement tĂ©lĂ©chargĂ©e depuis la registry. Dans le cadre dâune utilisation en mode dĂ©veloppement, Vagrant est Ă©galement capable dâaller construire lâimage si on lui spĂ©cifie Ă la plage de lâimage le chemin vers le Dockerfile, avec lâattribut build_dir, voire mĂȘme depuis un dĂ©pĂŽt git en utilisant lâattribut git_repo. Naturellement ces trois choix sont exclusifs.
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker' Vagrant.configure("2") do |config| config.vm.define "db" do |db| db.vm.synced_folder ".", "/vagrant", disabled: true db.vm.provider "docker" do |d| d.image = "mysql:5.7" d.name = "wordpressdb" d.remains_running = true d.volumes = ["/home/julien/mysql:/var/lib/mysql"] d.env = {"MYSQL_DATABASE" => "exampledb", "MYSQL_USER" => "exampleuser", "MYSQL_PASSWORD" => "examplepass", "MYSQL_RANDOM_ROOT_PASSWORD" => "'1'"} end end config.vm.define "app" do |app| app.vm.synced_folder ".", "/vagrant", disabled: true app.vm.provider "docker" do |d| d.image = "wordpress" d.ports = ["8080:80"] d.name = "wordpressapp" d.remains_running = true d.volumes = ["/home/julien/html:/var/www/html"] d.env = {"WORDPRESS_DB_HOST" => "db", "WORDPRESS_DB_USER" => "exampleuser", "WORDPRESS_DB_PASSWORD" => "examplepass", "WORDPRESS_DB_NAME" => "exampledb"} d.link("wordpressdb:mysql") end end end
Dans ce cas prĂ©sent, mon conteneur web est liĂ© au conteneur de base de donnĂ©es, il faut donc que vagrant ne lance pas lâinstanciation en parallĂšle des conteneurs, on y ajoute donc le paramĂštre âno-parallel.
vagrant up --no-parallel Bringing machine 'db' up with 'docker' provider... Bringing machine 'app' up with 'docker' provider... ==> db: Creating and configuring docker networks... ==> db: Creating the container... db: Name: wordpressdb db: Image: mysql:5.7 db: Volume: /home/julien/mysql:/var/lib/mysql db: db: Container created: 30dba7edabca0db7 ==> db: Enabling network interfaces... ==> db: Starting container... ==> app: Creating and configuring docker networks... ==> app: Creating the container... app: Name: wordpressapp app: Image: wordpress app: Volume: /home/julien/html:/var/www/html app: Port: 8080:80 app: Link: wordpressdb:mysql app: app: Container created: 19838387018b928d ==> app: Enabling network interfaces... ==> app: Starting container...
VĂ©rifions du cĂŽtĂ© de la CLI Docker quâil nây ait pas dâerreur. Parfait, nos conteneurs sont correctement instanciĂ©s.
docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 19838387018b wordpress "docker-entrypoint.sâŠ" 10 seconds ago Up 9 seconds 0.0.0.0:8080->80/tcp wordpressapp 30dba7edabca mysql:5.7 "docker-entrypoint.sâŠ" 11 seconds ago Up 10 seconds 3306/tcp, 33060/tcp wordpressdb
Si vous utilisiez un hyperviseur comme virtualbox ou libvirt pour dĂ©ployer des VM Ă la main et si vous avez besoin de rĂ©guliĂšrement recrĂ©er vos environnements de dĂ©veloppement, Vagrant devrait beaucoup apporter Ă votre trousse Ă outils. Et pour la production, Terraform est lâĂ©tape suivante si vous ne lâutilisez pas encore.
Références
[1] https://www.vagrantup.com/downloads
[2] https://app.vagrantup.com/boxes/search
[3] https://www.vagrantup.com/docs