「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Kubernetes を利用したリーダー選挙を Go アプリに追加する方法

Kubernetes を利用したリーダー選挙を Go アプリに追加する方法

2024 年 7 月 30 日に公開
ブラウズ:145

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

元々はブログで公開されました

Kubernetes 標準ライブラリには、エコシステムの一部であるさまざまなサブパッケージの多くに隠された宝石がたくさんあります。そのような例の 1 つは、私が最近発見した k8s.io/client-go/tools/leaderelection です。これは、Kubernetes クラスター内で実行されているアプリケーションにリーダー選出プロトコルを追加するために使用できます。この記事では、リーダー選挙とは何か、それがこの Kubernetes パッケージでどのように実装されるかについて説明し、独自のアプリケーションでこのライブラリを使用する方法の例を示します。

リーダー選挙

リーダー選挙は、高可用性ソフトウェアの中核となる構成要素である分散システムの概念です。これにより、複数の同時プロセスが相互に調整し、単一の「リーダー」プロセスを選択できるようになり、そのプロセスがデータ ストアへの書き込みなどの同期アクションの実行を担当します。

これは、ハードウェアやネットワーク障害に対する冗長性を確保するために複数のプロセスが実行されているが、データの一貫性を確保するためにストレージに同時に書き込むことができない分散データベースやキャッシュなどのシステムで役立ちます。将来のある時点でリーダー プロセスが応答しなくなった場合、残りのプロセスによって新しいリーダーの選挙が開始され、最終的にリーダーとして機能する新しいプロセスが選択されます。

この概念を使用すると、単一のリーダーと複数のスタンバイ レプリカを備えた高可用性ソフトウェアを作成できます。

Kubernetes では、コントローラー ランタイム パッケージはリーダー選挙を使用してコントローラーの高可用性を実現します。コントローラーのデプロイメントでは、リソースの調整は、プロセスがリーダーであり、他のレプリカがスタンバイで待機している場合にのみ発生します。リーダー ポッドが応答しなくなった場合、残りのレプリカは新しいリーダーを選択して後続の調整を実行し、通常の操作を再開します。

Kubernetes リース

このライブラリは、プロセスによって取得できる Kubernetes リース (分散ロック) を使用します。リースは、単一の ID によって一定期間保持されるネイティブ 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 エコシステムによって 3 つの方法で使用されます:

  1. ノード ハートビート: すべてのノードには対応するリース リソースがあり、その renewTime フィールドを継続的に更新します。リースの renewTime がしばらく更新されなかった場合、ノードは利用不可として汚染され、それ以上のポッドはスケジュールされなくなります。
  2. リーダー選挙: この場合、リースは、リーダーにリースのholderIdentityを更新させることで、複数のプロセス間を調整するために使用されます。異なる ID を持つスタンバイ レプリカは、リースの期限が切れるのを待ったままになります。リースの有効期限が切れ、リーダーによって更新されない場合は、新しい選挙が行われ、残りのレプリカが、そのholderIdentity を自分のもので更新することによって、リースの所有権を取得しようとします。 Kubernetes API サーバーは古いオブジェクトの更新を禁止しているため、リースを正常に更新できるのは 1 つのスタンバイ ノードだけであり、その時点で新しいリーダーとして実行を継続します。
  3. API サーバー ID: v1.26 以降、ベータ機能として、各 kube-apiserver レプリカは専用のリースを作成することでその ID を公開します。これは比較的スリムな新しい機能であるため、実行されている API サーバーの数以外に Lease オブジェクトから得られるものはあまりありません。ただし、これにより、将来の k8s バージョンでこれらのリースにさらにメタデータを追加する余地が残ります。

次に、リーダー選出シナリオでリースを使用する方法を示すサンプル プログラムを作成して、リースの 2 番目の使用例を調べてみましょう。

サンプルプログラム

