"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Comment ajouter l'élection des dirigeants alimentée par Kubernetes à vos applications Go

Comment ajouter l'élection des dirigeants alimentée par Kubernetes à vos applications Go

Publié le 2024-07-30
Parcourir:417

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

Publié à l'origine sur par blog

La bibliothèque standard de Kubernetes regorge de joyaux, cachés dans de nombreux sous-paquets qui font partie de l'écosystème. Un exemple que j'ai découvert récemment k8s.io/client-go/tools/leaderelection, qui peut être utilisé pour ajouter un protocole d'élection de leader à toute application exécutée dans un cluster Kubernetes. Cet article expliquera ce qu'est l'élection du leader, comment elle est implémentée dans ce package Kubernetes et fournira un exemple de la façon dont nous pouvons utiliser cette bibliothèque dans nos propres applications.

Élection du chef

L'élection du leader est un concept de systèmes distribués qui constitue un élément essentiel des logiciels hautement disponibles. Il permet à plusieurs processus simultanés de se coordonner et d'élire un seul processus « leader », qui est ensuite responsable de l'exécution d'actions synchrones telles que l'écriture dans un magasin de données.

Ceci est utile dans les systèmes tels que les bases de données distribuées ou les caches, où plusieurs processus sont en cours d'exécution pour créer une redondance contre les pannes matérielles ou réseau, mais ne peuvent pas écrire simultanément sur le stockage pour garantir la cohérence des données. Si le processus de leader ne répond plus à un moment donné dans le futur, les processus restants lanceront une nouvelle élection de leader, choisissant finalement un nouveau processus pour agir en tant que leader.

Grâce à ce concept, nous pouvons créer des logiciels hautement disponibles avec un seul leader et plusieurs répliques de secours.

Dans Kubernetes, le package d'exécution du contrôleur utilise l'élection du leader pour rendre les contrôleurs hautement disponibles. Dans un déploiement de contrôleur, la réconciliation des ressources se produit uniquement lorsqu'un processus est le leader et que d'autres réplicas sont en attente en attente. Si le pod leader ne répond plus, les répliques restantes éliront un nouveau leader pour effectuer les rapprochements ultérieurs et reprendre le fonctionnement normal.

Baux Kubernetes

Cette bibliothèque utilise un bail Kubernetes, ou verrou distribué, qui peut être obtenu par un processus. Les baux sont des ressources Kubernetes natives détenues par une seule identité, pour une durée donnée, avec une option de renouvellement. Voici un exemple de spécification tiré de la documentation :

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"

Les baux sont utilisés par l'écosystème k8s de trois manières :

  1. Node Heartbeats : chaque nœud possède une ressource de location correspondante et met à jour son champ renouvelerTime de manière continue. Si la durée de renouvellement d'un bail n'a pas été mise à jour depuis un certain temps, le nœud sera considéré comme indisponible et aucun autre pod ne lui sera programmé.
  2. Élection du leader : dans ce cas, un bail est utilisé pour coordonner plusieurs processus en demandant à un leader de mettre à jour l'identité du titulaire du bail. Les répliques de secours, avec des identités différentes, sont bloquées en attendant l'expiration du bail. Si le bail expire et n'est pas renouvelé par le leader, une nouvelle élection a lieu au cours de laquelle les répliques restantes tentent de prendre possession du bail en mettant à jour son identité de détenteur avec la leur. Étant donné que le serveur API Kubernetes interdit les mises à jour des objets obsolètes, un seul nœud de secours pourra mettre à jour le bail, auquel cas il poursuivra son exécution en tant que nouveau leader.
  3. Identité du serveur API : à partir de la v1.26, en tant que fonctionnalité bêta, chaque réplique kube-apiserver publiera son identité en créant un bail dédié. Puisqu'il s'agit d'une nouvelle fonctionnalité relativement mince, il n'y a pas grand-chose d'autre qui puisse être dérivé de l'objet Lease en dehors du nombre de serveurs API en cours d'exécution. Mais cela laisse la possibilité d'ajouter plus de métadonnées à ces baux dans les futures versions de k8.

Explorons maintenant ce deuxième cas d'utilisation des baux en écrivant un exemple de programme pour démontrer comment vous pouvez les utiliser dans des scénarios d'élection de dirigeants.

Exemple de programme

Dans cet exemple de code, nous utilisons le package leaderelection pour gérer les spécificités de l'élection du leader et de la manipulation du bail.

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

}

Ce qui est bien avec le package leaderelection, c'est qu'il fournit un cadre basé sur le rappel pour gérer les élections des dirigeants. De cette façon, vous pouvez agir de manière granulaire sur des changements d’état spécifiques et libérer correctement des ressources lorsqu’un nouveau leader est élu. En exécutant ces rappels dans des goroutines distinctes, le package profite de la forte prise en charge de la concurrence de Go pour utiliser efficacement les ressources de la machine.

