„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > So fügen Sie Ihren Go-Apps die von Kubernetes unterstützte Führungswahl hinzu

So fügen Sie Ihren Go-Apps die von Kubernetes unterstützte Führungswahl hinzu

Veröffentlicht am 30.07.2024
Durchsuche:584

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

Ursprünglich veröffentlicht am von Blog

Die Kubernetes-Standardbibliothek ist voller Juwelen, die in vielen der verschiedenen Unterpakete versteckt sind, die Teil des Ökosystems sind. Ein solches Beispiel habe ich kürzlich unter k8s.io/client-go/tools/leaderelection entdeckt und kann verwendet werden, um jeder Anwendung, die in einem Kubernetes-Cluster ausgeführt wird, ein Leader-Wahlprotokoll hinzuzufügen. In diesem Artikel wird erläutert, was eine Führungswahl ist, wie sie in diesem Kubernetes-Paket implementiert wird und ein Beispiel dafür bereitgestellt wird, wie wir diese Bibliothek in unseren eigenen Anwendungen verwenden können.

Führerwahl

Leaderwahl ist ein verteiltes Systemkonzept, das einen Kernbaustein hochverfügbarer Software darstellt. Es ermöglicht mehreren gleichzeitigen Prozessen, sich untereinander zu koordinieren und einen einzigen „Leiter“-Prozess zu wählen, der dann für die Ausführung synchroner Aktionen wie das Schreiben in einen Datenspeicher verantwortlich ist.

Dies ist in Systemen wie verteilten Datenbanken oder Caches nützlich, in denen mehrere Prozesse ausgeführt werden, um Redundanz gegen Hardware- oder Netzwerkausfälle zu schaffen, aber nicht gleichzeitig in den Speicher schreiben können, um die Datenkonsistenz sicherzustellen. Wenn der Anführerprozess irgendwann in der Zukunft nicht mehr reagiert, werden die verbleibenden Prozesse eine neue Anführerwahl einleiten und schließlich einen neuen Prozess als Anführer auswählen.

Mit diesem Konzept können wir hochverfügbare Software mit einem einzigen Leader und mehreren Standby-Replikaten erstellen.

In Kubernetes verwendet das Controller-Runtime-Paket die Leader-Wahl, um Controller hochverfügbar zu machen. In einer Controller-Bereitstellung findet der Ressourcenabgleich nur statt, wenn ein Prozess der Anführer ist und andere Replikate im Standby-Modus warten. Wenn der Leader-Pod nicht mehr reagiert, wählen die verbleibenden Replikate einen neuen Leader, der nachfolgende Abstimmungen durchführt und den normalen Betrieb wieder aufnimmt.

Kubernetes-Leasingverträge

Diese Bibliothek verwendet eine Kubernetes-Lease oder verteilte Sperre, die von einem Prozess abgerufen werden kann. Leases sind native Kubernetes-Ressourcen, die von einer einzelnen Identität für einen bestimmten Zeitraum mit einer Verlängerungsoption gehalten werden. Hier ist eine Beispielspezifikation aus den Dokumenten:

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"

Mietverträge werden vom k8s-Ökosystem auf drei Arten genutzt:

  1. Knoten-Heartbeats: Jeder Knoten verfügt über eine entsprechende Lease-Ressource und aktualisiert sein renewTime-Feld fortlaufend. Wenn die RenewTime eines Mietvertrags längere Zeit nicht aktualisiert wurde, wird der Knoten als nicht verfügbar markiert und es werden keine weiteren Pods für ihn geplant.
  2. Leader-Wahl: In diesem Fall wird ein Lease verwendet, um mehrere Prozesse zu koordinieren, indem ein Leader die Inhaberidentität des Lease aktualisiert. Standby-Replikate mit unterschiedlichen Identitäten stecken fest und warten auf den Ablauf der Lease. Wenn der Mietvertrag tatsächlich abläuft und vom Anführer nicht erneuert wird, findet eine Neuwahl statt, bei der die verbleibenden Replikate versuchen, den Besitz des Mietvertrags zu übernehmen, indem sie dessen Inhaberidentität durch ihre eigene aktualisieren. Da der Kubernetes-API-Server Aktualisierungen veralteter Objekte nicht zulässt, kann nur ein einziger Standby-Knoten den Lease erfolgreich aktualisieren und setzt dann die Ausführung als neuer Leader fort.
  3. API-Server-Identität: Ab Version 1.26 veröffentlicht jedes Kube-Apiserver-Replikat als Beta-Funktion seine Identität, indem es einen dedizierten Lease erstellt. Da es sich um eine relativ schlanke, neue Funktion handelt, lässt sich aus dem Lease-Objekt nicht viel anderes ableiten, als wie viele API-Server ausgeführt werden. Dies lässt jedoch Raum, um in zukünftigen k8s-Versionen weitere Metadaten zu diesen Mietverträgen hinzuzufügen.

Lassen Sie uns nun diesen zweiten Anwendungsfall von Leases untersuchen, indem wir ein Beispielprogramm schreiben, um zu demonstrieren, wie Sie sie in Szenarios zur Wahl von Führungskräften verwenden können.

Beispielprogramm

In diesem Codebeispiel verwenden wir das Paket „leaderelection“, um die Besonderheiten der Leader-Wahl und der Lease-Manipulation zu verwalten.

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

}

Das Schöne am Leaderelection-Paket ist, dass es ein Callback-basiertes Framework für die Abwicklung von Leader-Wahlen bietet. Auf diese Weise können Sie granular auf bestimmte Zustandsänderungen reagieren und Ressourcen ordnungsgemäß freigeben, wenn ein neuer Anführer gewählt wird. Durch die Ausführung dieser Rückrufe in separaten Goroutinen nutzt das Paket die starke Parallelitätsunterstützung von Go, um Maschinenressourcen effizient zu nutzen.

