Slide 1

Slide 1 text

2020.1.7 大西 健太 株式会社 Mobility Technologies CRDとカスタムコントローラを実装して学ぶ

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

3 ▪ Kubernetesの宣言的な設定と自律した状態コントロールを 実現する仕組みを知る ▪ カスタムリソース・カスタムコントローラの作り方を知る

Slide 4

Slide 4 text

4 ▪ Kubernetesは “宣言的” に定義された仕様 (spec) に対して 各オブジェクトの状態 (status) を “自律的” に近づけるこ とで、環境の制御を自動化している ▪ 制御を行っている主体がコントローラ ▪ Kubernetesは標準外のコントローラをインストールでき、 これによって独自の制御を追加できる ▪ カスタムコントローラを開発するためのSDKとして、 kubebuilderなどが用いられる

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 $ kubectl apply -f deployment.yaml deployment.apps/nginx-deployment created $ kubectl get all NAME READY STATUS RESTARTS AGE pod/nginx-deployment-574b87c764-6jqcn 1/1 Running 0 6s pod/nginx-deployment-574b87c764-k6z5b 1/1 Running 0 6s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nginx-deployment 2/2 2 2 6s NAME DESIRED CURRENT READY AGE replicaset.apps/nginx-deployment-574b87c764 2 2 2 6s Podが2個作られている deployment以外にもreplicasetやpodが作られている

Slide 7

Slide 7 text

7 ▪ Kubernetes (k8s) はマニフェストによってオブジェクトの あるべき状態が開発者によって定義される = apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 Podのspec “nginx:1.14.2のイメージで80 番ポートexposeしたコンテナ” が必要 Deploymentのspec “app:nginxのPodを2個”が必要

Slide 8

Slide 8 text

8 ▪ あるべき状態に対して、実際の現在の状態は乖離しうる ▪ アサイン予定のノードを起動している途中 (スケジューリング中) ▪ イメージをpullしている途中 (Podのセットアップ中) ▪ ノードやネットワークの故障によって、一部のPodと疎通できな い ▪ etc... ▪ オブジェクトには実際の現在の状態が定義される =

Slide 9

Slide 9 text

9 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: default ... spec: ... status: availableReplicas: 2 conditions: - lastTransitionTime: "2021-01-06T00:19:26Z" lastUpdateTime: "2021-01-06T00:19:26Z" message: Deployment has minimum availability. reason: MinimumReplicasAvailable status: "True" type: Available - lastTransitionTime: "2021-01-06T00:19:15Z" lastUpdateTime: "2021-01-06T00:19:26Z" message: ReplicaSet "nginx-deployment-574b87c764" has successfully progressed. reason: NewReplicaSetAvailable status: "True" type: Progressing observedGeneration: 1 readyReplicas: 2 replicas: 2 updatedReplicas: 2 $ kubectl get deployment nginx-deployment -o yaml specで定義したreplicas: 2が維持されている

Slide 10

Slide 10 text

10 ▪ あるべき姿を手続き的 (エッジドリブン) に定義すると、 不測の事態下で正しい状態へコントロールすることが困 難 2 +2して 3 4 -1して 障害 “-1” を命令したときと状況が異なる (素直にリトライすると 0になる) ▪ 期待した状態 (レベルドリブン) を定義する方がシンプル レプリカ数 t 2 4にして 3 4 3にして 障害 レプリカ数 t 2 3 多すぎたので減らす

Slide 11

Slide 11 text

11 ▪ 現在の状態をあるべき状態に近づける責務を持つのが、 制御ループ ( ) ▪ 1. リソースのstatusを取得する ▪ 2. specとの差分を埋めるように操作する ▪ 3. statusを更新する ▪ 以上を繰り返す

Slide 12

Slide 12 text

12 https://kubernetes.io/ja/docs/concepts/overview/components/ spec, statusの永続化 spec, statusの入出力 PodやDeploymentなどの ビルトインリソースのコントローラ

Slide 13

Slide 13 text

13 apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 $ kubectl apply -f deployment.yaml deployment.apps/nginx-deployment created $ kubectl get all NAME READY STATUS RESTARTS AGE pod/nginx-deployment-574b87c764-6jqcn 1/1 Running 0 6s pod/nginx-deployment-574b87c764-k6z5b 1/1 Running 0 6s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nginx-deployment 2/2 2 2 6s NAME DESIRED CURRENT READY AGE replicaset.apps/nginx-deployment-574b87c764 2 2 2 6s Podが2個作られている deployment以外にもreplicasetやpodが作られている

