REMARQUE : Toujours en phase alpha. Les API peuvent changer.
(Ce dépôt est reflété sur https://codeberg.org/flowerinthenight/zgroup).
zgroup est une bibliothèque Zig qui peut gérer l'adhésion au cluster et la détection des échecs des membres. Il utilise une combinaison de diffusion d'informations de type potins du protocole SWIM et de l'algorithme d'élection des dirigeants de Raft (sans la gestion des journaux) pour suivre les changements de cluster.
L'un des principaux objectifs de zgroup est de pouvoir suivre des clusters dont la taille peut changer dynamiquement au fil du temps (par exemple, les déploiements Kubernetes, les groupes d'instances GCP, les groupes AWS Autoscaling, etc.) avec un minimum de dépendances et de charge réseau. Tous mes travaux précédents jusqu'à présent dépendent d'un service externe (voir fuseau, haie), utilisant le battement de cœur traditionnel, pour y parvenir. Cette technique de battement de cœur souffre généralement de l'augmentation de la taille de la charge utile (proportionnelle à la taille des clusters) à mesure que les clusters grossissent. Mais je voulais un système qui ne souffre pas de cet effet secondaire. Entrez dans la diffusion d’informations de style infection de SWIM. Il peut utiliser une taille de charge utile constante quelle que soit la taille du cluster. SWIM utilise une combinaison de PING
, INDIRECT-PING
et ACK
pour détecter les échecs des membres tout en s'appuyant sur ces mêmes messages pour propager les mises à jour des membres (protocole gossip). Actuellement, zgroup utilise uniquement le protocole de sondage direct de SWIM ; il n'implémente pas (encore) pleinement le sous-protocole Suspicion.
Pour le moment, zgroup utilise une seule charge utile de 64 octets pour tous ses messages, y compris l'élection du leader (voir ci-dessous).
Je voulais également une sorte de capacité d'élection de leader sans dépendre d'un service de verrouillage externe. Pour le moment, zgroup utilise le sous-protocole de l'algorithme d'élection des leaders de Raft (sans la gestion des journaux) pour y parvenir. Je dois noter que l'algorithme d'élection du leader de Raft dépend d'une adhésion stable pour qu'il fonctionne correctement, donc l'élection du leader de zgroup n'est qu'une base d'efforts ; un split-brain peut encore se produire alors que la taille du cluster continue de changer. Des gardes de code supplémentaires sont ajoutés pour minimiser le fractionnement du cerveau dans ces scénarios, mais ils ne sont pas complètement éliminés. Dans mon cas d'utilisation (et mes tests), les changements progressifs de taille de cluster sont pour la plupart stables, alors que les changements soudains avec des deltas de taille énorme ne le sont pas. Par exemple, un saut important et soudain de trois nœuds (taille minimale du zgroup) à, disons, une centaine, en raison de la mise à l'échelle automatique, provoquerait une division du cerveau. Toutefois, une fois la taille cible atteinte, un seul dirigeant sera toujours élu.
Une note sur la plage de délai d'attente aléatoire de Raft lors de l'élection du leader : le leader de zgroup suit les moyennes de latence de ping et tente d'ajuster la plage de délai d'attente en conséquence pour s'adapter aux changements de taille du cluster au fil du temps.
Pour qu'un nœud rejoigne un cluster existant, il a besoin d'une adresse de jonction. Bien que zgroup expose une fonction join()
pour cela, il fournit également un mécanisme de rappel, fournissant aux appelants une adresse de jointure. Cette adresse peut ensuite être stockée dans un magasin externe pour que les autres nœuds puissent l'utiliser. En interne, zgroup utilise le nœud avec l'adresse IP(v4) la plus élevée du groupe.
Un exemple de binaire est fourni pour montrer une manière d'utiliser la bibliothèque. Il existe deux manières d'exécuter l'exemple :
# Build the sample binary:
$ zig build --summary all
# Run the 1st process. The expected args look like:
#
# ./zgroup groupname member_ip:port [join_ip:port]
#
# Run the first process (join to self).
$ ./zig-out/bin/zgroup group1 0.0.0.0:8080 0.0.0.0:8080
# Then you can run additional instances.
# Join through the 1st process/node (different terminal):
$ ./zig-out/bin/zgroup group1 0.0.0.0:8081 0.0.0.0:8080
# Join through the 2nd process/node (different terminal):
$ ./zig-out/bin/zgroup group1 0.0.0.0:8082 0.0.0.0:8081
# Join through the 1st process/node (different terminal):
$ ./zig-out/bin/zgroup group1 0.0.0.0:8083 0.0.0.0:8080
# and so on...
S'il est configuré, l'exemple binaire utilise un service gratuit, https://keyvalue.immanuel.co/, comme magasin pour l'adresse de jointure.
# Build the sample binary:
$ zig build --summary all
# Generate UUID:
$ uuidgen
{output}
# Run the 1st process. The expected args look like:
#
# ./zgroup groupname member_ip:port
#
# Run the first process:
$ ZGROUP_JOIN_PREFIX={output} ./zig-out/bin/zgroup group1 0.0.0.0:8080
# Add a second node (different terminal):
$ ZGROUP_JOIN_PREFIX={output} ./zig-out/bin/zgroup group1 0.0.0.0:8081
# Add a third node (different terminal):
$ ZGROUP_JOIN_PREFIX={output} ./zig-out/bin/zgroup group1 0.0.0.0:8082
# Add a fourth node (different terminal):
$ ZGROUP_JOIN_PREFIX={output} ./zig-out/bin/zgroup group1 0.0.0.0:8083
# and so on...
Un exemple de fichier de déploiement Kubernetes est fourni pour essayer zgroup sur les déploiements Kubernetes. Avant le déploiement, assurez-vous de mettre à jour la variable d'environnement ZGROUP_JOIN_PREFIX
, comme ceci :
# Generate UUID:
$ uuidgen
{output}
# Update the 'value' part with your output.
...
- name: ZGROUP_JOIN_PREFIX
value: " {output} "
...
# Deploy to Kubernetes:
$ kubectl create -f k8s.yaml
# You will notice some initial errors in the logs.
# Wait for a while before the K/V store is updated.
Un exemple de script de démarrage est fourni pour essayer zgroup sur un MIG GCP. Avant le déploiement, assurez-vous de mettre à jour la valeur ZGROUP_JOIN_PREFIX
dans le script, comme ceci :
# Generate UUID:
$ uuidgen
{output}
# Update the 'value' part of ZGROUP_JOIN_PREFIX with your output.
...
ZGROUP_JOIN_PREFIX={output} ./zgroup group1 ...
# Create an instance template:
$ gcloud compute instance-templates create zgroup-tmpl
--machine-type e2-micro
--metadata=startup-script= ' ' " $( cat startup-gcp-mig.sh ) " ' '
# Create a regional MIG:
$ gcloud compute instance-groups managed create rmig
--template zgroup-tmpl --size 3 --region {your-region}
# You can view the logs through:
$ tail -f /var/log/messages
Un exemple de script de démarrage est fourni pour essayer zgroup sur un AWS ASG. Avant le déploiement, assurez-vous de mettre à jour la valeur ZGROUP_JOIN_PREFIX
dans le script, comme ceci :
# Generate UUID:
$ uuidgen
{output}
# Update the 'value' part of ZGROUP_JOIN_PREFIX with your output.
...
ZGROUP_JOIN_PREFIX={output} ./zgroup group1 ...
# Create a launch template. ImageId here is Amazon Linux, default VPC.
# (Added newlines for readability. Might not run when copied as is.)
$ aws ec2 create-launch-template
--launch-template-name zgroup-lt
--version-description version1
--launch-template-data '
{
"UserData":" ' " $( cat startup-aws-asg.sh | base64 -w 0 ) " ' ",
"ImageId":"ami-0f75d1a8c9141bd00",
"InstanceType":"t2.micro"
} '
# Create the ASG:
$ aws autoscaling create-auto-scaling-group
--auto-scaling-group-name zgroup-asg
--launch-template LaunchTemplateName=zgroup-lt,Version= ' 1 '
--min-size 3
--max-size 3
--availability-zones {target-zone}
# You can view the logs through:
$ [sudo] journalctl -f
Pour obtenir les membres actuels du groupe, vous pouvez essayer quelque chose comme :
const members = try fleet . getMembers ( gpa . allocator ());
defer members . deinit ();
for ( members . items , 0 .. ) | v , i | {
defer gpa . allocator (). free ( v );
log . info ( "member[{d}]: {s}" , .{ i , v });
}
La partie délicate de l'utilisation de zgroup consiste à configurer les délais d'attente pour optimiser la diffusion et la convergence des états. L'implémentation actuelle n'a été testée qu'au sein d'un réseau local.
Les relations publiques sont les bienvenues.