Probieren Sie es aus

Um dies zu testen, starten wir einen Testcluster mit kind.

$ kind create cluster

Kopieren Sie den Beispielcode in main.go, erstellen Sie ein neues Modul (Go Mod Init Leaderelectiontest) und räumen Sie es auf (Go Mod Tidy), um seine Abhängigkeiten zu installieren. Sobald Sie go run main.go ausführen, sollten Sie eine Ausgabe wie diese sehen:

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

Die genaue Führungsidentität wird sich von der im Beispiel (138) unterscheiden, da dies nur die PID des Prozesses ist, der zum Zeitpunkt des Schreibens auf meinem Computer ausgeführt wurde.

Und hier ist der Lease, der im Testcluster erstellt wurde:

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

Achten Sie darauf, dass die „Inhaberidentität“ mit der PID des Prozesses, 138, übereinstimmt.

Jetzt öffnen wir ein anderes Terminal und führen dieselbe main.go-Datei in einem separaten Prozess aus:

$ 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

Dieser zweite Prozess wird ewig warten, bis der erste nicht mehr reagiert. Lassen Sie uns den ersten Prozess beenden und etwa 15 Sekunden warten. Da nun der erste Prozess seinen Anspruch auf den Mietvertrag nicht verlängert, wird das Feld „.spec.renewTime“ nicht mehr aktualisiert. Dies wird schließlich dazu führen, dass der zweite Prozess eine neue Wahl des Anführers auslöst, da die Verlängerungszeit des Mietvertrags älter als seine Laufzeit ist. Da dieser Prozess der einzige ist, der derzeit läuft, wird er sich selbst zum neuen Anführer wählen.

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

Wenn nach dem Ausscheiden des ursprünglichen Anführers noch mehrere Prozesse ausgeführt würden, wäre der erste Prozess, der die Lease erwirbt, der neue Anführer, und der Rest wäre weiterhin im Standby-Modus.

Keine Single-Leader-Garantie

Dieses Paket ist nicht narrensicher, da es „nicht garantiert, dass nur ein Kunde als Anführer fungiert (auch bekannt als Fechten)“. Wenn beispielsweise ein Anführer angehalten wird und seine Lease ablaufen lässt, übernimmt ein anderes Standby-Replikat die Lease. Sobald der ursprüngliche Anführer dann die Ausführung wieder aufnimmt, wird er denken, dass er immer noch der Anführer ist, und weiterhin an der Seite des neu gewählten Anführers arbeiten. Auf diese Weise kann es passieren, dass zwei Anführer gleichzeitig laufen.

Um dies zu beheben, muss in jede Anfrage an den Server ein Fencing-Token aufgenommen werden, der auf den Mietvertrag verweist. Ein Fechtmarker ist praktisch eine Ganzzahl, die sich jedes Mal um 1 erhöht, wenn ein Pachtvertrag den Besitzer wechselt. Daher werden die Anfragen eines Clients mit einem alten Fencing-Token vom Server abgelehnt. Wenn in diesem Szenario ein alter Anführer aus dem Schlaf erwacht und ein neuer Anführer den Fencing-Token bereits erhöht hat, werden alle Anfragen des alten Anführers abgelehnt, da er einen älteren (kleineren) Token sendet, als der Server ihn gesehen hat neuerer Anführer.

Die Implementierung von Fencing in Kubernetes wäre schwierig, ohne den Kern-API-Server zu ändern, um entsprechende Fencing-Tokens für jeden Mietvertrag zu berücksichtigen. Das Risiko mehrerer führender Controller wird jedoch durch den k8s-API-Server selbst etwas gemindert. Da Aktualisierungen veralteter Objekte abgelehnt werden, können nur Controller mit der aktuellsten Version eines Objekts dieses ändern. Während wir also mehrere Controller-Leader ausführen könnten, würde der Status einer Ressource niemals auf ältere Versionen zurückgehen, wenn ein Controller eine von einem anderen Leader vorgenommene Änderung übersieht. Stattdessen würde sich die Abstimmungszeit verlängern, da beide Führungskräfte ihre eigenen internen Ressourcenzustände aktualisieren müssen, um sicherzustellen, dass sie auf der Grundlage der aktuellsten Versionen handeln.

Wenn Sie dieses Paket jedoch verwenden, um die Wahl des Anführers mithilfe eines anderen Datenspeichers zu implementieren, ist dies ein wichtiger Vorbehalt, den Sie beachten sollten.

Abschluss

Leader-Wahl und verteiltes Sperren sind wichtige Bausteine ​​verteilter Systeme. Wenn Sie versuchen, fehlertolerante und hochverfügbare Anwendungen zu erstellen, ist die Verfügbarkeit solcher Tools von entscheidender Bedeutung. Die Kubernetes-Standardbibliothek bietet uns einen kampferprobten Wrapper um ihre Grundelemente, damit Anwendungsentwickler die Leader-Wahl problemlos in ihre eigenen Anwendungen integrieren können.

Während die Verwendung dieser speziellen Bibliothek Sie auf die Bereitstellung Ihrer Anwendung auf Kubernetes beschränkt, scheint sich die Welt in letzter Zeit so zu entwickeln. Wenn das tatsächlich ein Dealbreaker ist, können Sie die Bibliothek natürlich abzweigen und so modifizieren, dass sie mit jedem ACID-kompatiblen und hochverfügbaren Datenspeicher funktioniert.

Bleiben Sie dran für weitere Einblicke in die K8s-Quelle!

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/sklarsa/how-to-add-kubernetes-powered-leader-election-to-your-go-apps-57jh?1 Bei Verstößen wenden Sie sich bitte an Study_golang @163.com löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3