Raft
Observer la première élection et l’échange des
heart beats par la suite.
Les nœuds portent au départ le numéro de mandat 1, il n’y a pas de
leader :
élection etcd - étape 1
Il faut attendre que l’un des nœuds ait atteint son timeout
pour qu’il propose une élection. Ici, c’est le nœud S2 qui déclenche
l’élection :
élection etcd - étape 2
Les petites pastilles sur ce nœud S2 représentent le nombre de votes
lui étant favorable. Un vote sur cinq est pour le moment validé : le
sien. Les autres nœuds de l’agrégat reçoivent donc la candidature de S2
et y répondent tous favorablement :
élection etcd - étape 3
Nous voyons le nombre de vote augmenter au fur et à mesure que S2 les
reçoit :
élection etcd - étape 4
Finalement, S2 remporte l’élection, crée le mandat n°2 et envoie son
premier message de keep alive :
élection etcd - étape 5
Les autres se raccrochent à lui et entretiennent chacun leur time
out .
Mettre en défaut le leader (stop )
et attendre une élection.
Le même phénomène se produit et l’on arrive au mandat 3. Ici, le
timeout du nœud S1 expire en premier :
failover etcd - étape 1
S1 déclenche une élection :
failover etcd - étape 2
Les autres nœuds accordent leur vote à S1, sauf S2 qui est
éteint :
failover etcd - étape 3
S1 devient leader :
failover etcd - étape 4
Lancer plusieurs écritures (requests ) sur
le leader .
Elles apparaissent sur la droite dans le journal au fur et à mesure
que le leader les diffuse. La diffusion se fait en plusieurs
étapes. Écrite et émission depuis S1 :
Requête etcd - étape 1
Bonne réception des nœuds S3, S4 et S5 :
Requête etcd - étape 2
S0 ayant reçu une majorité d’acquittement, il valide la valeur auprès
des autres nœuds :
Requête etcd - étape 3
Tous les nœuds ont validé la valeur :
Requête etcd - étape 4
Remettre en route l’ancien leader .
Celui-ci redémarre à 2 (en tant que follower ) :
failback etcd - étape 1
Il bascule sur le mandat 3 au premier heart beat reçu, et
commence à remplir son journal :
failback etcd - étape 2
Il rattrape ensuite son journal avec autant d’échange avec le
leader que nécessaire :
Arrêter deux nœuds, dont le leader , et
observer le comportement du cluster.
Qu’en est-il de la tolérance de panne ?
Le quorum demeure au sein du cluster, il est donc toujours possible
de déclencher une élection. Comme précédemment, les trois nœuds restants
s’accordent sur un leader . Dans notre exemple, tous les nœuds
déclenchent leur propre élection en même temps :
second failover etcd - étape
1
Chaque nœud ayant voté pour lui-même, il n’y a pas de consensus sur
le nœud à élire, les nœuds redeviennent followers . L’un d’entre
eux voit son timeout atteint, ce qui donne lieu à une seconde
élection :
second failover etcd - étape
2
Elle conduit ici à l’élection du nœud S2 :
second failover etcd - étape
3
La tolérance de panne est maintenant nulle.
Arrêter un nœud secondaire (pour un total de 3
nœuds arrêtés).
Tester en soumettant des écritures au
primaire.
Le cluster continue de fonctionner : on peut lui soumettre des
écritures (requests ) :
perte quorum etcd - étape 1
Elles sont bien répliquées mais ne sont pas exécutées (ou commitées)
par le leader :
perte quorum etcd - étape 2
Pour cela, il faut que les écritures aient été répliquées vers la
majorité des nœuds du cluster. Cela signifie que le client reste en
attente de confirmation du traitement de sa demande.
Rallumer un nœud pour revenir à 3 nœuds actifs dont
un leader .
Éteindre le leader . Tenter de soumettre
des écritures.
Que se passe-t-il ?
L’un des deux nœuds survivants lance une élection :
Tentative d’élection sans quorum etcd -
étape 1
L’autre le suit :
Tentative d’élection sans quorum etcd -
étape 2
Mais comme le quorum de 3 nœuds (moitié de 5 plus 1) n’est pas
obtenu, l’élection échoue. Les time out continuent d’expirer et
de nouvelles élections sont lancées :
Tentative d’élection sans quorum etcd -
étape 3
Faute de leader , il devient impossible de soumettre des
écritures. Tout client s’appuyant dessus reçoit une erreur. Il faut
attendre le retour en ligne d’un nœud et l’élection d’un nouveau
leader .
Installation d’etcd sous
Debian
Installer etcd sur les 3 nœuds e1
,
e2
et e3
.
Les paquets etcd sont disponibles dans les dépôts officiels de Debian
12. L’installation consiste simplement à installer les deux paquets
etcd-server
et etcd-client
:
# apt-get install -y etcd-server etcd-client
Supprimer le nœud etcd créé sur chaque
serveur.
Afin de respecter la politique des paquets Debian, l’installation du
paquet etcd-server
crée automatiquement une instance etcd
locale. Nous devons la détruire avant de pouvoir construire notre
cluster etcd à trois nœuds.
Sur chaque serveur etcd, exécuter les commandes suivantes :
# systemctl stop etcd.service
# rm -Rf /var/lib/etcd/default
Configurer un cluster etcd sur les 3 nœuds
e1
, e2
et e3
.
La configuration sous Debian se situe dans le fichier
/etc/default/etcd
où doivent être renseignés les paramètres
etcd sous leur forme de variables d’environnements.
Voici le fichier de configuration commenté du nœud e1
,
veillez à adapter toutes les adresses IP
10.x.y.z
:
# nom du nœud au sein du cluster
ETCD_NAME = e1
# emplacement du répertoire de données
ETCD_DATA_DIR = /var/lib/etcd/acme
# interface et port d'écoute des autres nœuds
ETCD_LISTEN_PEER_URLS = http://10.0.0.11:2380
# interfaces et port d'écoute des clients
ETCD_LISTEN_CLIENT_URLS = http://10.0.0.11:2379,http://127.0.0.1:2379,http://[::1]:2379
# interface et port d'écoute à communiquer aux clients
ETCD_ADVERTISE_CLIENT_URLS = http://10.0.0.11:2379
#######################################
## Initialisation du cluster et du nœud
# création du nœud au sein d'un nouveau cluster
ETCD_INITIAL_CLUSTER_STATE = new
# interface et port à communiquer aux autres nœuds
ETCD_INITIAL_ADVERTISE_PEER_URLS = http://10.0.0.11:2380
# liste des nœuds composant le cluster
ETCD_INITIAL_CLUSTER = e1=http://10.0.0.11:2380,e2=http://10.0.0.12:2380,e3=http://10.0.0.13:2380
Une fois les trois fichiers de configuration établis, nous pouvons
démarrer le cluster en exécutant la commande suivante sur tous les
serveurs etcd :
# systemctl start etcd
La commande suivante doit désormais fonctionner sur n’importe quel
nœud :
$ etcdctl -wjson endpoint status | jq
[
{
"Endpoint" : "127.0.0.1:2379" ,
"Status" : {
"header" : {
"cluster_id" : 11628162814576028000 ,
"member_id" : 13786016400334385000 ,
"revision" : 95 ,
"raft_term" : 24
},
"version" : "3.4.23" ,
"dbSize" : 61440 ,
"leader" : 2266733444126347800 ,
"raftIndex" : 112 ,
"raftTerm" : 24 ,
"raftAppliedIndex" : 112 ,
"dbSizeInUse" : 53248
}
}
]
L’état du cluster est visible grace à la commande suivante :
$ etcdctl endpoint status -w table --cluster
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER |[..]| ERRORS |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| http://10.0.0.11:2379 | 9e80988e833ccb43 | 3.5.13 | 20 kB | false |[..]| |
| http://10.0.0.13:2379 | a10d8f7920cc71c7 | 3.5.13 | 29 kB | true |[..]| |
| http://10.0.0.12:2379 | abdc532bc0516b2d | 3.5.13 | 20 kB | false |[..]| |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
Installation d’etcd
sous Rocky Linux 9
Installer etcd sur les 3 nœuds e1
,
e2
et e3
.
Les paquets etcd sont disponibles depuis les dépôts PGDG pour les
distributions Red Hat 9 & dérivées (comme ici Rocky Linux 9). Il
nous faut donc au préalable configurer ce dépôt :
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/\
pgdg-redhat-repo-latest.noarch.rpm
Il est désormais possible d’installer le paquet etcd
en
activant le dépôt pgdg-rhel9-extras
:
# dnf --enablerepo=pgdg-rhel9-extras install -y etcd
Configurer un cluster etcd sur les 3 nœuds
e1
, e2
et e3
.
La configuration sur cette distribution se situe dans le fichier
/etc/etcd/etcd.conf
où sont renseignés les paramètres etcd
sous leur forme de variables d’environnements.
Ci-après les différents paramètres à modifier pour le nœud
e1
, veillez à adapter toutes les adresses
IP 10.x.y.z
:
ETCD_NAME = e1
ETCD_DATA_DIR = /var/lib/etcd/acme
ETCD_LISTEN_PEER_URLS = http://10.0.0.11:2380
ETCD_LISTEN_CLIENT_URLS = http://10.0.0.11:2379,http://127.0.0.1:2379,http://[::1]:2379
ETCD_ADVERTISE_CLIENT_URLS = http://10.0.0.11:2379
ETCD_INITIAL_CLUSTER_STATE = new
ETCD_INITIAL_ADVERTISE_PEER_URLS = http://10.0.0.11:2380
ETCD_INITIAL_CLUSTER = e1=http://10.0.0.11:2380,e2=http://10.0.0.12:2380,e3=http://10.0.0.13:2380
Activez et démarrez le cluster etcd
Une fois les trois fichiers de configuration établis, nous pouvons
démarrer le cluster en exécutant la commande suivante sur tous les
serveurs etcd :
# systemctl start etcd
Afin qu’etcd démarre automatiquement avec le serveur, il est
nécessaire d’activer le service :
# systemctl enable etcd
Une fois les trois fichiers de configuration établis, la commande
suivante doit fonctionner sur n’importe quel nœud :
$ etcdctl -wjson endpoint status | jq
[
{
"Endpoint" : "127.0.0.1:2379" ,
"Status" : {
"header" : {
"cluster_id" : 11628162814576028000 ,
"member_id" : 13786016400334385000 ,
"revision" : 95 ,
"raft_term" : 24
},
"version" : "3.4.23" ,
"dbSize" : 61440 ,
"leader" : 2266733444126347800 ,
"raftIndex" : 112 ,
"raftTerm" : 24 ,
"raftAppliedIndex" : 112 ,
"dbSizeInUse" : 53248
}
}
]
L’état du cluster est visible grace à la commande suivante :
$ etcdctl endpoint status -w table --cluster
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER |[..]| ERRORS |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
| http://10.0.0.11:2379 | 9e80988e833ccb43 | 3.5.13 | 20 kB | false |[..]| |
| http://10.0.0.13:2379 | a10d8f7920cc71c7 | 3.5.13 | 29 kB | true |[..]| |
| http://10.0.0.12:2379 | abdc532bc0516b2d | 3.5.13 | 20 kB | false |[..]| |
+-----------------------+------------------+---------+---------+-----------+[..]+--------+
etcd : manipulation
(optionnel)
Ces exercices utilisent l’API v3 d’etcd.
But : manipuler la base de données distribuée
d’Etcd.
Depuis un nœud, utiliser etcdctl put
pour écrire une clef foo
à la valeur bar
.
$ etcdctl put foo bar
OK
Récupérer cette valeur depuis un autre nœud.
$ etcdctl get foo
foo
bar
Modifier la valeur à baz
.
$ etcdctl put foo baz
OK
Créer un répertoire food
contenant les
clés/valeurs poisson: bar
et vin: blanc
.
$ etcdctl put food/poisson bar
OK
$ etcdctl put food/vin blanc
OK
$ etcdctl get --keys-only --prefix food/
food/poisson
food/vin
Récupérer toutes les clefs du répertoire
food
en exigeant une réponse d’un quorum.
$ etcdctl get --consistency="l" --keys-only --prefix food/
/food/poisson
/food/vin
But : constater le comportement d’Etcd conforme à
l’algorithme Raft.
Tout en observant les logs d’etcd et la santé de
l’agrégat, procéder au fencing du leader avec
virsh suspend <nom machine>
.
Le leader peut être identifié avec la commande suivante :
# etcdctl -wtable --endpoints http://10.0.0.11:2379,http://10.0.0.12:2379,http://10.0.0.13:2379 endpoint status
Dans notre correctif, e1
est actuellement
leader . Dans une fenêtre sur e2
et
e3
, laisser défiler le journal :
# journalctl -fu etcd
Depuis une session dans le serveur hôte, interroger e2
ou e3
continuellement à propos de la santé de l’agrégat
avec par exemple :
$ watch -n1 "curl -s -XGET http://10.0.0.12:2379/health | jq"
Depuis le serveur hôte, cette commande interrompt brutalement la
machine virtuelle e1
(mais sans la détruire) :
# virsh destroy e1
Domain 'e1' destroyed
Il est aussi possible de suspendre la machine avec la commande
suivante :
# virsh suspend e1
Domain 'e1' suspended
La commande inverse est alors virsh resume e1
.
Notez que la machine est suspendue , donc encore présente,
mais plus du tout exécutée par l’hyperviseur. Cette présence inactive
peut parfois créer des situations déstabilisantes pour les autres
machines virtuelles ayant toujours des connexions TCP en suspend. Aussi,
après son réveil, sauf présence de chrony
ou
ntpsec
, l’horloge de cette machine virtuelle nécessite une
intervention manuelle afin de la resynchroniser.
Dans les traces de e3 , nous trouvons :
INFO: 49fc71338f77c1c4 is starting a new election at term 3
INFO: 49fc71338f77c1c4 became candidate at term 4
INFO: 49fc71338f77c1c4 received MsgVoteResp from 49fc71338f77c1c4 at term 4
INFO: 49fc71338f77c1c4 [logterm: 3, index: 9] sent MsgVote request to 97e570ef03022438 at term 4
INFO: 49fc71338f77c1c4 [logterm: 3, index: 9] sent MsgVote request to be3b2f45686f7694 at term 4
INFO: raft.node: 49fc71338f77c1c4 lost leader be3b2f45686f7694 at term 4
L’identifiant 49fc71338f77c1c4
est celui de
e3
lui-même. Ces traces nous indiquent :
le déclenchement de l’élection avec un nouveau mandat n°4 ;
le vote de e3
pour lui-même ;
les demandes de vote vers e1
et e2
.
la perte du leader e1
(identifiant
be3b2f45686f7694
) durant le mandat n°4
Suivent alors les messages suivants:
INFO: 49fc71338f77c1c4 received MsgVoteResp from 97e570ef03022438 at term 4
INFO: 49fc71338f77c1c4 has received 2 MsgVoteResp votes and 0 vote rejections
INFO: 49fc71338f77c1c4 became leader at term 4
INFO: raft.node: 49fc71338f77c1c4 elected leader 49fc71338f77c1c4 at term 4
Seul e2
(ici 97e570ef03022438
) répond au
vote, mais cette réponse suffit à atteindre le quorum (de 2 sur 3) et
valider l’élection de e3
comme nouveau leader .
En interrogeant l’état de e3 , nous confirmons bien
qu’il est leader :
$ etcdctl -wtable endpoint status
+----------------+------------------+---------+---------+-----------+-[…]
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | […]
+----------------+------------------+---------+---------+-----------+-[…]
| 127.0.0.1:2379 | 49fc71338f77c1c4 | 3.4.23 | 20 kB | true | […]
+----------------+------------------+---------+---------+-----------+-[…]
Geler le nouveau leader de la même manière
et voir les traces du nœud restant.
L’état de l’agrégat tombe en erreur suite à cette perte du
quorum :
$ curl -s -XGET http://10.0.0.12:2379/health | jq
Dans les traces de e2
, nous constatons qu’il tente en
boucle une élection, envoie des messages mais faute de réponse,
n’obtient jamais d’accord pour l’élection :
15:36:31 INFO: 97e570ef03022438 is starting a new election at term 4
15:36:31 INFO: 97e570ef03022438 became candidate at term 5
15:36:31 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 5
15:36:31 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 5
15:36:31 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 5
15:36:31 INFO: raft.node: 97e570ef03022438 lost leader 49fc71338f77c1c4 at term 5
15:36:33 INFO: 97e570ef03022438 is starting a new election at term 5
15:36:33 INFO: 97e570ef03022438 became candidate at term 6
15:36:33 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 6
15:36:33 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 6
15:36:33 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 6
15:36:34 INFO: 97e570ef03022438 is starting a new election at term 6
15:36:34 INFO: 97e570ef03022438 became candidate at term 7
15:36:34 INFO: 97e570ef03022438 received MsgVoteResp from 97e570ef03022438 at term 7
15:36:34 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to 49fc71338f77c1c4 at term 7
15:36:34 INFO: 97e570ef03022438 [logterm: 4, index: 1192] sent MsgVote request to be3b2f45686f7694 at term 7
La situation revient à la normale dès qu’un des deux autres revient
en ligne avec par exemple la commande virsh start e3
.