"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Como adicionar a eleição de líder com tecnologia Kubernetes aos seus aplicativos Go

Como adicionar a eleição de líder com tecnologia Kubernetes aos seus aplicativos Go

Publicado em 30/07/2024
Navegar:641

How to add Kubernetes-powered leader election to your Go apps

Originalmente publicado em por blog

A biblioteca padrão do Kubernetes está cheia de joias, escondidas em muitos dos vários subpacotes que fazem parte do ecossistema. Um exemplo que descobri recentemente é k8s.io/client-go/tools/leaderelection, que pode ser usado para adicionar um protocolo de eleição de líder a qualquer aplicativo em execução dentro de um cluster Kubernetes. Este artigo discutirá o que é a eleição de líder, como ela é implementada neste pacote Kubernetes e fornecerá um exemplo de como podemos usar esta biblioteca em nossos próprios aplicativos.

Eleição de Líder

A eleição de líder é um conceito de sistemas distribuídos que é um alicerce central de software altamente disponível. Ele permite que vários processos simultâneos se coordenem entre si e elejam um único processo "líder", que é então responsável por executar ações síncronas, como gravar em um armazenamento de dados.

Isso é útil em sistemas como bancos de dados distribuídos ou caches, onde vários processos estão em execução para criar redundância contra falhas de hardware ou rede, mas não podem gravar no armazenamento simultaneamente para garantir a consistência dos dados. Se o processo do líder deixar de responder em algum momento no futuro, os processos restantes darão início a uma eleição de novo líder, eventualmente escolhendo um novo processo para atuar como líder.

Usando esse conceito, podemos criar software altamente disponível com um único líder e múltiplas réplicas em espera.

No Kubernetes, o pacote controller-runtime usa a eleição de líder para tornar os controladores altamente disponíveis. Em uma implantação de controlador, a reconciliação de recursos ocorre apenas quando um processo é o líder e outras réplicas estão aguardando em espera. Se o pod líder não responder, as réplicas restantes elegerão um novo líder para realizar reconciliações subsequentes e retomar a operação normal.

Locações do Kubernetes

Esta biblioteca usa um Kubernetes Lease, ou bloqueio distribuído, que pode ser obtido por um processo. Locações são recursos nativos do Kubernetes mantidos por uma única identidade, por um determinado período, com opção de renovação. Aqui está um exemplo de especificação dos documentos:

apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  labels:
    apiserver.kubernetes.io/identity: kube-apiserver
    kubernetes.io/hostname: master-1
  name: apiserver-07a5ea9b9b072c4a5f3d1c3702
  namespace: kube-system
spec:
  holderIdentity: apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05
  leaseDurationSeconds: 3600
  renewTime: "2023-07-04T21:58:48.065888Z"

Os arrendamentos são usados ​​pelo ecossistema k8s de três maneiras:

  1. Node Heartbeats: Cada Node tem um recurso Lease correspondente e atualiza seu campo promoteTime continuamente. Se o restartTime de um Lease não for atualizado há algum tempo, o Node será contaminado como não disponível e nenhum outro Pod será agendado para ele.
  2. Eleição do líder: Neste caso, um arrendamento é usado para coordenar vários processos, fazendo com que um líder atualize a titularidade do arrendamento. Réplicas em espera, com identidades diferentes, ficam paradas aguardando a expiração do Lease. Se o arrendamento expirar e não for renovado pelo líder, ocorre uma nova eleição na qual as réplicas restantes tentam apropriar-se do arrendamento atualizando sua titularIdentidade com a sua própria. Como o servidor da API Kubernetes não permite atualizações para objetos obsoletos, apenas um único nó em espera será capaz de atualizar o Lease, ponto em que continuará a execução como o novo líder.
  3. Identidade do servidor API: a partir da versão 1.26, como um recurso beta, cada réplica do kube-apiserver publicará sua identidade criando um Lease dedicado. Como esse é um recurso novo e relativamente pequeno, não há muito mais que possa ser derivado do objeto Lease além de quantos servidores API estão em execução. Mas isso deixa espaço para adicionar mais metadados a esses arrendamentos em futuras versões do k8s.

