Kubernetes deep dive - Nodeの登録と削除
背景
Kubernetesを構成する各種コンポーネントの中でも、Worker Nodeに常駐しPodを作成したりする仕事などを担っているKubeletですが、kubelet get nodes
をしたときにNodeが表示されるのもKubeletが人知れず仕事をしてくれているおかげです。
今回はKubeletが起動したときにどのようにNodeを登録しているのか、Nodeが終了するときはどのようにNodeを削除しているのかKubernetesのコードから追っていきたいと思います。
今回はKubernetes v1.25.5のコードを対象とし、AWS上のLinuxで動作することを前提としています。
Nodeの登録
Kubeletのエントリポイントはcmd/kubelet/kubelet.go
です。
35行目で生成した*cobra.Command
を引数に取りcli.Run
で起動しています。
このcommand
を追ってみると、cli.Run
したときに呼ばれるRunE
関数にたどり着きます。
cli.Run
したとき内部的には(*cobra.Command).Execute
が呼ばれており、cobra.Command
のRunE
フィールドに入っている関数を呼び出しています。
ここからRun
, run
, RunKubelet
と飛んで、
RunKubelet
関数の中のcreateAndInitKubelet
でKubeletの実体を生成しています。
createAndInitKubelet
で生成したKubeletの実体を持って、startKubelet
関数を呼び出しKubeletの開始処理へと入っていきます。
ここまではcmd/kubelet
がメインでしたが、この(*kubelet.Kubelet).Run
からpkg/kubelet
側がメインになっていきます。
(*Kubelet).Run
です。おそらくここが実質Kubeletのエントリポイントでしょう。
(*Kubelet).Run
の中で呼ばれる(*Kubelet).fastStatusUpdateOnce
はKubeletの起動時にのみ呼ばれる関数で、pod CIDR, ランタイムのステータス、Nodeのステータスをなるべく早く更新するために置かれているようです。
(*Kubelet).fastStatusUpdateOnce
の中で呼ばれているSyncNodeStatus
を見てみると、(*Kubelet).registerWithAPIServer
といういかにもNodeを登録していそうな関数が見つかります。
(*Kubelet).initialNode
で、登録するNodeのデータであるv1.Node
オブジェクトを生成し、
(*Kubelet).tryRegisterWithAPIServer
でAPI serverにNodeの登録リクエストをしています。
このリクエストが成功すればNodeが登録され、kubectl get nodes
で見えるようになるということになります。
Nodeの削除
ここまでNodeの登録の流れを追ってきましたが、Nodeの削除はどのように行っているのでしょうか。
もし登録時のようにKubeletが自分がいるNodeの削除リクエストをしているのだとすると、KubeletやNodeが異常終了したり、削除リクエストをしたときにAPIサーバーが不調なときにいつまでもNodeオブジェクトが残ったままになってしまいそうです。
そうなるとkubectl get nodes
をしたときに古いNodeがいつまでも残ったままになってしまい見にくそうですね。
Kubernetesの宣言型APIなどに見られる冪等性の観点からしてもそのアプローチはあまりあっていないように見えます。
それではNodeの削除の流れを追ってみましょう。
先に種明かしをしてしまうと、Node削除時に仕事をしているのはkube-controller-manager
, cloud-controller-manager
です。
まずはエントリポイントから見ていきます。
Kubeletのときと同じくcobraを使っているので、RunE
関数までスキップします。
RunE
の中のRun
関数を辿ってみると、run
関数の第3引数に渡しているNewControllerInitializers
が見つかります。
このNewControllerInitializers
は各種リソースのコントローラーの開始処理を持っていて、Nodeに関するコントローラーはstartNodeLifecycleController
, startCloudNodeLifecycleController
で定義されています。
このうち、NodeLifecycleController
はHealth status, Taintの管理などKubernetesレイヤーのNodeのライフサイクル、CloudNodeLifecycleController
は各種クラウドプロバイダー(AWS, GCPなど)との繋ぎこみといったKubernetesを構成するのに必要な比較的下側のNodeのライフサイクルを管理している印象です。
ということでNodeの削除は下側の世界にかかわるのでstartCloudNodeLifecycleController
を追っていきます。
startCloudNodeLifecycleController
から(*cloudnodelifecyclecontroller.CloudNodeLifecycleController).Run
に飛ぶと、
(*CloudNodeLifecycleController).MonitorNodes
がCloudNodeLifecycleController.nodeMonitorPeriod
で設定された間隔で実行されるようになっていることがわかります。
ちなみにデフォルトだと5秒ごとのようです。
(*CloudNodeLifecycleController).MonitorNodes
の中でReady=TrueのNodeはスキップしつつ、ensureNodeExistsByProviderID
でNodeオブジェクトに書かれているproviderID
をもとに各種クラウドプロバイダーにインスタンスがまだ存在しているかを問い合わせます。
問い合わせの結果、インスタンスがすでに存在していないことがわかったときNodeオブジェクトを削除しています。
この削除リクエストが成功すればNodeが削除され、kubectl get nodes
の結果から外されることになります。
まとめ
Nodeの登録と削除がどのように行われるかを見てきました。
登録時はNodeにいるKubeletがNodeを登録していて、削除時はControl Planeにいるcloud-controller-managerのCloudNodeLifecycleControllerがReadyではないNodeがまだ存在しているかを照会し、削除済であればKubernetesの世界からも削除を行うという仕組みになっていることがわかりました。
途中で端折った箇所もありますが、大まかな流れは追えているかと思います。
そして最後に、この記事を書くにあたって改めて公式のドキュメントに立ち返ってみたところちゃんと書いてありました。
この2つの違いがわかっていればもう少し早く理解できたのに……(Node削除時の処理を探してkube-controller-managerの周りをずっとぐるぐる回っていた)
kube-controller-manager
コントロールプレーン上で動作するコンポーネントで、複数のコントローラープロセスを実行します。
(中略)
ノードコントローラー:ノードがダウンした場合の通知と対応を担当します。
cloud-controller-manager
クラウド特有の制御ロジックを組み込むKubernetesのcontrol planeコンポーネントです。クラウドコントロールマネージャーは、クラスターをクラウドプロバイダーAPIをリンクし、クラスタのみで相互作用するコンポーネントからクラウドプラットフォームで相互作用するコンポーネントを分離します。
(中略)
ノードコントローラー:ノードが応答を停止した後、クラウドで削除されたかどうかを判断するため、クラウドプロバイダーをチェックします。
引用:https://kubernetes.io/ja/docs/concepts/overview/components/
kube-controller-managerのNode ControllerがNodeLifecycleController
、cloud-controller-managerのNode ControllerがCloudNodeLifecycleController
に該当します。
また次の機会があれば、先にドキュメントを読むことを忘れないようにしたいと思います。
Discussion