[Kubernetes] sample-controllerを細かく解説する
はじめに
kubernetesのsample-controllerはご存知でしょうか?
もし知らない方のために説明しますと、kubernetesのdeploymentリソースをCustomResource経由で管理し、作成/更新/削除処理を自動化したcontrollerです。これからCustomControllerを開発しようとしている方の入門編としてのうってつけのsampleとなっています。
また、kubebuilderやoperator-SDKなどのSDKを使用せずclient-goとcode-generatorで書かれているので、Kubernetesのコードを読んでみようと思う方のコードリーディングの練習にもなります。
CustomController実装初心者がまずは読むべき本であり、私のバイブルである「実践入門 Kubernetesカスタムコントローラへの道」にて、既にsample-controllerの読み方は解説されておりますが、書面のページの都合上は端折られている箇所があります。
そこで、改めてコードリーディングし直したので、ステップ毎に細かく解説を入れてみました。
sample-controllerの動作
sample-controllerでは、CustomResourceDefinitionとCustomResourceをdeployすることで、CustomResourceに定義したdeploymentのnameとreplicasを使って、deploymentを自動作成します。
また、CustomResourceのreplicasを変更すると、自動でdeploymentも更新され、CustomResourceを削除することでdeploymentも削除される動作となります。
CustomResource定義例
以下のCustomResourceをdeployすることで、上記動作を実現することができます。
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
name: example-foo
spec:
deploymentName: example-foo
replicas: 1
Let’s コードリーディング
api
CustomResource定義には以下のspecとstatusがあります。
ユーザでは、specを指定しますが、statusはContollerによって自動で更新されます。
main関数
sample-controllerはmain.goのmain関数から読んでいきます。まず、はじめにシャットダウンシグナルのsetupを行います。
SetupSignalHandler関数
シャットダウンシグナルのsetupを行う、SetupSignalHandler関数を解説します。
goroutineでSignal受信を待ち、SIGINTまたはSIGTERMが発生した場合、channelをcloseします。
Signal定義は以下の通りです。
以上で、SetupSignalHandler関数の解説を終了します。
main関数の解説に戻ります。
次に、clientset生成します。
kubeClientが標準Resource(deployment)用、
exampleClientがCustomResource用です。
clientsetの作成が完了すると、InformerFactory生成されます。
kubeInformerFactoryが標準Resource(deployment)用、
exampleClientがCustomResource用です。
clientsetとInformerFactoryを使用し、controllerの生成を行います。
Controller構造体のメンバは以下の通りです。
NewController関数
長いため、ステップ毎に解説していきます。
まず、schemeの生成します。
次にEvent recorderを生成します。
Controller構造体に、main関数で作成したclientsetやinformerなどの必要コンポーネントを代入します。
ちなみに、<InformerFactory>.Informer関数を実行することで、informerのメンバに値を代入しています。
informer追加の流れ
Informer関数でf.factory.InformerFor関数が呼ばれます。
InformerFor関数にはdefaultInformer関数の定義も渡されているので、newFunc(f.client, resyncPeriod)
の行でdefaultInformer関数が呼ばれます。
defaultInformer関数から、NewFilteredFooInformer関数が呼ばれます。
NewFilteredFooInformer関数では、cache.NewSharedIndexInformer関数が呼ばれ、cache.SharedIndexInformer interfaceにcache.sharedIndexInformer構造体が代入されます。
cache.NewSharedIndexInformer関数定義
informerとなるcache.SharedInformerの定義
Eventごとに実行される関数を定義します。
fooInformerではcontroller.enqueueFoo関数が、deploymentInformerではcontroller.handleObject関数が実行されます。
各Event毎に実行される関数は後述します。
NewController関数内で、Event毎に実行される関数は以下の通りです。
enqueueFoo関数
cache.MetaNamespaceKeyFunc関数が呼ばれ、"namespace/name"形式でobjectのkeyを生成し、workqueueに追加します。
handleObject関数
deploymentのOwner Referenceを取得し、Owner名がCustomResourceの名前と同じであれば、上記enqueueFooを呼び出して、workqueueに追加します。
以上で、NewController関数の解説を終了します。
main関数の解説に戻ります。
標準ResourceとCustomResourceのinformerを開始します。
最後にcontrollerを開始します。
第一引数の2はWorker数です。
Reconcile処理
Reconcile処理ではLoopを使い、本来あるべき姿を保とうとします。
sample-controllerの場合、CustomResourceに定義されたdeploymentのnameとreplicasを使って、以下の処理を自動化します。
- deployment自動作成
- deploymentが削除されたら自動再作成
- CustomResourceの定義が変更されたら、deploymentの更新
- deploymentのreplicasを使って、CustomResourceのstatus更新
それではReconcile処理の解説をしていきます。
Run関数
Run関数では、In-Memory-Cacheを同期させて、Run関数の引数に指定された数だけWorkerを実行します。
cache.wait.Until関数で無限ループを実行することでReconcile Loopを実現しています。
Reconcile Loopの流れ
以下の流れで最終的にBackoffUntil関数が呼び出されます。
BackoffUntil関数には、controller.runWorker関数も引数で渡されているため、f()の箇所で実行され、SIGINTもしくはSIGTERMが発生するまで継続されます。
runWorker関数
runWorker関数はLoopでprocessNextWorkItem関数を呼び出すのみとなっており、戻り値がtrueの間継続されます。
processNextWorkItem関数
まず、workqueue.Get関数にてReconcileの処理対象itemを取得します。
もしqueueが空なら、即座にfalseを返し、processNextWorkItem関数を終了します。
falseをrunWorker関数に返すことでrunWorker関数を終了させます。
つまり、processNextWorkItem関数はqueueが空になるまで、runWorker関数によって実行されます。
queueから取り出したitemの処理は以下のように無名関数で実行されます。
上記関数をステップ毎に順を追って解説します。
processNextWorkItem関数処理終了後にはworkqueue.Done関数が完了させます。
もし、workqueue.Forget関数が実行されていれば、workqueueからitemを取り除かれます。
queueに入っているitemはリソースの"namespace/name"形式のstringで入っています。
まず、型アサーションでkeyを取り出します。もしstringでない場合は、workqueue.Forget関数がitemの追跡を停止します。その後、deferで前述したworkqueue.Done関数が実行され、workqueueからitemを取り除かれます。
取り出したkeyを引数にsyncHandle関数でdeploymentを作成します。
もしエラーが返ってきたら、AddRateLimited関数で遅延してrequeueし、再度処理対象とします。
この時、workqueue.Done関数では、itemは取り除かれません。
syncHandle関数が正常終了した場合は、workqueue.Forget関数でitemの追跡を停止し、deferでworkqueueからitemを取り除きます。
以上無名関数の解説でした。
processNextWorkItem関数の解説に戻ります。
無名関数でエラーが発生した場合は、エラーを出力しtrueを返し、エラーがない場合はtrueを返すのみとします。いずれにしても、Reconcile Loopは継続されます。
Reconcile Loop中はEvent発生→enqueueFoo関数でitemが入り続け、Loopが継続されます。
次にReconcileの中核であり、リソース作成を行うsyncHandler関数の解説を行います。
syncHandler関数
まず、keyは"namespace/name"の形式なので、/を区切り文字としてsplitします。
もしerrorが発生すれば、nilを返し、processNextWorkItem関数にworkqueueからitemを取り除きます。
次に、listerを使用し、CustomResource objectをIn-Memory-Cacheから取得します。
errors.IsNotFound(err)はerrがNot Foundエラーか否かを判定する関数で、もしNot Foundエラーであればtrueを返し、そうでなければfalseを返します。
以下のコードでは、取得に失敗した場合かつ、Not Foundエラーであればnilをreturnします。
また、Not Foundエラーでなく、単純に取得に失敗しているのであれば、errorをreturnします。
この処理にて、もしCustomResource objectが存在いるにも関わらずエラーが返る場合、processNextWorkItem関数にerrorを返し、workqueue.AddRateLimited関数で遅延requeue後、再度処理を行うことが可能であるためです。
もしerrors.IsNotFound(err)での判定がなければ、CustomResource objectが存在しない場合、requeueが繰り返されます。
CustomResource定義の.Spec.DeploymentNameからdeploymentの名前を生成し、もし名前が生成できない場合は、nilをreturnします。nilをreturnしているので、requeueせずに、itemがworkqueueから削除されます。
deployment objectをIn-Memory-Cacheから取得し、Not Foundエラーが返るのであれば、リソースを作成します。存在しているがobjectの取得ができない、もしくはリソースの作成に失敗する場合はrequeue処理に戻し、再度GetとCreateを試みます。
deploymentリソースの定義設定は、newDeployment関数で行うため、後ほど後述します。
この処理ではdeploymentのReplicasの更新を行います。
もし、CustomResourceの.Spec.Replicasと、deploymentの.Spec.Replicasが異なる場合は、CustomResourceの.Spec.Replicasを用いてdeploymentを更新します。
もし更新に失敗した場合は遅延requeueし、再度更新処理を行います。
updateFooStatus関数にて、CustomResourceのstatusを更新します。
もし更新に失敗する場合は遅延requeueし、再度更新処理を行います。
最後にEventを出力し、nilをreturnします。
これにてworkqueueがForget→Doneで完了します。
以上で、Reconcile処理を行う関数の解説を終了します。
最後に、deploymentの定義作成とCustomResourceのstatus更新を行う関数の解説を行います。
newDeployment関数
CustomResourceに定義されているdeployment定義より、deployment定義が作成されます。
また、”OwnerReferences: []metav1.OwnerReference”の箇所でOwnerReferenceが追加されており、CustomResource削除時にGCによりdeploymetも削除されるようになっています。
updateFooStatus関数
CustomResourceの.Status.AvailableReplicasと、deploymentの.Status.AvailableReplicasが異なる場合は、CustomResourceの.Status.AvailableReplicas更新します。
最後に
sample-controllerを細かく掘り下げてみました。
「実践入門 Kubernetesカスタムコントローラへの道」のsample-controllerの章の読書時や、これからsample-controllerをコードリーディングしようとしている方に、本記事が細かい箇所の手助けとなれば幸いです。
Discussion