Agora vamos explorar este segundo caso de uso de arrendamentos escrevendo um programa de exemplo para demonstrar como você pode usá-los em cenários de eleição de líder.

Exemplo de programa

Neste exemplo de código, estamos usando o pacote leaderelection para lidar com as especificidades da eleição do líder e da manipulação do arrendamento.

package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "k8s.io/client-go/tools/leaderelection"
    rl "k8s.io/client-go/tools/leaderelection/resourcelock"
    ctrl "sigs.k8s.io/controller-runtime"
)

var (
    // lockName and lockNamespace need to be shared across all running instances
    lockName      = "my-lock"
    lockNamespace = "default"

    // identity is unique to the individual process. This will not work for anything,
    // outside of a toy example, since processes running in different containers or
    // computers can share the same pid.
    identity      = fmt.Sprintf("%d", os.Getpid())
)

func main() {
    // Get the active kubernetes context
    cfg, err := ctrl.GetConfig()
    if err != nil {
        panic(err.Error())
    }

    // Create a new lock. This will be used to create a Lease resource in the cluster.
    l, err := rl.NewFromKubeconfig(
        rl.LeasesResourceLock,
        lockNamespace,
        lockName,
        rl.ResourceLockConfig{
            Identity: identity,
        },
        cfg,
        time.Second*10,
    )
    if err != nil {
        panic(err)
    }

    // Create a new leader election configuration with a 15 second lease duration.
    // Visit https://pkg.go.dev/k8s.io/client-go/tools/leaderelection#LeaderElectionConfig
    // for more information on the LeaderElectionConfig struct fields
    el, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
        Lock:          l,
        LeaseDuration: time.Second * 15,
        RenewDeadline: time.Second * 10,
        RetryPeriod:   time.Second * 2,
        Name:          lockName,
        Callbacks: leaderelection.LeaderCallbacks{
            OnStartedLeading: func(ctx context.Context) { println("I am the leader!") },
            OnStoppedLeading: func() { println("I am not the leader anymore!") },
            OnNewLeader:      func(identity string) { fmt.Printf("the leader is %s\n", identity) },
        },
    })
    if err != nil {
        panic(err)
    }

    // Begin the leader election process. This will block.
    el.Run(context.Background())

}

O que há de bom no pacote leaderelection é que ele fornece uma estrutura baseada em retorno de chamada para lidar com eleições de líderes. Dessa forma, você pode agir de maneira granular em mudanças de estado específicas e liberar recursos adequadamente quando um novo líder for eleito. Ao executar esses retornos de chamada em goroutines separadas, o pacote aproveita o forte suporte de simultaneidade do Go para utilizar com eficiência os recursos da máquina.

Testando

Para testar isso, vamos criar um cluster de teste usando kind.

$ kind create cluster

Copie o código de exemplo em main.go, crie um novo módulo (go mod init leaderelectiontest) e arrume-o (go mod tidy) para instalar suas dependências. Depois de executar go run main.go, você deverá ver uma saída como esta:

$ go run main.go
I0716 11:43:50.337947     138 leaderelection.go:250] attempting to acquire leader lease default/my-lock...
I0716 11:43:50.351264     138 leaderelection.go:260] successfully acquired lease default/my-lock
the leader is 138
I am the leader!

A identidade exata do líder será diferente da que está no exemplo (138), pois este é apenas o PID do processo que estava sendo executado no meu computador no momento da escrita.

E aqui está o Lease que foi criado no cluster de teste:

$ kubectl describe lease/my-lock
Name:         my-lock
Namespace:    default
Labels:       
Annotations:  
API Version:  coordination.k8s.io/v1
Kind:         Lease
Metadata:
  Creation Timestamp:  2024-07-16T15:43:50Z
  Resource Version:    613
  UID:                 1d978362-69c5-43e9-af13-7b319dd452a6
Spec:
  Acquire Time:            2024-07-16T15:43:50.338049Z
  Holder Identity:         138
  Lease Duration Seconds:  15
  Lease Transitions:       0
  Renew Time:              2024-07-16T15:45:31.122956Z
Events:                    

Veja que a "Identidade do Titular" é igual ao PID do processo, 138.

