kubeletのPLEGをソースコード読んで理解したかった

Kubernetes

はじめに

こんにちは、最近パンを焼いている者です。

これは「Kubernetes Advent Calendar 2022」 6日目の記事です。2020年2021年 も参加させて頂きました。

昨年の「ReplicaSet Controllerは削除するPodをどうやって決めるのか」 に続き、今年もコード読んで勉強した内容を共有させてください。

PLEG is not healthy と NotReadyなノード

KubernetesにおいてノードがNotReadyな状態になる原因はさまざまです。例えばこのように、kubeletのログでPLEG is not healthy: pleg was last seen active 3m20s ago; threshold is 3m0s みたいに出力され、NotReadyになることがあります。PLEG(←?!) が最後にactiveになったのが3分20秒前らしく、閾値の3分を超えているので異常と判断したようです。ちょっと何を言ってるのかわからない、PLEGってそもそも何なんだ…

PLEG とは?

PLEGとは、kubeletの一部で、Pod Lifecycle Event Generatorの略です。概要は、Kubernetesの日本語ドキュメントのコンセプト に載っていました。

一旦desired stateを設定すると、Pod Lifecycle Event Generator(PLEG)を使用したKubernetes コントロールプレーンが機能し、クラスターの現在の状態をdesired state (望ましい状態)に一致させます。そのためにKubernetesはさまざまなタスク(たとえば、コンテナの起動または再起動、特定アプリケーションのレプリカ数のスケーリング等)を自動的に実行します

なるほど、めっちゃ重要そうですね。このドキュメント内にあるPLEGのDesign Proposalへのlinkが埋め込まれているのですが、古いらしくアクセスできません。どうやら古いDesign Proposalは kubernetes/design-proposals-archive に移動されたようです。PLEGのものはこちらです。

design-proposals-archive/node/pod-lifecycle-event-generator.md at main · kubernetes/design-proposals-archive
Archive of Kubernetes Design Proposals. Contribute to kubernetes/design-proposals-archive development by creating an account on GitHub.

↓の図のように、Container Runtimeおよびkubeletのmain loopとお話するのが仕事のようです。


引用元: https://github.com/kubernetes/design-proposals-archive/blob/main/node/pod-lifecycle-event-generator.md#overview

PLEG の relist 処理

Design Proposalの中の Detect Changes in Container States Via Relisting という章を読むと、relist と呼ばれる処理によってコンテナの状態変化を検知する設計になっています。

  • コンテナの状態変化を検知するために、PLEGはすべてのコンテナを定期的にrelistする
  • PLEGに任せることですべてのPod Workerが並列でコンテナランタイムをpollingするのを防ぐ
  • その後はSyncが必要なPod Workerのみを起動するので、さらに効率がいい

なお、ここでいう Pod Workerは、PLEGと同じくkubeletのコンポーネントの1つです。Pod Workerは個々のPodに対する操作を追跡し、コンテナランタイムなどとの整合性を保証します (雑な訳)。

Runtime Pod Cache とは?

PLEGを調べる上でもう1つ重要そうなのは、Runtime Pod Cacheです。こちらもDesign Proposalがあったので読んでみます。

design-proposals-archive/node/runtime-pod-cache.md at main · kubernetes/design-proposals-archive
Archive of Kubernetes Design Proposals. Contribute to kubernetes/design-proposals-archive development by creating an account on GitHub.


引用元: https://github.com/kubernetes/design-proposals-archive/blob/main/node/runtime-pod-cache.md#runtime-pod-cache

載っている図はPLEGのとき見たものとほとんど同じですが、PLEGとPod Workerの間に”pod cache”の箱が追記されています。

Runtime Pod Cacheは、すべてのPodの状態を保存するインメモリキャッシュで、Podの同期に使われます。PLEGによって管理され、内部のPodステータスに関するSingle Source of Truth (SSOT) として機能し、kubeletは直接コンテナランタイムに問い合わせる必要はありません。

PLEGはPod Cacheのエントリーを更新する役割を担っており、常にキャッシュを最新の状態に保ちます。以下の順で処理を行い、Podに変化があった場合のみ、対応するPod Lifecycle Eventを生成し、送信する設計のようです。

  1. Detect change of container state
  2. Inspect the pod for details
  3. Update the pod cache with the new PodStatus

その他の情報とソースコード

PLEGのことがふんわりわかったところで、もう少し詳しく調べてみます。

まずこちらの記事が、図が豊富ですごくすごくわかりやすいです。ソースコードの抜粋もあります。

一方、自分でもソースコードを読んでみかったので、以下にまとめてみます。今回読んだソースコードは、Kubernetes 1.25時点のものです。

pkg/kubelet/pleg/pleg.go

PLEGにおける PodLifeCycleEventの種類や関連する構造体、インターフェイスはこちらに定義されています。ContainerStartedContainerDiedContainerRemovedPodSyncContainerChanged といった種類のイベントがあります。

pkg/kubelet/pleg/generic.go

PLEG関連のソースコードは主にここに書かれています。GenericPLEG構造体はこんな感じです。cache が冒頭説明したRuntime Pod Cacheを指しているのでしょう。kubecontainer.Cache interface のコメントを見ると、キャシュにはコンテナランタイムから見えるすべてのコンテナ/PodのPodStatusが保存されているようです。

定数の定義はこんな感じです。relistThreshold は3分ですね。この閾値は PLEG is not healthy: pleg was last seen active XXmYYs ago; threshold is 3m0s というログの中で見ました。

