«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Как добавить выборы лидеров на базе Kubernetes в ваши приложения Go

Как добавить выборы лидеров на базе Kubernetes в ваши приложения Go

Опубликовано 30 июля 2024 г.
Просматривать:263

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

Первоначально опубликовано в блоге

Стандартная библиотека Kubernetes полна драгоценностей, спрятанных во многих подпакетах, которые являются частью экосистемы. Один из таких примеров я недавно обнаружил: k8s.io/client-go/tools/leaderelection, который можно использовать для добавления протокола выборов лидера в любое приложение, работающее внутри кластера Kubernetes. В этой статье мы обсудим, что такое выборы лидера, как они реализованы в этом пакете Kubernetes, а также приведем пример того, как мы можем использовать эту библиотеку в наших собственных приложениях.

Выборы лидера

Выборы лидеров — это концепция распределенных систем, которая является основным строительным блоком высокодоступного программного обеспечения. Это позволяет нескольким параллельным процессам координировать действия друг с другом и выбирать один «лидерный» процесс, который затем отвечает за выполнение синхронных действий, таких как запись в хранилище данных.

Это полезно в таких системах, как распределенные базы данных или кэши, где выполняется несколько процессов для создания избыточности на случай сбоев оборудования или сети, но не может одновременно записывать данные в хранилище, чтобы обеспечить согласованность данных. Если в какой-то момент в будущем процесс-лидер перестанет отвечать на запросы, оставшиеся процессы начнут выборы нового лидера, в конечном итоге выбрав новый процесс в качестве лидера.

Используя эту концепцию, мы можем создать высокодоступное программное обеспечение с одним ведущим и несколькими резервными репликами.

В Kubernetes пакет среды выполнения контроллера использует выбор лидера, чтобы обеспечить высокую доступность контроллеров. При развертывании контроллера согласование ресурсов происходит только тогда, когда процесс является ведущим, а другие реплики находятся в режиме ожидания. Если модуль-лидер перестанет отвечать на запросы, оставшиеся реплики выберут нового лидера для выполнения последующих сверок и возобновления нормальной работы.

Аренда Kubernetes

Эта библиотека использует аренду Kubernetes или распределенную блокировку, которую можно получить в процессе. Аренда — это собственные ресурсы Kubernetes, которые принадлежат одному удостоверению на определенный срок с возможностью продления. Вот пример спецификации из документации:

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"

Аренда используется в экосистеме k8s тремя способами:

  1. Node Heartbeats: каждый узел имеет соответствующий ресурс Lease и постоянно обновляет свое поле renewTime. Если время продления аренды не было обновлено в течение некоторого времени, узел будет помечен как недоступный, и для него больше не будет запланировано никаких подов.
  2. Выбор лидера: в этом случае аренда используется для координации между несколькими процессами путем обновления лидером идентификатора держателя аренды. Резервные реплики с разными идентификаторами зависают в ожидании истечения срока аренды. Если срок аренды истекает и лидер не продлевает ее, происходят новые выборы, в ходе которых оставшиеся реплики пытаются завладеть арендой, обновляя ее идентификатор владельца на свою собственную. Поскольку сервер Kubernetes API запрещает обновления устаревших объектов, только один резервный узел сможет успешно обновить аренду, после чего он продолжит выполнение в качестве нового лидера.
  3. Идентификатор API-сервера: начиная с версии 1.26, в качестве бета-функции, каждая реплика kube-apiserver будет публиковать свою идентификацию путем создания выделенной аренды. Поскольку это относительно небольшая новая функция, из объекта Lease больше ничего нельзя получить, кроме количества работающих серверов API. Но это оставляет возможность добавить больше метаданных в эти аренды в будущих версиях k8s.

Теперь давайте рассмотрим второй вариант использования аренды, написав пример программы, чтобы продемонстрировать, как вы можете использовать их в сценариях выборов лидера.

Пример программы

В этом примере кода мы используем пакет Leaderelection для обработки выборов лидера и особенностей манипуляций с арендой.

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())

}

Что хорошо в пакете Leaderelection, так это то, что он предоставляет структуру на основе обратных вызовов для обработки выборов лидера. Таким образом, вы можете детально действовать в отношении конкретных изменений состояния и правильно высвобождать ресурсы при избрании нового лидера. Запуская эти обратные вызовы в отдельных горутинах, пакет использует преимущества мощной поддержки параллелизма Go для эффективного использования машинных ресурсов.