このコード例では、リーダー選挙とリース操作の詳細を処理するためにリーダー選挙パッケージを使用しています。

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 パッケージの優れている点は、リーダー選挙を処理するためのコールバック ベースのフレームワークを提供していることです。こうすることで、特定の状態の変化にきめ細かく対応し、新しいリーダーが選出されたときにリソースを適切に解放できます。これらのコールバックを個別の goroutine で実行することにより、パッケージは Go の強力な同時実行サポートを利用して、マシン リソースを効率的に利用します。

テストしてみる

これをテストするには、kind を使用してテスト クラスターを起動しましょう。

$ kind create cluster

サンプル コードを main.go にコピーし、新しいモジュールを作成し (go mod init leaderelectiontest)、整理して (go mod tiny) 依存関係をインストールします。 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!

これは、この記事を書いている時点で私のコンピュータ上で実行されていたプロセスの PID にすぎないため、正確なリーダー ID は例 (138) のものとは異なります。

テスト クラスターで作成されたリースは次のとおりです:

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

「ホルダー ID」がプロセスの 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

この 2 番目のプロセスは、最初のプロセスが応答しなくなるまで永遠に待機します。最初のプロセスを強制終了して、15 秒ほど待ちます。最初のプロセスがリースの請求を更新していないため、.spec.renewTime フィールドは更新されなくなります。リースの更新時期がその期間よりも古いため、最終的には 2 番目のプロセスで新しいリーダーの選挙がトリガーされます。このプロセスは現在実行されている唯一のプロセスであるため、それ自体が新しいリーダーとして選出されます。

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

最初のリーダーが終了した後も複数のプロセスが実行中の場合、リースを取得する最初のプロセスが新しいリーダーとなり、残りは引き続きスタンバイ状態になります。

単一リーダーの保証はない

このパッケージは、「1 つのクライアントだけがリーダー (別名フェンシング) として機能することを保証しない」という点で、絶対確実というわけではありません。たとえば、リーダーが一時停止され、そのリースが期限切れになると、別のスタンバイ レプリカがリースを取得します。その後、元のリーダーが実行を再開すると、自分がまだリーダーであると考え、新しく選出されたリーダーと一緒に作業を続行します。このようにして、最終的に 2 つのリーダーを同時に実行することができます。

これを修正するには、サーバーへの各リクエストにリースを参照するフェンシング トークンを含める必要があります。フェンシング トークンは事実上、リースの所有者が変わるたびに 1 ずつ増加する整数です。したがって、古いフェンシング トークンを持つクライアントは、そのリクエストをサーバーによって拒否されます。このシナリオでは、古いリーダーがスリープから目覚め、新しいリーダーがすでにフェンシング トークンをインクリメントしている場合、古いリーダーのリクエストはすべて拒否されます。これは、サーバーがサーバーから認識したものよりも古い (小さい) トークンを送信しているためです。新しいリーダー。

各リースに対応するフェンシング トークンを考慮してコア API サーバーを変更しない限り、Kubernetes にフェンシングを実装することは困難です。ただし、複数のリーダー コントローラーを持つリスクは、k8s API サーバー自体によってある程度軽減されます。古いオブジェクトへの更新は拒否されるため、オブジェクトの最新バージョンを持つコントローラーのみがそのオブジェクトを変更できます。したがって、複数のコントローラー リーダーを実行できますが、コントローラーが別のリーダーによる変更を見逃しても、リソースの状態が古いバージョンに戻ることはありません。代わりに、両方のリーダーが最新バージョンで動作していることを確認するためにリソースの内部状態を更新する必要があるため、調整時間が増加します。

ただし、このパッケージを使用して、別のデータ ストアを使用してリーダー選出を実装している場合、これは注意すべき重要な注意事項です。

結論

リーダーの選出と分散ロックは、分散システムの重要な構成要素です。フォールトトレラントで可用性の高いアプリケーションを構築しようとする場合、このようなツールを自由に使えることが重要です。 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