*GenericPLEG.Healthy()

GenericPLEG構造体のポインタメソッドとして、Healthy() メソッドがあります。これはPLEG自身がが適切に動作しているかどうか確認するものです。具体的には、2つのrelist間隔が 3分 を超える場合(if elapsed > relistThreshold) に失敗と判断されます。ここ最近親の顔より見た PLEG is not healthy: pleg was last seen active XXmYYs ago; threshold is 3m0s といったエラーを報告しているのがここです。

elapsed は、relistTime というtime.Time型の時刻(たぶん直近のrelistを開始した時刻)から、どれだけの時間が経過したかを測っているように見えます。

*GenericPLEG.relist()

PLEGのrelistについては、relist() メソッドで実装されています。relistではコンテナランタイムに問い合わせを行い、Pod/コンテナのリストを取得します。そして、内部のPod/コンテナと比較し、その結果に応じて適宜イベントを生成します。

最初に現在の時刻を取得します。そしてすべてのPod情報をコンテナランタイムから取得したあと、relistTime を直近のrelist()開始時のtimestampに更新します。g.runtime.GetPods(true) で引数にtrueを渡すことで、exitedやdeadなコンテナも含めたリストを取得しています。
取得したリストを用いて、podRecords (Pod/コンテナの情報を持つ内部キャッシュ)のデータを更新しています。

PodRecordには oldcurrent フィールドがあり、どちらもkubecontainer.Pod 型です。kubecontainer.Pod の構造体はこんな感じで、Pod名、Namespace、コンテナのリストなどの情報が含まれています。

relistの続きに戻ります。各Podについて、podRecordsの現在(current)と過去(old)の状態を比較し、適宜イベントを生成します。生成されたイベントは、Map eventsByPodID に追加されていきます。そして、このMap eventsByPodID からPodを1つずつ見ていき、もしイベントがある場合には、PodCacheを更新します。

case g.eventChannel <- events[i]: のところで、PodLyifecycleEventのチャネルに空きがあれば、イベントを送信しているのが分かります。

Pod Lifecycle Eventの生成は、以下のgenerateEvents() が実施します。最初のif文で分かる通り、状態が前回と状態が変わらない場合は、イベントは生成されません。

*GenericPLEG.Start()

Start()メソッドでは、goroutineでrelist() メソッドを実行しています。wait.Until() を使用しているので、relistPeriod (= 1秒) 間隔でループします。

*GenericPLEG.Watch()

Watch() は、PodLifecycleEvent のチャネルを返すメソッドです。kubeletはこのチャネルからイベントを受け取り、 適宜Podの同期処理を行います。

pkg/kubelet/kubelet.go

さらに、kubelet側でPLEGをどのように呼び出しているか見てみます。いくつかPLEG関連っぽい定数があります。relist間隔は1秒、PodLifecycleEvent用のチャネルのキャパシティは1000のようです。

NewMainKubelet() では、必要なすべての内部モジュールとともに新しいKubeletオブジェクトをインスタンス化します。pleg関連の処理はこの辺で、NewGenericPLEG() でGenericPLEGオブジェクトをインスタンス化したあと、kubeletのmain loopのヘルスチェック機構に追加されています。これにより、冒頭で見た PLEG is not healthy: pleg was last seen active XXmYYs ago; threshold is 3m0s といったエラーログが、kubeletのログに表示されていそうです。

kubelet側の Run() メソッドの中で、PLEGも起動(kl.pleg.Start())されているのがわかります。

Run() メソッドの最後に、syncLoop() メソッドも呼び出しています。syncLoop()plegCh := kl.pleg.Watch() のところで、PLEGの更新を読み込むためのチャネルを取得し、チャネル経由でPLEGが生成したイベントを受け取っています。

syncLoopIteration() では、さまざまなチャネルを読み込み、与えられたHandlerへディスパッチするメソッドらしいです。そのうちplegCh チャネルは、Runtime Cacheの更新やPodの同期に使用されると説明が載っています。例えば、PLEGのContainerDied (=最新のコンテナの状態がExitedである)というイベントを受け取った場合、kubeletは cleanUpContainersInPod() により、Pod内の当該コンテナインスタンスを削除します。

pkg/kubelet/runtime.go

klet.runtimeState.addHealthCheck("PLEG", klet.pleg.Healthy) でPLEGのヘルスチェックがkubelet runtimeのヘルスチェックに追加されていた箇所もついでに見てみます。addHealthCheck() で追加された各ヘルスチャック用の関数が、for文で呼び出され、評価されています。エラー発生時には、fmt.Errorf("%s is not healthy: %v", hc.name, err) という出力形式で表示されるらしく、PLEG is not healthy: pleg was last seen active XXmYYs ago; threshold is 3m0s というエラーメッセージはここから来てたんですね〜。

今後のPLEG

KEPの中に、Kubelet Evented PLEG for Better Performance というのを見つけました。気が向いたら読んでみたいと思います。

Kubelet Evented PLEG for Better Performance · Issue #3386 · kubernetes/enhancements
Enhancement Description One-line enhancement description (can be used as a release note): Kubelet Evented PLEG for Better Performance Kubernetes Enhancement Pro...(続く)

さいごに

数多あるkubeletのコンポーネントの1つであるPLEGについて調べてみました。Advent Calendar期間しかブログ更新する気が起きないので、今年も開催もいただき感謝です。

Kubernetesな皆さま、2022年ありがとうございました。よいお年を〜

References

コメント