リーダー選出

Spring Cloud Kubernetes リーダー選出メカニズムは、Kubernetes ConfigMap を使用して Spring Integration のリーダー選出 API を実装します。

複数のアプリケーションインスタンスがリーダーシップをめぐって競合しますが、リーダーシップが与えられるのは 1 つだけです。リーダーシップが付与されると、リーダーアプリケーションはリーダーシップ Context を持つ OnGrantedEvent アプリケーションイベントを受け取ります。アプリケーションは定期的にリーダーシップを獲得しようとし、最初の呼び出し元にリーダーシップが与えられます。リーダーは、クラスターから削除されるか、リーダーシップを放棄するまで、リーダーであり続けます。リーダーの削除が発生すると、前のリーダーは OnRevokedEvent アプリケーションイベントを受け取ります。削除後は、古いリーダーを含め、クラスター内のすべてのインスタンスが新しいリーダーになる可能性があります。

これをプロジェクトに含めるには、次の依存関係を追加します。Fabric8 リーダーの実装

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-kubernetes-fabric8-leader</artifactId>
</dependency>

リーダーの選択に使用される構成マップの名前を指定するには、次のプロパティを使用します。

spring.cloud.kubernetes.leader.config-map-name=leader

リーダー選出情報コントリビューター

Spring Cloud Kubernetes Leader には、Spring Boot の /actuator/info エンドポイントにリーダー選出情報を追加する InfoContributor が含まれています。このコントリビューターは、リーダー ID、ロール、現在のアプリケーションインスタンスがリーダーであるかどうかなど、現在のリーダーに関する情報を提供します。

出力例:

{
  "leaderElection": {
    "leaderId": "my-app-pod-1",
    "role": "my-role",
    "isLeader": true
  }
}

application.[properties | yaml] で management.info.leader.enabled を false に設定することで、この InfoContributor を無効にすることができます。

management.info.leader.enabled=false

リーダー選出を設定する別の方法があり、これは Fabric8 と Kubernetes クライアントの両方でネイティブサポートされています。長期的には、これがリーダー選出を設定するデフォルトの方法となり、以前の方法は廃止される予定です。この方法は、JDK の「プレビュー」機能と同様に扱うことができます。

これを使用するには、プロパティを設定する必要があります。

spring.cloud.kubernetes.leader.election.enabled=true

以前の実装とは異なり、この実装ではクラスタのバージョンに応じて、Lease  または ConfigMap のいずれかをロックとして使用します。リースがサポートされている場合でも、以下のコマンドで configMap の使用を強制できます。

spring.cloud.kubernetes.leader.election.use-config-map-as-lock=true

Lease または ConfigMap の名前は、プロパティを使用して定義できます (デフォルト値は spring-k8s-leader-election-lock です)。

spring.cloud.kubernetes.leader.election.lockName=other-name

ロックが作成される名前空間 (明示的なものが存在しない場合は default が設定される) も設定できます。

spring.cloud.kubernetes.leader.election.lockNamespace=other-namespace

リーダー選出プロセスが開始される前に、pod が準備完了(準備チェックによる)になるまで待つことができます。これはデフォルトで有効になっていますが、必要に応じて無効にすることができます。

spring.cloud.kubernetes.leader.election.waitForPodReady=false

以前の実装と同様に、デフォルトでイベントを公開しますが、これを無効にすることもできます。

spring.cloud.kubernetes.leader.election.publishEvents=false

リーダー選出プロセスを制御するパラメーターがいくつかあります。説明するには、このプロセスの高レベル実装を確認する必要があります。すべての pods 候補は、リーダーになろうとするか、ロックの取得を試みます。ロックがすでに取得されている場合は、spring.cloud.kubernetes.leader.election.retryPeriod (値は java.time.Duration で指定され、デフォルトでは 2 秒)ごとにロック取得を再試行します。

ロックが取得されない場合、現在の pod がリーダーになります。これは、いわゆる「レコード」(Lease または ConfigMap)をロックに挿入することによって行われます。この「レコード」に含まれるものの中には、leaseDuration があります(これは spring.cloud.kubernetes.leader.election.leaseDuration で指定できます。デフォルトでは 15 秒で、java.time.Duration 型です)。これはロックの TTL のように機能します。つまり、この期間(最終更新時刻から)が経過しない限り、他の候補はロックを取得できません。