Slide 14

Slide 14 text

14 ▪ 下位オブジェクトはapiを介してspecをCRUDするのみ ▪ その後の制御は下位オブジェクトのコントローラに任せる apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 deployment controller replicaset controller pod controller kube-api-server etcd apps/v1 Deployment spec status apps/v1 ReplicaSet spec status core/v1 Pod spec status kubectl apply read write

Slide 15

Slide 15 text

15

Slide 16

Slide 16 text

16 ▪ k8sではリソースもコントローラも拡張できる ▪ カスタムリソース定義 (CRD) ▪ カスタムコントローラ ▪ 便利ツールの多くがCRD + カスタムコントローラで実現 apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: hello-world- labels: workflows.argoproj.io/archive-strategy: "false" spec: entrypoint: whalesay templates: - name: whalesay container: image: docker/whalesay:latest command: [cowsay] args: ["hello world"] apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: creationTimestamp: null labels: prometheus: example role: alert-rules name: prometheus-example-rules spec: groups: - name: ./example.rules rules: - alert: ExampleAlert expr: vector(1) argo/workflowのワークフロー設定 prometheusのアラート設定

Slide 17

Slide 17 text

k8s control plane 17 ▪ カスタムリソース + カスタムコントローラの拡張 ( ) ▪ 1. カスタムリソース定義 (CRD) のマニフェストを作成 ▪ 2. カスタムコントローラの実装 ▪ 3. CRDとカスタムコントローラのデプロイ (apply) kube-api-server etcd k8s data plane 制御ループの実行 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: customexample.ohke.io spec: ... custom controller manifests カスタムリソースの 入出力 制御ループの実装を 含むイメージを指定 CRD apply apply

Slide 18

Slide 18 text

18 ▪ マニフェストで定義 [1] ▪ Group: apiextensions.k8s.io ▪ Version: v1 (k8s 1.16でGA) ▪ Kind: CustomResourceDefinition ▪ OpenAPI v3に則ってspecを記述 [2] ▪ 値の名前や型 ▪ 簡単なバリデーション (requiredなど) ▪ kubectlで表示させるカラムの指定 (additionalPrinterColumns) apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: crontabs.stable.example.com spec: group: stable.example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: cronSpec: type: string image: type: string replicas: type: integer scope: Namespaced names: plural: crontabs singular: crontab kind: CronTab shortNames: - ct [1] https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#customresourcedefinition-v1-apiextensions-k8s-io [2] https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md

Slide 19

Slide 19 text

19 ▪ コントローラは以下のフローを実装していく [1] [1] https://github.com/kubernetes/sample-controller Reflector (1 - 2) - kube-api-serverを監視することでオ ブジェクト (ビルトイン or カスタム) の変更を検知して、エンキュー Informer (3 - 4, 6) - デキューしてIndexerに渡す - イベントハンドラとして登録されたコ ントローラをキックする Indexer (5, 9) - ラベル (ex. .metadata.name) などを キーとしてメモリに保持 - コントローラからのリクエストに応じ て参照 コントローラのロジック (7 - 8) - workキューからデキューして処理 - 完了したら削除、リトライするなら再 度エンキュー

Slide 20

Slide 20 text

20 ▪ CRDはビルトインリソースを組み合わせて作ることが多い ▪ PodやJobなどが入れ子になるとコピー&ペーストが必要 ▪ コントローラ実装の観点では型の制約を付けたい ▪ コントローラは複雑なプラクティスを踏襲する必要があ る ▪ kube-api-serverへ不要な負荷をかけないため ▪ コアとなるロジック以外のコードが多い ▪ 効率的な実装には、何らかSDKが必要

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

22 ▪ Golangにおける主要な実装方法は3パターン ▪ client-go + code-generator (前頁で紹介) ▪ 最もプリミティブで、ビルトインのコントローラはこの方法で実装 ▪ Kubebuilder (SIGs) ▪ kubebuilder cliによるテンプレート生成 (scaffolding) ▪ バックボーンは controller-tools + controller-runtime ▪ Operator SDK (CoreOS) ▪ operator-sdk cliによるテンプレート生成 (scaffolding) ▪ バックボーンは controller-tools + controller-runtime ▪ kubebuilder (v2.3.1) を使った開発方法を紹介

