2020年11月28日(土) に Open Source Conference 2020 Online/Fukuoka でKubernetes Operator の直観 というタイトルでセッションを行う。レベルは入門編として対象者はKubernetes Operator の開発運用を検討している人で前提知識についてはKubernetes に関する書籍を何となく理解できる方としているがそんなレベル感で聞いてくれる酔狂な人間はなかなかいないと思う。そのため、何回かに分けて Kubebuilder やその周辺知識について解説したいと思いますが図も何もない中でこんな文章読まされても意味ないので絶対に読んでほしいとかもありません。ちなみに、書籍であれば 実践入門 Kubernetesカスタムコントローラーへの道 や Programming Kubernetes: Developing Cloud-Native Applications などを読むとよいのでオススメです。
はじめに
Kubernetesには、分散システムを構築、運用に必要な機能を提供する多くのAPIがあります。しかし、これらは意図的に汎用であり、およそ80%のユースケースを対象としています。Kubernetesに存在するアドオンと拡張機能の豊富なエコシステムを利用すると、重要な新機能を追加し、クラスタを拡張することができる。
Kubernetes の動作をカスタマイズするには、Continuous Integration 側ではconftest や kubeval などを用いてポリシーを定義し、マニフェストが定義したポリシーを満たしているかテストする方法がある。これは導入コストは低いが強制力も弱い。Kubernetes が提供しているAPI機能を拡張する為の方法としてgatekeeper や Pod Security Policiesを用いることで同様にAPI Server 側のチェックを行う事ができる。導入コストはそここで強制力がある。しかし、柔軟性に欠ける。それ以外にもKubernetesが提供しているAPI機能を拡張する為の方法として主に次のモノが挙げられる。
今回のお題はAdmission Webhook や CRD の開発を円滑に進める為のツールの Kubebuilder を用いて実際に作成した何もしないリソースをデプロイしたいと思います。
Kubebuilder について
Kubernetes 向けのAdmission Webhook は比較的にシュッと開発することができますがCustom Resourcesは、Kubernetesの設計コンセプトを正しく理解する必要があり、たくさんのマニフェストを記述する必要もあるため、非常に難しいです。
Kubebuilder では、controller-tools,controller-runtimeを用いて抽象化したライブラリと、マニフェストを自動生成するツール群を提供することで、Kubernetesの設計コンセプトを完璧に正しく理解する必要もたくさんのマニュフェスト群も書かずに簡単にカスタムコントローラを開発できます。Kubebuilder は、API を構築するための以下のような開発者のワークフローを容易にすることを試みている。
1.新しいプロジェクトディレクトリの作成 2.1つ以上のリソースAPIをCRDとして作成し、リソースにフィールドを追加。 3.コントローラにリコンサイルループを実装し、追加リソースを見る 4.クラスタに対して実行してテストする(CRDをセルフインストールし、コントローラを自動的に起動する 5.新しいフィールドとビジネスロジックをテストするために、ブートストラップされた統合テストを更新。 6.提供されたDockerfileからコンテナをビルドして公開
Kubebuilder の背景や設計思想については [KubeBuilder Design Principles] を読んでいただければと思います。ちなみに本来であれば、この後にKubernetes のアーキテクチャやAPI-Server について及びあるべき姿と実際の状態を比較して実際の状態をあるべき状態へと近づける Reconciliation Loop と呼ばれる処理についてやCRD についても説明した方が良いのですが ゼロから始めるKubernetes Controller などの資料があるのでそちらを参照下さい。
環境構築
Quick Start - The Kubebuilder Book を参考にしていただければ基本的に環境構築は問題ないと思います。
kubebuilder install
os=$(go env GOOS) arch=$(go env GOARCH) # download kubebuilder and extract it to tmp # curl -L https://go.kubebuilder.io/dl/2.3.1/linux/amd64 | tar -xz -C /tmp/ curl -L https://go.kubebuilder.io/dl/2.3.1/${os}/${arch} | tar -xz -C /tmp/ # move to a long-term location and put it on your path # (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else) sudo mv /tmp/kubebuilder_2.3.1_${os}_${arch} /usr/local/kubebuilder export PATH=$PATH:/usr/local/kubebuilder/bin
はい、インストールできたと思います。
kubebuilder --help Development kit for building Kubernetes extensions and tools. Provides libraries and tools to create new projects, APIs and controllers. Includes tools for packaging artifacts into an installer container. Typical project lifecycle: - initialize a project: kubebuilder init --domain example.com --license apache2 --owner "The Kubernetes authors" - create one or more a new resource APIs and add your code to them: kubebuilder create api --group <group> --version <version> --kind <Kind> Create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only scaffold a Controller for an existing Resource, select "n" for Resource. To only define the schema for a Resource without writing a Controller, select "n" for Controller. After the scaffold is written, api will run make on the project. Usage: kubebuilder [command] Available Commands: create Scaffold a Kubernetes API or webhook. edit This command will edit the project configuration help Help about any command init Initialize a new project version Print the kubebuilder version Flags: -h, --help help for kubebuilder Use "kubebuilder [command] --help" for more information about a command.
kubebuilder init
kubebuilder init
で プロジェクトを作成したいと思います。
* --domain
: APIグループのドメインを指定
* --license
: ソフトウェアライセンスを選択
* --owner
: このソフトウェアのオーナーを指定
$ go mod init kubebuilder-test-controller $ kubebuilder init --domain nwiizo.dev --license apache2 --owner nwiizo Writing scaffold for you to edit... Get controller runtime: $ go get sigs.k8s.io/controller-runtime@v0.5.0 Update go.mod: $ go mod tidy Running make: $ make /home/smotouchi/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... go build -o bin/manager main.go Next: define a resource with: $ kubebuilder create api
成果物の確認
$ tree . ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── bin │ └── manager ├── config │ ├── certmanager │ │ ├── certificate.yaml │ │ ├── kustomization.yaml │ │ └── kustomizeconfig.yaml │ ├── default │ │ ├── kustomization.yaml │ │ ├── manager_auth_proxy_patch.yaml │ │ ├── manager_webhook_patch.yaml │ │ └── webhookcainjection_patch.yaml │ ├── manager │ │ ├── kustomization.yaml │ │ └── manager.yaml │ ├── prometheus │ │ ├── kustomization.yaml │ │ └── monitor.yaml │ ├── rbac │ │ ├── auth_proxy_client_clusterrole.yaml │ │ ├── auth_proxy_role.yaml │ │ ├── auth_proxy_role_binding.yaml │ │ ├── auth_proxy_service.yaml │ │ ├── kustomization.yaml │ │ ├── leader_election_role.yaml │ │ ├── leader_election_role_binding.yaml │ │ └── role_binding.yaml │ └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── go.mod ├── go.sum ├── hack │ └── boilerplate.go.txt └── main.go
kubebuilder create api
API作成してリソースの定義するために kubebuilder create api
を実行してきます。
みんな大好き --help
を使っていけば様々な情報が得られるので割愛しますが素晴らしい情報がたくさんあるのでよろしくお願いします。
# $ kubebuilder create api --group ship --version v1beta1 --kind Frigate Create Resource [y/n] y Create Controller [y/n] y Writing scaffold for you to edit... api/v1beta1/frigate_types.go controllers/frigate_controller.go Running make: $ make /home/smotouchi/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... go build -o bin/manager main.go
実行するための環境構築 Kind でのcluster の構築
手元のconfigから接続出来るKubernetes Cluster に入れていくためのCluster 作成しました。
$ kind create cluster --name kubebuilder-test-controller # Cluster の作成 出力省略 $ kind get clusters # Cluster の確認 kubebuilder-test-controller $ kind get kubeconfig --name kubebuilder-test-controller > kubeconfig.yaml
make install
controller-gen を実行してCRD を実行します。
$ make install ~/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases kustomize build config/crd | kubectl apply -f - customresourcedefinition.apiextensions.k8s.io/frigates.ship.nwiizo.dev created
make run
make run を実行したらcontroller のビルドと実行が出来ており、先程作成したCRDのcontrollerとして機能していると思います。
$ make run /home/smotouchi/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." go fmt ./... go vet ./... /home/smotouchi/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases go run ./main.go 2020-11-02T06:45:33.772Z INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"} 2020-11-02T06:45:33.773Z INFO setup starting manager 2020-11-02T06:45:33.773Z INFO controller-runtime.manager starting metrics server {"path": "/metrics"} 2020-11-02T06:45:33.777Z INFO controller-runtime.controller Starting EventSource {"controller": "frigate", "source": "kind source: /, Kind="} 2020-11-02T06:45:33.877Z INFO controller-runtime.controller Starting Controller {"controller": "frigate"} 2020-11-02T06:45:33.877Z INFO controller-runtime.controller Starting workers {"controller": "frigate", "worker count": 1}
kubectl apply -f config/samples/ship_v1beta1_frigate.yaml
kubectl apply -f config/samples/ship_v1beta1_frigate.yaml
を実行したらcontroller のようなログが出てきていると思います。
2020-11-02T06:49:54.186Z DEBUG controller-runtime.controller Successfully Reconciled {"controller": "frigate", "request": "default/frigate-sample"}
そして、frigate-sample がデプロイされていることを確認できると思います。今回は何もしないリソースをデプロイしたので何もしません。 何もしていない controllerを見ていきましょう
$ kubectl get crd/frigates.ship.nwiizo.dev NAME CREATED AT frigates.ship.nwiizo.dev 2020-11-02T06:42:36Z $ kubectl get frigate NAME AGE frigate-sample 6m23s $ kubectl get frigate/frigate-sample -o yaml apiVersion: ship.nwiizo.dev/v1beta1 kind: Frigate metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"ship.nwiizo.dev/v1beta1","kind":"Frigate","metadata":{"annotations":{},"name":"frigate-sample","namespace":"default"},"spec":{"foo":"bar"}} creationTimestamp: "2020-11-02T06:58:32Z" generation: 1 managedFields: - apiVersion: ship.nwiizo.dev/v1beta1 fieldsType: FieldsV1 fieldsV1: f:metadata: f:annotations: .: {} f:kubectl.kubernetes.io/last-applied-configuration: {} f:spec: .: {} f:foo: {} manager: kubectl operation: Update time: "2020-11-02T06:58:32Z" name: frigate-sample namespace: default resourceVersion: "3165" selfLink: /apis/ship.nwiizo.dev/v1beta1/namespaces/default/frigates/frigate-sample uid: 2daa801b-d728-4de8-b7f1-ae56ad5eab61 spec: foo: bar
これで、なにもしないリソースをデプロイできたと思います。
controllers/frigate_controller.go
最後に controllers/frigate_controller.go
の Reconcile の中は現在このようになっていると思います。
各種 ロジックなどを実装したい場合にはReconcile の中に諸々を書いてき再度ビルドしなおせば基本的にはOkです。
37 // +kubebuilder:rbac:groups=ship.nwiizo.dev,resources=frigates,verbs=get;list;watch;create;update;patch;delete 38 // +kubebuilder:rbac:groups=ship.nwiizo.dev,resources=frigates/status,verbs=get;update;patch 39 40 func (r *FrigateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 41 ▸---_ = context.Background() 42 ▸---_ = r.Log.WithValues("frigate", req.NamespacedName) 43 44 ▸---// your logic here 45 46 ▸---return ctrl.Result{}, nil 47 }
最後に
次回のブログではReconcile の実装に触れてみたいと思います。