Agora, vamos abrir outro terminal e executar o mesmo arquivo main.go em um processo separado:

$ go run main.go
I0716 11:48:34.489953     604 leaderelection.go:250] attempting to acquire leader lease default/my-lock...
the leader is 138

Este segundo processo irá esperar para sempre, até que o primeiro não responda. Vamos encerrar o primeiro processo e esperar cerca de 15 segundos. Agora que o primeiro processo não está renovando sua reivindicação no Lease, o campo .spec.renewTime não será mais atualizado. Isto acabará por fazer com que o segundo processo desencadeie uma eleição de novo líder, uma vez que o tempo de renovação do arrendamento é mais antigo que a sua duração. Como este processo é o único em execução, ele se elegerá como o novo líder.

the leader is 604
I0716 11:48:51.904732     604 leaderelection.go:260] successfully acquired lease default/my-lock
I am the leader!

Se houvesse vários processos ainda em execução após a saída do líder inicial, o primeiro processo a adquirir o arrendamento seria o novo líder e o restante continuaria em espera.

Sem garantias de líder único

Este pacote não é infalível, pois "não garante que apenas um cliente atue como líder (também conhecido como esgrima)". Por exemplo, se um líder estiver pausado e deixar seu Lease expirar, outra réplica em espera adquirirá o Lease. Então, quando o líder original retomar a execução, ele pensará que ainda é o líder e continuará trabalhando ao lado do líder recém-eleito. Dessa forma, você pode acabar com dois líderes concorrendo simultaneamente.

Para corrigir isso, um token de isolamento que faça referência ao Lease precisa ser incluído em cada solicitação ao servidor. Um token de esgrima é efetivamente um número inteiro que aumenta em 1 cada vez que um arrendamento muda de mãos. Portanto, um cliente com um token de esgrima antigo terá suas solicitações rejeitadas pelo servidor. Neste cenário, se um líder antigo acordar do sono e um novo líder já tiver incrementado o token de esgrima, todas as solicitações do antigo líder seriam rejeitadas porque ele está enviando um token mais antigo (menor) do que o que o servidor viu no líder mais novo.

Implementar o isolamento no Kubernetes seria difícil sem modificar o servidor API principal para contabilizar os tokens de isolamento correspondentes para cada arrendamento. No entanto, o risco de ter vários controladores líderes é um tanto mitigado pelo próprio servidor API k8s. Como as atualizações em objetos obsoletos são rejeitadas, somente os controladores com a versão mais atualizada de um objeto podem modificá-lo. Portanto, embora pudéssemos ter vários controladores líderes em execução, o estado de um recurso nunca regrediria para versões mais antigas se um controlador perdesse uma alteração feita por outro líder. Em vez disso, o tempo de reconciliação aumentaria, uma vez que ambos os líderes precisariam de actualizar os seus próprios estados internos de recursos para garantir que estão a agir de acordo com as versões mais recentes.

Ainda assim, se você estiver usando este pacote para implementar a eleição de líderes usando um armazenamento de dados diferente, esta é uma advertência importante a ser observada.

Conclusão

A eleição do líder e o bloqueio distribuído são blocos de construção críticos de sistemas distribuídos. Ao tentar construir aplicativos tolerantes a falhas e altamente disponíveis, é fundamental ter ferramentas como essas à sua disposição. A biblioteca padrão do Kubernetes nos fornece um wrapper testado em batalha em torno de seus primitivos para permitir que os desenvolvedores de aplicativos criem facilmente a eleição de líderes em seus próprios aplicativos.

Embora o uso desta biblioteca específica limite você à implantação de seu aplicativo no Kubernetes, parece ser assim que o mundo está indo recentemente. Se de fato isso for um obstáculo, é claro que você pode bifurcar a biblioteca e modificá-la para funcionar em qualquer armazenamento de dados altamente disponível e compatível com ACID.

Fique ligado para mais detalhes sobre fontes k8s!

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/sklarsa/how-to-add-kubernetes-powered-leader-election-to-your-go-apps-57jh?1 Se houver alguma violação, entre em contato com study_golang @163.com excluir
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3