Slide 23

Slide 23 text

23 ▪ インストール必須 ▪ Go v1.13+ ▪ Kustomize v3.0.0+ ▪ Docker v1.11.3+ ▪ Kubernetes v1.11.3+ ▪ kubectl v1.11.3+ ▪ kindの使用がおすすめ ▪ ビルド時に依存 ▪ controller-gen ▪ デフォルトではv0.2.5だが、最新化 (v0.4.1) することをおすすめ

Slide 24

Slide 24 text

24 ▪ 1. プロジェクトのテンプレート生成 ▪ cronjobをカスタムコントローラで実装する例を示します ▪ 2. APIのテンプレート生成 ▪ 3. CRDのGo実装 ▪ TypeMeta + ObjectMeta + Spec + Statusの4つの構造体 ▪ 4. CRDマニフェストの生成 ▪ 5. カスタムコントローラの実装 (Reconciler構造体) ▪ 初期化処理 (SetupWithManagerメソッド) ▪ 制御ループから呼び出されるハンドラ (Reconcileメソッド) ▪ 6. CRDとカスタムコントローラのデプロイ [1] https://book.kubebuilder.io/cronjob-tutorial/cronjob-tutorial.html

Slide 25

Slide 25 text

25 apiVersion: batch.tutorial.kubebuilder.io/v1 kind: CronJob metadata: name: hello spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox imagePullPolicy: IfNotPresent args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure ▪ ビルトインのCronJob (batch/v1beta1) をクローンした CronJob (cronjobs.batch.tutorial.kubebuilder.io/v1) を作る ▪ kubebuilderのチュートリアル [1] [1] https://book.kubebuilder.io/cronjob-tutorial/cronjob-tutorial.html

Slide 26

Slide 26 text

26 $ mkdir cronjob && cd cronjob $ kubebuilder init --domain tutorial.kubebuilder.io $ tree . . ├── Dockerfile ├── Makefile -> CRDの生成やapply、イメージビルドなどのスクリプト ├── PROJECT ├── bin ├── config -> 各種マニフェストのテンプレートを kustomizeで定義 │ ├── certmanager │ ├── default │ ├── manager │ ├── prometheus │ ├── rbac │ └── webhook ├── go.mod -> 依存するgoライブラリを記述 ├── go.sum ├── hack │ └── boilerplate.go.txt -> 生成されたコードのヘッダを記述 └── main.go-> エントリポイント module github.com/ohke/cronjob go 1.13 require ( k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.0 ) 書き換えて最新化

Slide 27

Slide 27 text

27 $ kubebuilder create api --group batch --version v1 --kind CronJob Create Resource [y/n] y Create Controller [y/n] y $ tree . ... ├── api │ └── v1 │ ├── cronjob_types.go -> Goの構造体で表現されたCRD (3で実装) │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── config │ ... │ ├── crd -> CustomResourceDefinitionマニフェスト │ └── samples -> カスタムリソースのサンプルマニフェスト (batch.tutorial.kubebuilder.io/v1) ├── controllers │ ├── cronjob_controller.go -> コントローラ本体 (5で実装) │ └── suite_test.go ...

Slide 28

Slide 28 text

28 //+kubebuilder:object:root=true //+kubebuilder:subresource:status type CronJob struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec CronJobSpec `json:"spec,omitempty"` Status CronJobStatus `json:"status,omitempty"` } type CronJobSpec struct { //+kubebuilder:validation:MinLength=0 Schedule string `json:"schedule"` //+kubebuilder:validation:Minimum=0 // +optional StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty"` // +optional ConcurrencyPolicy ConcurrencyPolicy `json:"concurrencyPolicy,omitempty"` // +optional Suspend *bool `json:"suspend,omitempty"` JobTemplate batchv1beta1.JobTemplateSpec `json:"jobTemplate"` } type CronJobStatus struct { // +optional Active []corev1.ObjectReference `json:"active,omitempty"` // +optional LastScheduleTime *metav1.Time `json:"lastScheduleTime,omitempty"` } TypeMeta, ObjectMeta, Spec, Statusを持つ構 造体を定義 //+kubebuilder:... はcontroller-genに渡される情報を表す マーカコメント (後ろの空行とセット) cronjob_types.go (抜粋) バリデーションもマーカコメントで記述 他のビルトインリソースやカスタムリソースの入 れ子も可能

