🦉

k8sのコントローラはどのようにリソースの変更を検知しているのか

2023/05/09に公開

はじめに

本記事はKubernetesのコントローラの解説記事のPart2になる。
解説記事では、下記3パートに分けてKubernetesのコントローラについて解説している。

  1. 各コントローラはどのようにリソースを更新しているのか
  2. どのようにリソースの変更を検知しているのか
  3. どのようにリソースの変更を各コントローラに送信しているのか

Part1では、「各コントローラはどのようにリソースを更新しているのか」を解説している。もしPart1をまだ読んでいない&コントローラの全体像を把握したい方がいれば、そちらを先に読むことをお勧めする。

https://zenn.dev/x_color/articles/cfcb3e46ce0ec5

それでは、「どのようにリソースの変更を検知しているのか」について解説していく。

※解説している処理・コードはKubernetes v1.27.0のものとなる。

リソースの変更検知を行うコンポーネント

イベントに関わる処理を担当するコンポーネントとしてSharedIndexInformerがある。
主に下記を行うコンポーネントとなっている。

  • 各リソースの状態変更検知
  • 状態のキャッシュ
  • 各コントローラへイベントを送信

本記事ではリソースの状態変更検知について解説する。ほかの機能については次回の記事で解説する。

このコンポーネントはkube-controller-managerによって生成・起動される。これは各種コントローラの起動時に埋め込まれる。

SharedIndexInformerの埋め込みから起動までの流れ

SharedIndexInformerFactoryをフィールドに持つControllerContextを生成。
この時点ではSharedIndexInformerFactoryは内部に各種コントローラ用のSharedIndexInformerを持っていない。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/cmd/kube-controller-manager/app/controllermanager.go#L231-L235

次に各種コントローラを起動する。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/cmd/kube-controller-manager/app/controllermanager.go#L236-L240

渡されたControllerContextを用いて、Deployment、ReplicaSet、Pod用のSharedIndexInformerを取得し、Deploymentコントローラに埋め込む。
controllerContext.InformerFactory.Apps().V1().Deployments()はDeployment用のSharedIndexInformerを返す)

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/cmd/kube-controller-manager/app/apps.go#L77-L83

これらはコントローラがイベントハンドラを登録されるときに利用される。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/pkg/controller/deployment/deployment_controller.go#L115-L126

ここでInformer()が呼ばれるとSharedIndexInformerFactoryに対象のSharedIndexInformerが登録される。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/informers/factory.go#L205

ここまでの処理により、各リソースに対応するSharedIndexInformerの登録が完了する。
その後、kube-controller-managerはSharedIndexInformerFactoryを起動する。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/cmd/kube-controller-manager/app/controllermanager.go#L242

この起動処理により、各種Controllerの起動後に登録されたSharedIndexInformerが一括で並列起動される。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/informers/factory.go#L141-L154

下記はDeploymentのSharedIndexInformerの生成処理。Deploymentの状態を監視するための関数を登録し、生成している。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go#L58-L78

これら監視用関数が実際のリソースの変更検知処理にて利用されている。これら関数を用いて変更があった場合は変更イベントが生成されている。

ここまでの処理により生成されたSharedIndexInformerは、起動時に下記コンポーネントを実行する。

  • リソースの変更を検知するためのcontrollerの起動
  • イベントを各種コントローラへ伝搬するためのsharedProcessorの起動

本記事では、controllerの検知処理について解説していく。(sharedProcessorについては次回の記事で解説する)

リソースの変更検知のしくみ

リソースの状態を監視し、変更を検知するのはcontrollerの責務となっている。
controllerは内部で2つのコンポーネントに分かれており、Reflector(r.Run())とcontroller.processLoop()を起動する。

  • Reflector: 対象のリソースの変更を監視し、変更イベントをイベント用のキューへ送信する。
  • processLoop(): Reflectorが送信したイベントに応じて、sharedProcessorへ変更イベントを送信する(次回の記事で解説する)

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/controller.go#L161-L163

Reflectorについて

Reflectorはリソースの変更を監視するコンポーネントであり、起動後に下記処理を行う。

  1. 現状のリソース状態を取得する
  2. キューの中身を最新状態に置き換える
  3. 現状のリソース状態を取得する
  4. 過去の状態と比較し、変更内容を生成する
  5. キューへ変更イベントを送信する
  6. 3~6を繰り返す

Reflectorは起動時にReflector.ListAndWatch()を起動する。これがReflectorの処理を行っている。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L286-L294

このメソッドは、まず始めにReflector.watchList(), Reflector.list()を呼び出すことにより、現在のリソース状態を取得し、キューの中身を最新の状態に置き換える。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L328-L350

たとえば、Reflector.watchList()listerWatcher.Watch()を呼び出し、リソース状態の監視を開始する。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L641-L652

このWatch()は事前に登録されたWatchFunc()を呼び出している。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/informers/apps/v1/deployment.go#L67-L72

変更監視結果はwatchHandler()で取得され、その変更タイプに応じたイベントをtemporaryStoreへ送信する。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L717-L770

ここで利用されているtemporaryStoreは名前の通り一時的なリソースであり、下記のキュー(store)に入っている状態を最新の状態で置き換えるのに利用されている。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L671-L673

ここまでの処理により、起動時のリソース状態の取得とキューの初期化が完了する。
なお、ここで利用されているキューはSharedIndexInformerの起動時に登録されている

その後はReflector.ListAndWatch()Reflector.watch()を起動し、リソースの状態監視を開始する。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L356

事前にReflector.watchList()を実行していた場合は、そこで起動したlisterWatcher.Watch()の監視処理を渡して利用している(wが監視処理)。

変更があれば、Reflector.watchList()と同様にwatchHandler()がその情報を取得し、適切なイベントに変換してキューへ送信する。ここでは一時的なリソースは利用せずに、直接キューにイベントを送信している。

https://github.com/kubernetes/kubernetes/blob/1b4df30b3cdfeaba6024e81e559a6cd09a089d65/staging/src/k8s.io/client-go/tools/cache/reflector.go#L431

ここまでの処理によりリソースの状態が監視され、変更があった場合にはイベントが生成される。そのイベントはキューとsharedProcessorを経由して各コントローラへ送信され、リソース更新に利用される。このイベント送信周りについては次回の記事で解説する。

おわりに

本記事では、リソースの変更検知処理について解説した。
次回の記事では、「どのようにリソースの変更を各コントローラに送信しているのか」について解説する。

Discussion