Kubebuilder と触れ合う #osc20fk Kubernetes Operator の直観 予稿 (1)

2020年11月28日(土) に Open Source Conference 2020 Online/FukuokaKubernetes Operator の直観 というタイトルでセッションを行う。レベルは入門編として対象者はKubernetes Operator の開発運用を検討している人で前提知識についてはKubernetes に関する書籍を何となく理解できる方としているがそんなレベル感で聞いてくれる酔狂な人間はなかなかいないと思う。そのため、何回かに分けて Kubebuilder やその周辺知識について解説したいと思いますが図も何もない中でこんな文章読まされても意味ないので絶対に読んでほしいとかもありません。ちなみに、書籍であれば 実践入門 Kubernetesカスタムコントローラーへの道Programming Kubernetes: Developing Cloud-Native Applications などを読むとよいのでオススメです。

はじめに

Kubernetesには、分散システムを構築、運用に必要な機能を提供する多くのAPIがあります。しかし、これらは意図的に汎用であり、およそ80%のユースケースを対象としています。Kubernetesに存在するアドオンと拡張機能の豊富なエコシステムを利用すると、重要な新機能を追加し、クラスタを拡張することができる。

Kubernetes の動作をカスタマイズするには、Continuous Integration 側ではconftestkubeval などを用いてポリシーを定義し、マニフェストが定義したポリシーを満たしているかテストする方法がある。これは導入コストは低いが強制力も弱い。Kubernetes が提供しているAPI機能を拡張する為の方法としてgatekeeper や Pod Security Policiesを用いることで同様にAPI Server 側のチェックを行う事ができる。導入コストはそここで強制力がある。しかし、柔軟性に欠ける。それ以外にもKubernetesが提供しているAPI機能を拡張する為の方法として主に次のモノが挙げられる。

今回のお題はAdmission WebhookCRD の開発を円滑に進める為のツールの 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 の実装に触れてみたいと思います。