Slide 29

Slide 29 text

$ make manifests $ tree. . ... ├── config │ ├── certmanager │ ├── crd │ │ ├── bases │ │ │ └── batch.tutorial.kubebuilder.io_cronjobs.yaml │ │ ├── kustomization.yaml │ │ ├── kustomizeconfig.yaml │ │ └── patches │ ├── default │ ├── manager │ ├── prometheus │ ├── rbac │ │ ├── role.yaml -> CRDの操作権限を持つClusterRole ... 29 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null name: cronjobs.batch.tutorial.kubebuilder.io spec: group: batch.tutorial.kubebuilder.io names: kind: CronJob listKind: CronJobList plural: cronjobs singular: cronjob scope: Namespaced versions: - name: v1 schema: openAPIV3Schema: ...

Slide 30

Slide 30 text

30 var ( jobOwnerKey = ".metadata.controller" apiGVStr = batch.GroupVersion.String() ) func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error { if r.Clock == nil { r.Clock = realClock{} } if err := mgr.GetFieldIndexer().IndexField(context.Background(), &kbatch.Job{}, jobOwnerKey, func(rawObj client.Object) []string { // grab the job object, extract the owner... job := rawObj.(*kbatch.Job) owner := metav1.GetControllerOf(job) if owner == nil || owner.APIVersion != apiGVStr || owner.Kind != "CronJob" { return nil } return []string{owner.Name} }); err != nil { return err } return ctrl.NewControllerManagedBy(mgr). For(&batch.CronJob{}). Owns(&kbatch.Job{}). Complete(r) } ▪ SetupWithManagerメソッドに初期化処理を記述 インデックスの作成 CronJobとCronJobが作成したJobが 変更された際にReconcileをトリガ

Slide 31

Slide 31 text

31 ▪ Reconcileメソッドに制御ループ内の処理を記述 //+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch //+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=get func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var cronJob batch.CronJob r.Get(ctx, req.NamespacedName, &cronJob) var childJobs kbatch.JobList r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: req.Name}) // ...Jobのstatus確認、完了Jobのカウントなど... r.Status().Update(ctx, &cronJob) // ...Jobの開始判定など... job, err := constructJobForCronJob(&cronJob, missedRun) r.Create(ctx, job) scheduledResult := ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())} return scheduledResult, nil } CronJob, Jobへの RBAC設定 CronJobの取得 Jobの取得 CronJobのstatusを更新 新しいJobを作成 次のエンキュー時刻を指 定して終了

Slide 32

Slide 32 text

32 $ kind create cluster --image=kindest/node:v1.16.15 --name kind-16-15 $ kubectx kind-kind-16-15 $ make install $ kubectl get crd NAME CREATED AT cronjobs.batch.tutorial.kubebuilder.io 2021-01-07T03:27:52Z $ make docker-build $ kind load docker-image controller:v1 $ make deploy $ kubectl apply -f config/samples/batch_v1_cronjob.yaml 動作確認用クラスタの作成 CRDのデプロイ カスタムコントローラのデプロイ

Slide 33

Slide 33 text

33 ▪ Kubernetesは “宣言的” に定義された仕様 (spec) に対して 各オブジェクトの状態 (status) を “自律的” に近づけるこ とで、環境の制御を自動化している ▪ 制御を行っている主体がコントローラ ▪ Kubernetesは標準外のコントローラをインストールでき、 これによって独自の制御を追加できる ▪ カスタムコントローラを開発するためのSDKとして、 kubebuilderなどが用いられる

Slide 34

Slide 34 text

34 ▪ Kubernetes、コントローラ ▪ https://kubernetes.io/docs/home/ ▪ https://kubernetes.io/docs/reference/generated/kubernete s-api/v1.20/ ▪ https://github.com/kubernetes/sample-controller ▪ Kubebuilder/controller-runtime入門 ▪ 負荷を予測して事前スケーリングする HPA の Custom Controller 実装 ▪ kubebuilder ▪ https://book.kubebuilder.io/ ▪ 実践入門 Kubernetesカスタムコントローラーへの道 ▪ https://zoetrope.github.io/kubebuilder-training/