やったことのまとめ
- sample-controllerを読んで動かしてCRD(CustomResourceDefinition)とCustom Controllerの概要をつかんだ
つかうもの
- macOS Big Sur 11.2.3
- Docker Desktop for Mac
- Version 3.4.0
- Docker Engine Version 20.10.7
- Docker Compose Version 1.29.2
- kind
- v0.11.0 go1.16.4 darwin/amd64
- Kubernetes
- v1.21.1
やったこと
CRDの作成
Kubernetes
には既存のPod
やDeployment
などのリソースに加えて独自のリソースを定義できるCustomResourceDefinition(CRD)
1というリソースがある.
これを使うことで既存のリソースでは物足りない機能なんかを自分で作ることができるらしい. すごい.
CRDを自分で書くのは骨が折れるので, 今回は公式のsample-controllerに付属のCRDを使ってみる.
https://github.com/kubernetes/sample-controller/blob/master/artifacts/examples/crd.yaml
動かす前に重要そうな設定項目(ほんとは全部重要だが…)を確認しておく.
key | value |
---|---|
apiVersion | “apiextensions.k8s.io/v1"で固定 |
kind | “CustomResourceDefinition"で固定 |
metadata.name | “{spec.names.plural}.{spec.group}“とする |
spec.group | KubernetesのREST APIで使用するgroup |
spec.versions[].name | KubernetesのREST APIで使用するAPIのversion |
spec.versions[].schema | CustomResourceの構造の定義(たぶん一番重要) |
spec.scope | CustomResourceをNamespace単位で管理する場合:“Namespaced”, Cluster単位で管理する場合:“Cluster” |
spec.names | Kubernetes APIやkubectlで扱うときの名前の定義 |
これを実際にクラスタに適用する.
# CRDを適用
$ pwd
/path/to/sample-controller
$ kubectl apply -f artifacts/examples/crd.yaml
customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created
$ kubectl get crd
NAME CREATED AT
foos.samplecontroller.k8s.io 2021-06-28T13:49:53Z
# CRDで定義したリソース(Foo)がAPIで操作可能になる
$ kubectl api-resources | grep foo
foos samplecontroller.k8s.io/v1alpha1 true Foo
クラスタにCRD
(foos.samplecontroller.k8s.io)が追加された.
次にこのCRD
に従ったリソースを作成する.
https://github.com/kubernetes/sample-controller/blob/master/artifacts/examples/example-foo.yaml
# CustomResourceを作成
$ kubectl apply -f artifacts/examples/example-foo.yaml
foo.samplecontroller.k8s.io/example-foo created
# CRDで定義した名前(Foo)で参照できる
$ kubectl get foo
NAME AGE
example-foo 105s
$ kubectl get foo example-foo -o yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"samplecontroller.k8s.io/v1alpha1","kind":"Foo","metadata":{"annotations":{},"name":"example-foo","namespace":"default"},"spec":{"deploymentName":"example-foo","replicas":1}}
creationTimestamp: "2021-06-28T13:59:25Z"
generation: 1
name: example-foo
namespace: default
resourceVersion: "2385464"
uid: 560ced47-d085-4b92-b92c-1be78639f878
spec:
deploymentName: example-foo
replicas: 1
$ kubectl delete foo example-foo
foo.samplecontroller.k8s.io "example-foo" deleted
apiVersion
, kind
がCRD
で独自に定義した内容に合致しているので,
それに沿ったFooというリソースを作成することができた.
ただし, この時点ではただ構造化されたデータがREST APIで扱えるようになっただけで,
このリソースがどういった振る舞いをするかなどの情報はどこにも定義されていない.
これについては次のカスタムコントローラーで定義していく.
Controllerのデプロイ
カスタムコントローラーではリソースのstatus
がspec
で定義した所望の状態に近づくように対象のオブジェクトについて操作を繰り返す.
これを自分で書くのは大変なので, こちらも公式のサンプルをそのまま使う.
https://github.com/kubernetes/sample-controller/blob/master/controller.go
実際に動かす前にこのコントローラーを構成するコンポーネントについて確認しておく.
- Informer
- Kubernetes API(正確にはAPIをwatchしたReflectorによってオブジェクトが追加されたキュー)から変化のあったオブジェクトを順番に取り出す
- 取り出したオブジェクトをIndexerに渡す
- オブジェクトの状態に応じたEvent Handlerを呼び出す
- Indexer
- Informerから受け取ったオブジェクトをメタ情報(namespace/オブジェクト名)で参照可能な状態にしてスレッドセーフな領域に保持する
- 必要なKeyが与えられた場合は対象のオブジェクトを返す
- Resource Event Handlers
- Informerで取り出したオブジェクトの状態に応じた処理を行う
- 基本的には対象のオブジェクトのKeyをWork queueに追加する
- Work queue
- 処理が必要なオブジェクトのKeyを保持するキュー
- Process Item
- Work queueから取り出したKeyを参照してIndexerからオブジェクトを取り出し必要な操作を行う
(画像は公式2のもの)
この中で実際のCRD
(Foo)の挙動を決めているのはProcess Itemの部分で,
今回使用するコントローラーの場合はsyncHandler()
3がそれにあたる.
syncHandler()
により, FooというCRD
は
- Specに
Deployment
名とレプリカ数の情報を, Statusに利用可能なレプリカ数の情報を持つ(ここまではCRD
で定義) nginx
コンテナ1台のPod
をSpecで定義されたレプリカ数ぶん保持するDeployment
を管理する(syncHandler()
で定義)Deployment
がなければ作り,Deployment
が保持するPod
数(レプリカ数)がSpecで定義されたレプリカ数を満たすようDeployment
を更新する
Deployment
の状態によってStatusを更新する(syncHandler()
で定義)
という振る舞いをするリソースとなる.
実際にカスタムコントローラーを動かしてみる.
# カスタムコントローラーのビルド
$ go build -o sample-controller .
# 有効なkubeconfigを指定して起動
$ ./sample-controller -kubeconfig=$HOME/.kube/config
I0704 00:35:30.758496 96780 controller.go:115] Setting up event handlers
I0704 00:35:30.758719 96780 controller.go:156] Starting Foo controller
I0704 00:35:30.758731 96780 controller.go:159] Waiting for informer caches to sync
I0704 00:35:30.858942 96780 controller.go:164] Starting workers
I0704 00:35:30.858973 96780 controller.go:170] Started workers
# (別のターミナルで実行)Fooリソースの作成
$ kubectl apply -f artifacts/examples/example-foo.yaml
# カスタムコントローラー適用前とは異なりstatusに変化が生じている
$ kubectl get foo example-foo -o yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"samplecontroller.k8s.io/v1alpha1","kind":"Foo","metadata":{"annotations":{},"name":"example-foo","namespace":"default"},"spec":{"deploymentName":"example-foo","replicas":1}}
creationTimestamp: "2021-07-03T15:37:32Z"
generation: 3
name: example-foo
namespace: default
resourceVersion: "3127576"
uid: 5bbab9a1-0baf-41b1-8f68-93be619a6048
spec:
deploymentName: example-foo
replicas: 1
status:
availableReplicas: 1
# Fooで定義されたDeploymentが作成されている
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
example-foo 1/1 1 1 96s
# Deploymentを削除してもカスタムコントローラーによりFooのspecを満たすようにすぐ再作成される
$ kubectl delete deployment example-foo
deployment.apps "example-foo" deleted
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
example-foo 1/1 1 1 5s
# FooリソースのSpecをいじっても問題なく作成できる
$ cat << EOF | kubectl apply -f -
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
name: my-foo
spec:
deploymentName: my-foo
replicas: 10
EOF
$ kubectl get foo my-foo
NAME AGE
my-foo 23s
$ kubectl get deployment my-foo
NAME READY UP-TO-DATE AVAILABLE AGE
my-foo 10/10 10 10 36s
CRD
(Foo)で定義したリソースに対して所望の処理が行われることが確認できた.
ここで面白いのはこのコントローラーがクラスタ外で稼働しているという点.
各種リソースの操作はKubernetes API
を通じて行われるので,
APIに接続さえできればコントローラーの実体はどこで動いていても関係ないらしい.
(デフォルトのkube-controller-manager
と同様にDeployment
として動かしても問題ないはず)
おわり
CRD
とカスタムコントローラーのサンプルで遊んでみた.
実際にコードを読んだりしてカスタムリソースとかコントローラーの仕組みがわかった気がする.
時間があれば今回使ったカスタムコントローラーをクラスタ内でDeployment
として動かしてみたい.
おまけ
https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ ↩︎
https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md ↩︎
https://github.com/kubernetes/sample-controller/blob/db75202208d77b1af79a3b04ee7612fb82564c6a/controller.go#L240-L319 ↩︎