NOTA : Aún en etapa alfa. Las API pueden cambiar.
(Este repositorio se refleja en https://codeberg.org/flowerinthenight/zgroup).
zgroup es una biblioteca Zig que puede administrar la membresía del clúster y la detección de fallas de los miembros. Utiliza una combinación de la difusión de información estilo chisme del Protocolo SWIM y el algoritmo de elección de líder de Raft (menos la gestión de registros) para rastrear los cambios del grupo.
Uno de los principales objetivos de zgroup es poder realizar un seguimiento de clústeres con tamaños que pueden cambiar dinámicamente con el tiempo (por ejemplo, implementaciones de Kubernetes, grupos de instancias de GCP, grupos de Autoscaling de AWS, etc.) con dependencias y carga de red mínimas. Todos mis trabajos anteriores relacionados hasta ahora dependen de algún servicio externo (ver huso, cobertura), utilizando latidos tradicionales, para lograr esto. Esta técnica de latidos generalmente sufre el aumento del tamaño de la carga útil (proporcional al tamaño de los clústeres) a medida que los clústeres crecen. Pero quería un sistema que no sufriera ese efecto secundario. Ingrese a la difusión de información al estilo de una infección de SWIM. Puede utilizar un tamaño de carga útil constante independientemente del tamaño del clúster. SWIM utiliza una combinación de PING
, INDIRECT-PING
y ACK
para detectar fallas de los miembros mientras aprovecha estos mismos mensajes para propagar actualizaciones de miembros (protocolo de chismes). Actualmente, zgroup sólo utiliza el protocolo de sondeo directo de SWIM; no implementa completamente el subprotocolo Sospecha (todavía).
Por el momento, zgroup utiliza una única carga útil de 64 bytes para todos sus mensajes, incluida la elección del líder (ver más abajo).
También quería algún tipo de capacidad de elección de líder sin depender de un servicio de bloqueo externo. Por el momento, zgroup utiliza el subprotocolo del algoritmo de elección de líder de Raft (sin la gestión de registros) para lograrlo. Debo señalar que el algoritmo de elección de líder de Raft depende de una membresía estable para que funcione correctamente, por lo que la elección de líder de zgroup es solo una base de mejor esfuerzo; El cerebro dividido aún puede ocurrir mientras el tamaño del clúster aún está cambiando. Se agregan protecciones de código adicionales para minimizar la división del cerebro en estos escenarios, pero no se eliminan por completo. En mi caso de uso (y pruebas), los cambios graduales en el tamaño de los grupos son en su mayoría estables, mientras que los cambios repentinos con deltas de tamaño enormes no lo son. Por ejemplo, un salto grande y repentino de tres nodos (el tamaño mínimo de zgroup) a, digamos, cien, debido al escalado automático, provocaría una división del cerebro. Sin embargo, una vez que se alcance el tamaño objetivo, siempre se elegirá un único líder.
Una nota sobre el rango de tiempo de espera aleatorio de Raft durante la elección del líder: el líder de zgroup rastrea los promedios de latencia de ping e intenta ajustar el rango de tiempo de espera en consecuencia para adaptarse a los cambios de tamaño del clúster con el tiempo.
Para que un nodo se una a un clúster existente, necesita una dirección de unión. Si bien zgroup expone una función join()
para esto, también proporciona un mecanismo de devolución de llamada, proporcionando a las personas que llaman una dirección de unión. Esta dirección luego se puede almacenar en un almacén externo para que la utilicen otros nodos. Internamente, zgroup utiliza el nodo con la dirección IP (v4) más alta del grupo.
Se proporciona un binario de muestra para mostrar una forma de utilizar la biblioteca. Hay dos formas de ejecutar el ejemplo:
# 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...
Si está configurado, el binario de muestra utiliza un servicio gratuito, https://keyvalue.immanuel.co/, como almacén para la dirección de unión.
# 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...
Se proporciona un archivo de implementación de Kubernetes de muestra para probar zgroup en implementaciones de Kubernetes. Sin embargo, antes de implementar, asegúrese de actualizar la variable de entorno ZGROUP_JOIN_PREFIX
, así:
# 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.
Se proporciona un script de inicio de muestra para probar zgroup en un MIG de GCP. Sin embargo, antes de implementar, asegúrese de actualizar el valor ZGROUP_JOIN_PREFIX
en el script, así:
# 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
Se proporciona un script de inicio de muestra para probar zgroup en un AWS ASG. Sin embargo, antes de implementar, asegúrese de actualizar el valor ZGROUP_JOIN_PREFIX
en el script, así:
# 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
Para obtener los miembros actuales del grupo, puedes probar algo como:
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 parte complicada de usar zgroup es configurar los tiempos de espera para optimizar la difusión y la convergencia del estado. La implementación actual sólo se probó dentro de una red local.
Las relaciones públicas son bienvenidas.