NOTA : Ainda em fase alfa. As APIs podem mudar.
(Este repositório é espelhado em https://codeberg.org/flowerinthenight/zgroup).
zgroup é uma biblioteca Zig que pode gerenciar membros de cluster e detecção de falhas de membros. Ele usa uma combinação de disseminação de informações no estilo fofoca do Protocolo SWIM e o algoritmo de eleição de líder do Raft (sem o gerenciamento de log) para rastrear mudanças no cluster.
Um dos principais objetivos do zgroup é ser capaz de rastrear clusters com tamanhos que podem mudar dinamicamente ao longo do tempo (por exemplo, implantações do Kubernetes, grupos de instâncias do GCP, grupos de escalonamento automático da AWS, etc.) com dependências e carga de rede mínimas. Todos os meus trabalhos anteriores relacionados até agora dependem de algum serviço externo (ver fuso, hedge), usando pulsação tradicional, para conseguir isso. Essa técnica de pulsação geralmente sofre com o aumento do tamanho da carga útil (proporcional ao tamanho do cluster) à medida que os clusters ficam maiores. Mas eu queria um sistema que não sofresse desse efeito colateral. Entre na disseminação de informações sobre estilo de infecção do SWIM. Ele pode usar um tamanho de carga constante, independentemente do tamanho do cluster. SWIM usa uma combinação de PING
s, INDIRECT-PING
s e ACK
s para detectar falhas de membros enquanto aproveita essas mesmas mensagens para propagar atualizações de membros (protocolo de fofoca). Atualmente, o zgroup usa apenas o protocolo de investigação direta do SWIM; ele não implementa totalmente o subprotocolo Suspeita (ainda).
No momento, o zgroup usa uma única carga útil de 64 bytes para todas as suas mensagens, incluindo a eleição do líder (veja abaixo).
Eu também queria algum tipo de capacidade de eleição de líder sem depender de um serviço de bloqueio externo. No momento, o zgroup usa o subprotocolo do algoritmo de eleição de líder do Raft (sem o gerenciamento de log) para conseguir isso. Devo observar que o algoritmo de eleição do líder do Raft depende de membros estáveis para funcionar corretamente, portanto a eleição do líder do zgroup é apenas uma base de melhor esforço; o cérebro dividido ainda pode acontecer enquanto o tamanho do cluster ainda está mudando. Protetores de código adicionais são adicionados para minimizar a divisão do cérebro nesses cenários, mas não são completamente eliminados. No meu caso de uso (e testes), as mudanças graduais no tamanho do cluster são em sua maioria estáveis, enquanto as mudanças repentinas com deltas de tamanho enorme não são. Por exemplo, um salto grande e repentino de três nós (tamanho mínimo do zgroup) para, digamos, cem, devido ao escalonamento automático, causaria uma divisão do cérebro. Uma vez alcançado o tamanho da meta, no entanto, um único líder será sempre eleito.
Uma observação sobre o intervalo de tempo limite aleatório do Raft durante a eleição do líder: o líder do zgroup rastreia as médias de latência do ping e tenta ajustar o intervalo de tempo limite de acordo para acomodar alterações no tamanho do cluster ao longo do tempo.
Para que um nó ingresse em um cluster existente, ele precisa de um endereço de adesão. Embora zgroup exponha uma função join()
para isso, ele também fornece um mecanismo de retorno de chamada, fornecendo aos chamadores um endereço de junção. Esse endereço pode então ser armazenado em um armazenamento externo para uso de outros nós. Internamente, o zgroup usa o nó com o endereço IP(v4) mais alto do grupo.
Um exemplo de binário é fornecido para mostrar uma maneira de usar a biblioteca. Existem duas maneiras de executar a amostra:
# 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...
Se configurado, o binário de amostra usa um serviço gratuito, https://keyvalue.immanuel.co/, como armazenamento para o endereço de junção.
# 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...
Um arquivo de implantação do Kubernetes de amostra é fornecido para testar o zgroup nas implantações do Kubernetes. Antes de implantar, certifique-se de atualizar a variável de ambiente ZGROUP_JOIN_PREFIX
, assim:
# 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.
Um exemplo de script de inicialização é fornecido para testar o zgroup em um MIG do GCP. Antes de implantar, certifique-se de atualizar o valor ZGROUP_JOIN_PREFIX
no script, assim:
# 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
Um exemplo de script de inicialização é fornecido para testar o zgroup em um AWS ASG. Antes de implantar, certifique-se de atualizar o valor ZGROUP_JOIN_PREFIX
no script, assim:
# 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 obter os membros atuais do grupo, você pode tentar 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 });
}
A parte complicada de usar o zgroup é configurar os tempos limite para otimizar a disseminação e convergência do estado. A implementação atual foi testada apenas em uma rede local.
PRs são bem-vindos.