Le tester

Pour tester cela, développons un cluster de test en utilisant kind.

$ kind create cluster

Copiez l'exemple de code dans main.go, créez un nouveau module (go mod init leaderelectiontest) et rangez-le (go mod spice) pour installer ses dépendances. Une fois que vous avez exécuté, lancez main.go, vous devriez voir un résultat comme celui-ci :

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

L'identité exacte du leader sera différente de celle de l'exemple (138), puisqu'il s'agit simplement du PID du processus qui était en cours d'exécution sur mon ordinateur au moment de la rédaction.

Et voici le bail qui a été créé dans le cluster de test :

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

Vérifiez que « l'identité du titulaire » est la même que le PID du processus, 138.

Maintenant, ouvrons un autre terminal et exécutons le même fichier main.go dans un processus séparé :

$ 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

Ce deuxième processus attendra indéfiniment, jusqu'à ce que le premier ne réponde plus. Tuons le premier processus et attendons environ 15 secondes. Maintenant que le premier processus ne renouvelle pas sa réclamation sur le bail, le champ .spec.renewTime ne sera plus mis à jour. Cela finira par amener le deuxième processus à déclencher l'élection d'un nouveau leader, puisque la période de renouvellement du bail est plus ancienne que sa durée. Parce que ce processus est le seul en cours, il s’élira lui-même comme nouveau leader.

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

S'il y avait plusieurs processus toujours en cours après la sortie du leader initial, le premier processus à acquérir le bail serait le nouveau leader, et les autres continueraient à être en attente.

Pas de garanties à leader unique

Ce package n'est pas infaillible, dans le sens où il "ne garantit pas qu'un seul client agit en tant que leader (c'est-à-dire l'escrime)". Par exemple, si un leader est mis en pause et laisse son bail expirer, une autre réplique de secours acquerra le bail. Ensuite, une fois que le leader d'origine aura repris l'exécution, il pensera qu'il est toujours le leader et continuera à travailler aux côtés du leader nouvellement élu. De cette façon, vous pouvez vous retrouver avec deux dirigeants qui courent simultanément.

Pour résoudre ce problème, un jeton de clôture faisant référence au bail doit être inclus dans chaque requête adressée au serveur. Un jeton d'escrime est en fait un nombre entier qui augmente de 1 à chaque fois qu'un bail change de mains. Ainsi, un client possédant un ancien jeton de clôture verra ses requêtes rejetées par le serveur. Dans ce scénario, si un ancien chef se réveille et qu'un nouveau chef a déjà incrémenté le jeton d'escrime, toutes les demandes de l'ancien chef seront rejetées car il envoie un jeton plus ancien (plus petit) que celui que le serveur a vu du serveur. nouveau leader.

La mise en œuvre du fencing dans Kubernetes serait difficile sans modifier le serveur API principal pour prendre en compte les jetons de fencing correspondants pour chaque bail. Cependant, le risque d'avoir plusieurs contrôleurs principaux est quelque peu atténué par le serveur API k8s lui-même. Étant donné que les mises à jour des objets obsolètes sont rejetées, seuls les contrôleurs disposant de la version la plus récente d’un objet peuvent le modifier. Ainsi, même si plusieurs contrôleurs leaders sont en cours d'exécution, l'état d'une ressource ne régressera jamais vers les anciennes versions si un contrôleur manque une modification apportée par un autre leader. Au lieu de cela, le temps de réconciliation augmenterait car les deux dirigeants devraient actualiser leurs propres états internes de ressources pour s'assurer qu'ils agissent sur les versions les plus récentes.

Néanmoins, si vous utilisez ce package pour mettre en œuvre l'élection d'un chef à l'aide d'un autre magasin de données, il s'agit d'une mise en garde importante à prendre en compte.

Conclusion

L'élection du leader et le verrouillage distribué sont des éléments essentiels des systèmes distribués. Lorsque vous essayez de créer des applications tolérantes aux pannes et hautement disponibles, il est essentiel de disposer d'outils comme ceux-ci. La bibliothèque standard Kubernetes nous offre un wrapper éprouvé autour de ses primitives pour permettre aux développeurs d'applications d'intégrer facilement l'élection des dirigeants dans leurs propres applications.

Bien que l'utilisation de cette bibliothèque particulière vous limite au déploiement de votre application sur Kubernetes, cela semble être la façon dont le monde évolue récemment. S'il s'agit en fait d'un problème, vous pouvez bien sûr créer une bibliothèque et la modifier pour qu'elle fonctionne avec n'importe quelle banque de données conforme à ACID et hautement disponible.

Restez à l'écoute pour plus d'informations approfondies sur les sources K8 !

Déclaration de sortie Cet article est reproduit sur : https://dev.to/sklarsa/how-to-add-kubernetes-powered-leader-election-to-your-go-apps-57jh?1 En cas d'infraction, veuillez contacter study_golang @163.com supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3