HINWEIS : Noch im Alpha-Stadium. APIs können sich ändern.
(Dieses Repo wird auf https://codeberg.org/flowerinthenight/zgroup gespiegelt).
zgroup ist eine Zig-Bibliothek, die die Clustermitgliedschaft und die Erkennung von Mitgliederfehlern verwalten kann. Es verwendet eine Kombination aus der klatschartigen Informationsverbreitung des SWIM-Protokolls und dem Leader-Wahlalgorithmus von Raft (ohne Protokollverwaltung), um Clusteränderungen zu verfolgen.
Eines der Hauptziele von zgroup besteht darin, Cluster mit Größen, die sich im Laufe der Zeit dynamisch ändern können (z. B. Kubernetes-Bereitstellungen, GCP-Instanzgruppen, AWS-Autoscaling-Gruppen usw.), mit minimalen Abhängigkeiten und Netzwerklast verfolgen zu können. Um dies zu erreichen, sind alle meine bisherigen verwandten Arbeiten auf einen externen Dienst (siehe Spindel, Hecke) angewiesen und verwenden traditionelles Heartbeating. Diese Heartbeating-Technik leidet normalerweise unter der zunehmenden Nutzlastgröße (proportional zur Clustergröße), wenn die Cluster größer werden. Aber ich wollte ein System, das nicht unter dieser Nebenwirkung leidet. Betreten Sie die Verbreitung von Informationen im Infektionsstil von SWIM. Es kann unabhängig von der Clustergröße eine konstante Nutzlastgröße verwenden. SWIM verwendet eine Kombination aus PING
, INDIRECT-PING
und ACK
, um Mitgliederausfälle zu erkennen, während es dieselben Nachrichten huckepack nimmt, um Mitgliedschaftsaktualisierungen zu verbreiten (Klatschprotokoll). Derzeit verwendet zgroup nur das Direct-Probing-Protokoll von SWIM. Es implementiert das Suspicion-Unterprotokoll (noch) nicht vollständig.
Derzeit verwendet zgroup eine einzige 64-Byte-Nutzlast für alle seine Nachrichten, einschließlich der Wahl des Leiters (siehe unten).
Ich wollte auch eine Möglichkeit zur Wahl des Anführers haben, ohne auf einen externen Sperrdienst angewiesen zu sein. Derzeit verwendet zgroup das Unterprotokoll des Raft-Anführerwahlalgorithmus (ohne Protokollverwaltung), um dies zu erreichen. Ich sollte beachten, dass der Algorithmus zur Wahl des Anführers von Raft von einer stabilen Mitgliedschaft abhängt, damit er richtig funktioniert, sodass die Wahl des Anführers von zgroup nur eine Best-Effort-Basis ist; Split-Brain kann immer noch auftreten, während sich die Clustergröße noch ändert. Es werden zusätzliche Codewächter hinzugefügt, um Split-Brain in diesen Szenarien zu minimieren, es wird jedoch nicht vollständig beseitigt. In meinem Anwendungsfall (und meinen Tests) sind allmähliche Änderungen der Clustergröße größtenteils stabil, während plötzliche Änderungen mit großen Größendeltas dies nicht sind. Beispielsweise würde ein großer, plötzlicher Sprung von drei Knoten (der Mindestgröße von zgroup) auf beispielsweise hundert aufgrund der automatischen Skalierung zu einem Split-Brain führen. Sobald jedoch die Zielgröße erreicht ist, wird immer ein einziger Anführer gewählt.
Ein Hinweis zum zufälligen Timeout-Bereich von Raft während der Leader-Wahl: Der Leader von zgroup verfolgt die durchschnittliche Ping-Latenz und versucht, den Timeout-Bereich entsprechend anzupassen, um Änderungen der Clustergröße im Laufe der Zeit Rechnung zu tragen.
Damit ein Knoten einem bestehenden Cluster beitreten kann, benötigt er eine Beitrittsadresse. Während zgroup hierfür eine Funktion join()
bereitstellt, bietet es auch einen Rückrufmechanismus, der Aufrufern eine Join-Adresse bereitstellt. Diese Adresse kann dann in einem externen Speicher gespeichert werden, damit die anderen Knoten sie verwenden können. Intern verwendet zgroup den Knoten mit der höchsten IP(v4)-Adresse in der Gruppe.
Eine Beispielbinärdatei wird bereitgestellt, um eine Möglichkeit zur Verwendung der Bibliothek zu zeigen. Es gibt zwei Möglichkeiten, das Beispiel auszuführen:
# 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...
Sofern konfiguriert, verwendet die Beispielbinärdatei einen kostenlosen Dienst, https://keyvalue.immanuel.co/, als Speicher für die Join-Adresse.
# 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...
Es wird eine Beispiel-Kubernetes-Bereitstellungsdatei bereitgestellt, um zgroup bei Kubernetes-Bereitstellungen auszuprobieren. Stellen Sie jedoch vor der Bereitstellung sicher, dass Sie die Umgebungsvariable ZGROUP_JOIN_PREFIX
aktualisieren, etwa so:
# 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.
Es wird ein Beispiel-Startskript bereitgestellt, um zgroup auf einer GCP MIG auszuprobieren. Stellen Sie jedoch vor der Bereitstellung sicher, dass Sie den ZGROUP_JOIN_PREFIX
Wert im Skript aktualisieren, etwa so:
# 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
Es wird ein Beispiel-Startskript bereitgestellt, um zgroup auf einer AWS ASG auszuprobieren. Stellen Sie jedoch vor der Bereitstellung sicher, dass Sie den ZGROUP_JOIN_PREFIX
Wert im Skript aktualisieren, etwa so:
# 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
Um die aktuellen Mitglieder der Gruppe zu ermitteln, können Sie Folgendes versuchen:
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 });
}
Der schwierige Teil bei der Verwendung von zgroup besteht darin, die Zeitüberschreitungen zu konfigurieren, um die Statusverteilung und -konvergenz zu optimieren. Die aktuelle Implementierung wurde nur innerhalb eines lokalen Netzwerks getestet.
PRs sind willkommen.