kubebuilderを利用して簡素なk8sのControllerを作ってみる
記事一覧はこちら
背景・モチベーション
Controllerを書きたい!とはずっと思っていましたが、なかなか一歩を踏み出せませんでした。 しかしながら、external-dnsのコードを読んでふんわり分かってきたので書き始めようと思います。
肝心なControllerの中身ですが、検証環境のコスト削減に役立つものにしたいと思います。Deploymentリソースのアノテーションに、起動する時間と落とす時間を宣言しておくと、そのとおりにpodをスケールアウト/インしてくれるものを書こうと思います。
Controllerを作るときの指針
参考文献
つくって学ぶKubebuilder (母国語かつ無料で読める質とは思えないくらいにとても良いドキュメント様です)
Controllerの作り方について
external-dns や Bitnamiさん の記事を読んでいると client-go
のライブラリをそのまま利用してControllerが実装されています。しかしながら、Bitnamiさんの記事は少し古く2017年時点でのものでした。
しかし近年は、 kubebuilder
というフレームワークを利用することが多いようです。
aws-load-balaner-controller
も kubebuilder
をベースに作成されていました。
Controllerの仕事
この部分は、k8sのcommunityのリポジトリ や 作って学ぶKuberbuilderさん 、deeeetさんのブログ記事 で触れられています。
Controllerの仕事は、任意のオブジェクトについて、現在の状態が望ましい状態と一致することを保証することです。各Controllerはrootとなる一つのKindにフォーカスしますが、他のkindと相互に作用することもあります。
これらの処理を reconciling
と言うそうな。
(…そう思うと今回実装しているのって厳密にControllerと言えるものなのだろうか)
// reconciling loop for { desired := getDesiredState() current := getCurrentState() makeChanges(desired, current) }
kubebuilderを利用して雛形を作り不要なものを捨てる
kubebuilderは、CRDやAdmission Webhookを作成することなども想定してコードを生成しています。ただ、今回の自分の用途の場合にはCRDやAdmission Webhookは必要としません。主に検証環境で利用する用途のため、冗長性を確保するためのLeader Electionも今回は利用しません。 そのため不要な生成済コードは取り除くことにします。
生成されたコードの役割はパッと見ではわかりません。公式のドキュメントで一箇所にまとめてある記述が見つからなかったので、作って学ぶKunebuilderさんのこのページを参考にしました。
kubebuilder init --domain ars.44smkn.github.io kubebuilder create api --group apps --version v1 --kind Deployment --namespaced=false --resource=false --controller=true
雛形を作ったので実装していきます。
Controllerの実装
実装の大枠
Controllerを管理してくれる部分は、前述の kubebuilder
コマンドによって既に作成済です。
controller-runtime によって提供された Manager が、全てのControllerの実行、共有キャッシュの設定、APIサーバーへのクライアントの設定を管理してくれます。
生成された main.go
内にてManagerのインスタンス化と Reconciler
のセットアップが行われています。
func main() { // ... mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, Port: 9443, LeaderElection: false, LeaderElectionID: "08a80630.ars.44smkn.github.io", }) // ... if err = (&controllers.DeploymentReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("Deployment"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } // ... }
では、どこにコードを書いていくことになるかと言うと、 controller
ディレクトリ配下の xxxxx_controller.go
の Reconcile
メソッドの中です。
下記のようにコメントしてあるので分かりやすいです。
// your logic here
Reconcile
が呼び出されるのは、下記のタイミングだそうです。
こちら より引用させていただきました。
- コントローラが扱うリソースが作成、更新、削除されたとき
- Reconcileに失敗してリクエストが再度キューに積まれたとき
- コントローラの起動時
- 外部イベントが発生したとき
- キャッシュを再同期するとき(デフォルトでは10時間に1回)
このタイミングで自分の実装ではDeploymentのアノテーションを読み取り、replicas数を操作する処理をcronに登録します。
func (r *DeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { // ... // Reconcilerに生えている Getメソッドを利用してイベントがあったリソースを取得する var deployment appsv1.Deployment err := r.Get(ctx, req.NamespacedName, &deployment) if err != nil { return ctrl.Result{}, err } // ...(アノテーションのパースなどをする) return ctrl.Result{}, nil }
動作確認
ローカルでは Kind の環境を利用して動作確認しました。
その後にRBACのマニフェスト等を整備して、Katacodaにデプロイして動くことを確認できました!気持ちいい!
$ kubectl apply -f mf.yaml serviceaccount/app-regulary-scaler created clusterrole.rbac.authorization.k8s.io/app-regulary-scaler created clusterrolebinding.rbac.authorization.k8s.io/app-regulary-scaler created deployment.apps/controller-manager created $ kubectl get po NAME READY STATUS RESTARTS AGE controller-manager-75ccf997dd-jl426 1/1 Running 0 63s # 1分後にreplicas数が2になる、replicas数1を宣言しているnginxのマニフェスト $ kubectl apply -f nginx.yaml deployment.apps/nginx-deployment created $ kubectl get po NAME READY STATUS RESTARTS AGE controller-manager-75ccf997dd-jl426 1/1 Running 0 4m34s nginx-deployment-6b474476c4-nrjsm 1/1 Running 0 3m15s nginx-deployment-6b474476c4-s55xm 1/1 Running 0 2m9
README.mdやリリースをちゃんと整備して仕事の環境とかで使えると便利だなあと思いました!以上!