特定の pod が(ロックを取得して)リーダーとしての地位を確立すると、その pod は(すべての spring.cloud.kubernetes.leader.election.retryPeriod に対して)継続的にリースの更新を試みます。言い換えれば、リーダーシップの拡大を試みます。更新が発生すると、ロック内に保存されている「レコード」が更新されます。たとえば、renewTime はレコード内で更新され、前回の更新がいつ行われたかを示します。(これらのフィールドの内容は、たとえば kubectl describe lease…​ を使用することでいつでも確認できます)

更新は、spring.cloud.kubernetes.leader.election.renewDeadline で指定された一定の間隔内に行われなければなりません。デフォルトでは 10 秒に設定されており、リーダーである pod は最大 10 秒以内にリーダーシップを更新する必要があります。この時間が経過しない場合、この pod はリーダーシップを失い、リーダー選出が再度開始されます。他の pods は 2 秒ごとにリーダーになろうとするため(デフォルト)、リーダーシップを失った pod が再びリーダーになる可能性があります。他の pods がリーダーになる可能性を高めたい場合は、プロパティ(秒単位で指定、デフォルトは 3)を設定できます。

spring.cloud.kubernetes.leader.election.wait-after-renewal-failure=3

これは、リースを更新できずリーダーシップを失った pod が、再びリーダーになろうとする前に、この秒数待機することを意味します。

これらの設定を例に基づいて説明してみましょう。リーダー選出に参加する 2 つの pods があります。説明を簡単にするために、これらを podA と podB と呼びます。これらは同時に 12:00:00 で開始されますが、podA がリーダーとして選出されます。つまり、2 秒ごと(retryPeriod)、podB は新しいリーダーになろうとします。つまり、12:00:0212:00:04 …と、基本的に「リーダーになってもいいですか?」と尋ねていることになります。この簡略化された例では、その質問への答えは podA のアクティビティに基づいて得られます。

podA がリーダーになった後、2 秒ごとにリーダーシップの「延長」、つまり更新を試みます。そのため、12:00:0212:00:04 などでは、podA がロックにアクセスし、自身のレコードを更新して、依然としてリーダーであることを反映させます。最後の正常な更新から次の更新までの間は、ちょうど 10 秒です (renewalDeadline)。この 10 秒以内にリーダーシップの更新に失敗した場合 (接続の問題や GC の大きな一時停止など)、podA はリーダーとしての立場を停止し、podB がリーダーシップを獲得できるようになります。podA がリーダーとしてのロールを正常に終了すると、ロックレコードは「クリア」されます。これは基本的に、podB がすぐにリーダーシップを獲得できることを意味します。

たとえば、podA が OutOfMemory でロックレコードを適切に更新できずに終了した場合、状況は変わります。このとき、leaseDuration 引数が重要になります。最も簡単な説明は、例を挙げることです。

podA has renewed its leadership at 12:00:04, but at 12:00:05 it has been killed by the OOMKiller. At 12:00:06podB will try to become the leader. It will check if "now" (12:00:06) is after last renewal + lease duration, essentially it will check:

12:00:06 > (12:00:04 + 00:00:10)

The condition is not fulfilled, so it can’t become the leader. Same result will be at 12:00:0812:00:10 and so on, until 12:00:16 and this is where the TTL (leaseDuration) of the lock will expire and podB can acquire it. As such, a lower value of leaseDuration will mean a faster acquiring of leadership by other pods.

You might have to give proper RBAC to be able to use this functionality, for example:

 - apiGroups: [ "coordination.k8s.io" ]
   resources: [ "leases" ]
   resourceNames: [ "spring-k8s-leader-election-lock" ]
   verbs: [ "get", "update", "create" ]
 - apiGroups: [ "" ]
   resources: [ "configmaps" ]
   resourceNames: [ "spring-k8s-leader-election-lock" ]
   verbs: [ "get", "update", "create" ]