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