はじめに
以前Cloud Controller Manager(CCM)について簡単にまとめた記事を書きましたが、改めてもう少しちゃんとKubernetesのServiceControllerのコードを読んでみたので、自分へのメモとしてServiceControllerの実装についてまとめてみました。
読んでみたKubernetesのコードのバージョンはv1.17のコードになります。
また前提知識としては
- ゼロから始めるKubernetes Controller / Under the Kubernetes Controller で解説されている内容がなんとなく理解できて
- controller-runtime/kubebuilderあたりでKubernetesのコントローラーの実装サンプル(hello worldしたりするのなど)を触ったことがある
- また公式ドキュメントやCCMについての記事を読むなどしてCloud Controller Managerについてなんとなく知っている
あたりの知識があれば多分問題ない気がします。
ServiceControllerとは?
CCMについての記事にも書いた通りServiceControllerは
- Cloud Controller Managerから起動されるコントローラーの1つで
- Serviceリソースのtype: Loadbalanacerのためのコントローラーで、type: LoadbalancerなServiceリソースの作成~削除などのイベントに応じてクラウドのL4ロードバランサーを構築~削除するコントローラーになります
- (ちなみにtype: Loadbalancer以外のServiceリソースがどのコントローラーによって処理されるかはまだ調べてないです。軽く見てた感じkube-proxyあたり?)
のような感じのものになります。
Cloud Controller Managerの実装
Cloud Controller Managerのコードは
func main() { rand.Seed(time.Now().UnixNano())
command := app.NewCloudControllerManagerCommand() logs.InitLogs()
defer logs.FlushLogs() if err := command.Execute(); err != nil {
os.Exit(1)
}
}
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/controller-manager.go#L37-L52
のようになっており、cobraというライブラリによってCLIが実装されています。
これはCloud Controller Managerに限らずKube Controller ManagerなどKubernetesの様々なコマンドラインが同様にcobraによって実装されていたはずです。
上記によって起動する処理の実態は
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L117-L220
にまとめられておりステップで分けると
- https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L122-L169 でCloud Controller Managerの初期化処理などを行い
- https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L171-L175 でCloud Controller Managerが起動する各種コントローラーの起動処理を行う関数定義を行い
- https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L177-L218 でleaderelectionを利用したコントローラーの起動(leaderelectionを利用していなければそのまま上記関数を呼び出します)
のようになっています。
更に各種コントローラーの起動処理は
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L222-L261
にまとまっており
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L238
でコントローラーの起動を行っています。
ここで呼び出される initFunc
は
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/controllermanager.go#L277-L286
で登録されている startXXXController
のことで、ServiceControllerの起動は startServiceController
となります。
ServiceControllerの起動処理
startServiceControllerの中身は
func startServiceController(ctx *cloudcontrollerconfig.CompletedConfig, cloud cloudprovider.Interface, stopCh <-chan struct{}) (http.Handler, bool, error) {
// Start the service controller
serviceController, err := servicecontroller.New(
cloud,
ctx.ClientBuilder.ClientOrDie("service-controller"),
ctx.SharedInformers.Core().V1().Services(),
ctx.SharedInformers.Core().V1().Nodes(),
ctx.ComponentConfig.KubeCloudShared.ClusterName,
) if err != nil {
// This error shouldn't fail. It lives like this as a legacy.
klog.Errorf("Failed to start service controller: %v", err)
return nil, false, nil
} go serviceController.Run(stopCh, int(ctx.ComponentConfig.ServiceController.ConcurrentServiceSyncs)) return nil, true, nil
}
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/core.go#L79-L97
のようになっており、ここでServiceControllerの初期化~起動を行っています。
上記のserviceController.Run
の中身は
func (s *Controller) Run(stopCh <-chan struct{}, workers int) {
defer runtime.HandleCrash()
defer s.queue.ShutDown() klog.Info("Starting service controller")
defer klog.Info("Shutting down service controller") if !cache.WaitForNamedCacheSync("service", stopCh, s.serviceListerSynced, s.nodeListerSynced) {
return
} for i := 0; i < workers; i++ {
go wait.Until(s.worker, time.Second, stopCh)
} go wait.Until(s.nodeSyncLoop, nodeSyncPeriod, stopCh) <-stopCh
}
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L203-L221
のようになっており、この中で重要なのが
- s.worker
- s.nodeSyncLoop
の2つのメソッドです。
ServiceControllerのメイン処理
ServiceControllerのメイン処理は上記に挙げた
- s.worker
- s.nodeSyncLoop
の2つのメソッドになり、これらが wait.Until
メソッドにより定期的に実行されます。
s.workerの処理
s.workerの処理は
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L225-L228
となっており processNextWorkItem
メソッドがfalseを返すまで processNextWorkItem
メソッドを呼び出し続けます。processNextWorkItem
メソッドはキューがない場合にはfalseを返すので、実態としては溜まったキューを全て処理するまで processNextWorkItem
メソッドを呼び出し続けるという挙動になりそうです。
processNextWorkItem
メソッドの中身は
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L230-L246
のようになっており、KubernetesのControllerパターンにより実装されています。
KubernetesのControllerパターンに関してのちゃんとした説明は
あたりを読むのが良いかと思いますが、雑に説明してしまうと「Kubernetesの各種リソースを監視し、リソースの変更に応じて(宣言的された)あるべき状態になるように調整を行うアプリケーション」で、コントローラーは
- 調整処理を行うためのキューにジョブを貯める処理を行うイベントハンドラーと
- 定期的にキューに溜まったイベントを取り出してあるべき状態になるよう調整を行う調整処理
の2つの処理を行うものになります。
ServiceControllerの場合
- https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L152-L175 でServiceリソースのイベントハンドリングを設定していて
- https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L731-L758 の
syncService
メソッドでキューから渡されたリソースに対する調整処理を行っている
というふうになっています。
そして調整処理の中の
- https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L740
で処理対象となるServiceリソースの情報を取り出しており - https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L745-L755 の中でリソースの情報に基づくロードバランサーの作成/更新/削除を行う
- https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L833-L845 でロードバランサーの作成/更新/削除後にServiceリソースのステータス状態に変更があれば更新を行う
という感じになっています。
s.nodeSyncLoop
s.nodeSyncLoopの処理はs.workerのControllerパターンとは異なっており
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L641-L668
のように単純に wait.Until
を利用して定期実行を行うようになっています。
処理の内容としては
- https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L644-L648 でnode情報の取得を行い
- 変更があれば https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L659-L667 でnode情報などのServiceControllerの持つプロパティのアップデートを行いつつ、クラスタの持つ全てのtype: LoadbalancerなServiceリソースに紐づくロードバランサーのアップデートを行う
- (また、node一覧に変化がなければ https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L649-L654 で更新に失敗しているロードバランサーの更新処理を行う)
という感じになっています。
またnode情報の更新を目的としているためか、s.workerとは異なり各Serviceリソースのステータス更新は行われないようでした。
Cloud Provider側で呼び出されるメソッド
Cloud Providerのロードバランサーのインターフェイスで定義されているメソッドは
- GetLoadBalancer
- GetLoadBalancerName
- EnsureLoadBalancer
- UpdateLoadBalancer
- EnsureLoadBalancerDeleted
の5つとなりますが、上記のs.worker/s.nodeSyncLoopの処理を読む限りGetLoadBalancerNameメソッドは呼び出されていないようなので、現在は使われていないメソッドなのかもしれません。
またs.worker側では
- EnsureLoadBalancerによってロードバランサーの作成/更新が
- EnsureLoadBalancerDeletedによってロードバランサーの削除が
が行われているようで
s.nodeSyncLoop側では
- UpdateLoadBalancerによってロードバランサーの更新が
が行われているようでした。
GetLoadBalancerによるロードバランサーの取得処理はどちらの処理でも行われるようです。
ServiceControllerで利用/監視されるKubernetesリソース
上記のs.worker/s.nodeSyncLoopで見たように、監視されるのはServiceリソースのみでした。
ただしロードバランサーの設定にはNodeリソースも必要なため、Nodeリソースの取得も行われるようです。
上記のリソースの利用/監視には
https://github.com/kubernetes/kubernetes/blob/release-1.17/cmd/cloud-controller-manager/app/core.go#L84-L85
のようにCloud Controller Managerが持つShared Informerが使われているようでした。
finalizerの使い方について
Kubernetesのリソースにはfinalizerというリソースの削除前にフック処理を挟む機能があるのですが、ServiceControllerでもこのfinalizerを利用していました。
finalizerについては
- https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#finalizers
- https://book.kubebuilder.io/reference/using-finalizers.html
あたりを読むとわかると思いますが、一言で言ってしまうとfinalizerに設定したmetadataがある限りリソースの削除をさせないようにする機能のようです。
ServiceControllerの場合ロードバランサーの削除を確実に行わせるためにロードバランサー作成時にfinalizerを
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L338-L342
のように設定しているようで、削除が完了するのを確認してから
https://github.com/kubernetes/kubernetes/blob/release-1.17/pkg/controller/service/controller.go#L327-L331
でfinalizerの設定を削除するようになっていました。
まとめ
前回CCMの記事を書いた時よりもある程度ちゃんServiceControllerのコードを読んだので、ServiceControllerの処理の全体像がなんとなくわかってきました。
またあまり詳しくないfinalizerについての利用例が見れたので良かったです。
一応最後にまとめ用に全体像の雑な図をつくってみました。
追記
- 2019/02/19: workerのループに関する説明が間違ってたので修正と、ServiceController Overviewの図に一部文言追加を行いました