Тестирование

Чтобы проверить это, давайте создадим тестовый кластер, используя kind.

$ kind create cluster

Скопируйте пример кода в main.go, создайте новый модуль (go mod init Leaderelectiontest) и приведите его в порядок (go mod tidy), чтобы установить его зависимости. После запуска go run main.go вы должны увидеть такой вывод:

$ 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!

Точная личность лидера будет отличаться от приведенной в примере (138), поскольку это всего лишь PID процесса, который выполнялся на моем компьютере на момент написания статьи.

А вот договор аренды, созданный в тестовом кластере:

$ 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:                    

Убедитесь, что «Идентификация держателя» совпадает с PID процесса, 138.

Теперь давайте откроем другой терминал и запустим тот же файл main.go в отдельном процессе:

$ 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

Этот второй процесс будет ждать вечно, пока первый не перестанет отвечать. Давайте убьем первый процесс и подождем около 15 секунд. Теперь, когда первый процесс не продлевает свою заявку на аренду, поле .spec.renewTime больше не будет обновляться. В конечном итоге это приведет к тому, что второй процесс инициирует выборы нового лидера, поскольку время продления договора аренды превышает его продолжительность. Поскольку этот процесс сейчас работает единственный, он изберет себя новым лидером.

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

Если бы после выхода первоначального лидера продолжало работать несколько процессов, первым процессом, получившим аренду, был бы новый лидер, а остальные продолжали бы оставаться в режиме ожидания.

Никаких гарантий единого лидера

Этот пакет не является надежным, поскольку он «не гарантирует, что только один клиент выступает в качестве лидера (так называемого фехтования)». Например, если лидер приостановлен и истекает срок его аренды, другая резервная реплика получит аренду. Затем, как только первоначальный лидер возобновит исполнение, он будет думать, что он все еще лидер, и продолжит работать вместе с вновь избранным лидером. Таким образом, вы можете получить двух лидеров, работающих одновременно.

Чтобы это исправить, в каждый запрос к серверу необходимо включать токен ограждения, ссылающийся на аренду. Токен фехтования фактически представляет собой целое число, которое увеличивается на 1 каждый раз, когда аренда переходит из рук в руки. Таким образом, запросы клиента со старым токеном ограждения будут отклонены сервером. В этом сценарии, если старый лидер выходит из спящего режима, а новый лидер уже увеличил токен ограждения, все запросы старого лидера будут отклонены, поскольку он отправляет более старый (меньший) токен, чем тот, который сервер видел от новый лидер.

Реализовать ограждение в Kubernetes было бы сложно без изменения основного сервера API для учета соответствующих токенов ограждения для каждой аренды. Однако риск наличия нескольких ведущих контроллеров несколько снижается за счет самого сервера API k8s. Поскольку обновления устаревших объектов отклоняются, изменять их могут только контроллеры с самой последней версией объекта. Таким образом, хотя у нас может быть запущено несколько лидеров контроллеров, состояние ресурса никогда не вернется к более старым версиям, если контроллер пропустит изменение, внесенное другим лидером. Вместо этого время согласования увеличится, поскольку обоим лидерам необходимо обновить свои внутренние состояния ресурсов, чтобы гарантировать, что они действуют на основе самых последних версий.

Тем не менее, если вы используете этот пакет для реализации выборов лидера с использованием другого хранилища данных, это важное предостережение, о котором следует знать.

Заключение

Выборы лидера и распределенная блокировка являются важнейшими строительными блоками распределенных систем. При создании отказоустойчивых и высокодоступных приложений наличие таких инструментов в вашем распоряжении имеет решающее значение. Стандартная библиотека Kubernetes предоставляет нам проверенную оболочку вокруг своих примитивов, позволяющую разработчикам приложений легко встраивать выборы лидеров в свои собственные приложения.

Хотя использование этой конкретной библиотеки ограничивает вас развертыванием вашего приложения в Kubernetes, похоже, что в последнее время мир развивается именно так. Если на самом деле это нарушает условия сделки, вы, конечно, можете разветвить библиотеку и изменить ее для работы с любым ACID-совместимым и высокодоступным хранилищем данных.

Оставайтесь с нами, чтобы узнать больше о исходном коде k8s!

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/sklarsa/how-to-add-kubernetes-powered-leader-election-to-your-go-apps-57jh?1 Если есть какие-либо нарушения, свяжитесь с Study_golang. @163.com удалить
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3