KubernetesのServiceControllerの実装

bells17
17 min readFeb 18, 2020

--

はじめに

以前Cloud Controller Manager(CCM)について簡単にまとめた記事を書きましたが、改めてもう少しちゃんとKubernetesのServiceControllerのコードを読んでみたので、自分へのメモとしてServiceControllerの実装についてまとめてみました。

読んでみたKubernetesのコードのバージョンはv1.17のコードになります。

また前提知識としては

あたりの知識があれば多分問題ない気がします。

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#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の場合

というふうになっています。

そして調整処理の中の

という感じになっています。

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 を利用して定期実行を行うようになっています。

処理の内容としては

という感じになっています。

また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については

あたりを読むとわかると思いますが、一言で言ってしまうと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についての利用例が見れたので良かったです。

一応最後にまとめ用に全体像の雑な図をつくってみました。

ServiceController Overview

追記

  • 2019/02/19: workerのループに関する説明が間違ってたので修正と、ServiceController Overviewの図に一部文言追加を行いました

--

--

No responses yet