takutakahashi.dev

Kubernetes Custom Controller を手抜きで作る技術

このポストは GMOペパボエンジニア Advent Calendar 2020 の11日目の記事です。

最近業務や個人プロジェクトで Kubernetes Controller を書くことがたまにあり、
段々どう書いていけばいいかわかってきたので、書いていきます。
あまり使いこなせていない機能もあるため、自分の理解ベースでお話します。

Custom Controller とは?

Kubernetes のリソースの変化に応じて、ワークロードに変更を適用したり、
その他任意の処理を実行するものを指します。
通常、Custom Resource Definition と同時に作成されることが多いです。
istio を試したことがある方は、kubectl get ingressgateway などを実行したことがあるでしょう。
ingressgateway に該当するものが Custom Resource と呼ばれ、
それらのリソースの変更をハンドリングするコンポーネントを Custom Controller と呼ぶことが多いです。

Custom Controller がどのように動くか?

完全に説明してくださっているページを見つけたためデリゲートします。

https://zoetrope.github.io/kubebuilder-training/introduction/basics.html

Custom Controller って難しいんでしょ?

Custom Controller を作るのは難しい、高度な技術を要するものでしょ?と思われる方は多いかもしれません。
端的に言うと、 全部まともに作ろうとするととても難しいと言えます。

難しい点

以下、Custom Controller を実装する上で躓きがちなポイントを書いてみます。

難しい点1. 何から始めればいいかわからない

まず始めるにはどうすりゃいいの!となります。ぐぬぬ。

難しい点2. CRD の設計が難しい

とある動作 A を実現するためのリソースである hoge リソースを定義するとして、
hoge.spec 以下にどのような情報を入れることがいいか、hoge.status をどのように定義するかなど、
CRD の設計部分がとても大変です。ここを考えるのが難しくて諦めてしまうことが多いです。

難しい点3. Reconcilation が難しい

一番難しいです。
Reconcilation というのは、リスク管理にも同じ用語がありますが
要は変更前後のデータの差分を突合して、差分を対応する動作を指します。
例えば、deployment リソースの replicas を 1 から 2 に増やした際に、Reconcilation が実行され、Pod の個数が2個になる、という動作です。
この挙動は、複雑性が高まり非常に実装が難しい部分になります。
いくら実装してもバグが潰せないし、実際のワークロードと密に結合すると単体テストも書きづらいし。。。みたいな状態になります。
段々実用に耐えない Controller ができてしまって、フェードアウトしてしまう。ということになりがちです。

解決方法

以下、できるだけ Custom Controller に慣れていくために難しい部分を極力簡潔にしていきます。

解決法1. kubebuilder などのコードジェネレータを使う

一昔前にコードジェネレータを使わずに CRD を実装していた人たちを尊敬します。。。
今は、 kubebuilder や operator-sdk など、Custom Controller プロジェクトを簡単に開始できるツールが発達しています。
僕はもっぱら kubebuilder を利用して開発をしています。

以下のチュートリアルを実施すると、kubebuilder の使い方はわかるかと思います。

https://book.kubebuilder.io/cronjob-tutorial/cronjob-tutorial.html

解決方法2. CRD を作らない

CRD を作ろうと思うと大変で挫折するため、一旦 CRD を作ることをあきらめましょう。
実は、kubebuilder には、 deploymentservice などのコアリソースの Custom Controller を作る機能が存在します。

https://book-v1.book.kubebuilder.io/beyond_basics/controllers_for_core_resources.html

以下のように kubebuilder を実行します。

kubebuilder create api --group apps --version v1 --kind Deployment
kubebuilder create api --group core --version v1 --kind Service

kubebuilder の実行時に「リソースを作成するか?」と聞かれるため、 No と答えます。
すると、 controller の雛形だけ作成され、resource は作成されません。 これで、 deploymentservice が作成/変更/削除されたときに発火する controller が作成できます!やったね!

解決方法3.1. アップデートの処理を実装しない

Reconcilation を実装するのが大変だという話を書きました。
何が大変なのかと言うと、リソースの状態に合わせて他のリソースやなんやらをアップデートしていくことが大変なので、
他のリソースをアップデートしない Controller を書きましょう。 Read Only Controller です。

例えば、以下では Deployment のロールアウトを Slack 通知する Controller を実装しようとしています。(まだ実装していない)

https://github.com/takutakahashi/rollout-notifier

この場合、Deployment リソースや他のリソースに変更は発生しません。変更しないので非常に簡単!

解決方法3.2. Reconcilation を他のリソースやプロダクトに任せる

自分で実装するのは大変なので、誰かが実装したものに乗っかってしまうと楽です。
例えば自分が実装した以下の Controller を例にとります。

https://github.com/takutakahashi/loadbalancer-controller

これは awsbackend リソースが作成されたら、該当情報を利用して NLB を作成する Controller なのですが、 Reconcilation の実装部分を terraform に任せています。
awsbackend リソースが作成されたら、中身を取り出して tfvars を作り、Job として apply を実行します。
こうすることで、差分を見て A が変更されていたら、B が変更されていたら、、、みたいな複雑性を terraform が吸収してくれるため、
Controller のテストは、期待する tfvars が作成されるかを確認するだけ、ということが可能になります。

また、 OpenStack Loadbalancer の情報を利用して NLB を建てる Controller も社内向けに実装したのですが、
そちらは Loadbalancer の情報から awsbackend リソースを生成するという方法で、Reconcilation の複雑性を awsbackend に委譲しています。
こちらもテストは、期待する awsbackend リソースが作成されることを確認するだけになり、簡単に実装できます。

まとめ

ガッと文章で書いてしまいましたが、簡単に実装するための Tips をお伝えしました。
少しでも Controller を書いてみたい!と思っている人は、まず Deployment の変更を Log に出す Controller を作ってみてはどうでしょうか?