Featured image of post Kubernetesのsample-controllerで遊ぶ

Kubernetesのsample-controllerで遊ぶ

サンプルを動かしてCRDとカスタムコントローラーについて勉強した

やったことのまとめ

  • sample-controllerを読んで動かしてCRD(CustomResourceDefinition)とCustom Controllerの概要をつかんだ

つかうもの

やったこと

CRDの作成

Kubernetesには既存のPodDeploymentなどのリソースに加えて独自のリソースを定義できるCustomResourceDefinition(CRD)1というリソースがある.
これを使うことで既存のリソースでは物足りない機能なんかを自分で作ることができるらしい. すごい.

CRDを自分で書くのは骨が折れるので, 今回は公式のsample-controllerに付属のCRDを使ってみる.
https://github.com/kubernetes/sample-controller/blob/master/artifacts/examples/crd.yaml

動かす前に重要そうな設定項目(ほんとは全部重要だが…)を確認しておく.

keyvalue
apiVersion“apiextensions.k8s.io/v1"で固定
kind“CustomResourceDefinition"で固定
metadata.name“{spec.names.plural}.{spec.group}“とする
spec.groupKubernetesのREST APIで使用するgroup
spec.versions[].nameKubernetesのREST APIで使用するAPIのversion
spec.versions[].schemaCustomResourceの構造の定義(たぶん一番重要)
spec.scopeCustomResourceをNamespace単位で管理する場合:“Namespaced”,
Cluster単位で管理する場合:“Cluster”
spec.namesKubernetes 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, kindCRDで独自に定義した内容に合致しているので,
それに沿ったFooというリソースを作成することができた.

ただし, この時点ではただ構造化されたデータがREST APIで扱えるようになっただけで,
このリソースがどういった振る舞いをするかなどの情報はどこにも定義されていない.

これについては次のカスタムコントローラーで定義していく.

Controllerのデプロイ

カスタムコントローラーではリソースのstatusspecで定義した所望の状態に近づくように対象のオブジェクトについて操作を繰り返す.
これを自分で書くのは大変なので, こちらも公式のサンプルをそのまま使う.
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からオブジェクトを取り出し必要な操作を行う

CustomController
(画像は公式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として動かしてみたい.

おまけ

不機嫌なねこ