kubebuilderに触れてみた①概念編

# kubebuilderとは

https://github.com/kubernetes-sigs/kubebuilder

Kubebuilderは、カスタムリソース定義(CRD)を使ってKubernetes APIを構築するためのフレームワーク。

依存するcontroller-tools,controller-runtimeを用いてコードテンプレ/マニュフェスト群を生成してくれる

注意書き
「kubebuilderはコピペするための例として存在するのではなく、Kubernetes APIをゼロから構築して公開することを簡単にするための強力なライブラリやツールを提供している」

# kubebuilderの思想

KubernetesのツールやAPIを構築するには、多くの決定を行い、多くの定型文を書く必要がある。

このkubebuilderでは、カノニカルアプローチを用いてKubernetesのAPIやツールを簡単に構築できるようにするために、Kubernetesの開発ツールをまとめて提供し、労力を最小限に抑えている。

Kubebuilder は、API を構築するための以下のような開発者のワークフローを容易にすることを試みている。

1.新しいプロジェクトディレクトリの作成
2.1つ以上のリソースAPIをCRDとして作成し、リソースにフィールドを追加。
3.コントローラにリコンサイルループを実装し、追加リソースを見る
4.クラスタに対して実行してテストする（CRDをセルフインストールし、コントローラを自動的に起動する
5.新しいフィールドとビジネスロジックをテストするために、ブートストラップされた統合テストを更新。
6.提供されたDockerfileからコンテナをビルドして公開

# そもそもCRDって？

## k8sのアーキテクチャ
コンポーネントレベルでみると、Master には etcd, kube-apiserver, kubescheduler, etcd, kube-controller-manager, cloud-controller-manager,kube-dns がWorker には kubelet, kube-proxy, Container Runtimeが動いている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265549/6707588c-83a4-7167-5ff0-92879c021932.png)

## api-server 
api-server は、kubectl からのリクエストを受けた際に DeploymentやService などのObjectの作成・更新・削除などを⾏う。Resource の作成・更新・削除などを⾏う際は、api-server がリクエストを受け付けて、その Object の情報を etcd に永続化する。scheduler や controller-manager が Resource を操作しようとする 時も、api-server を経由する。 データストアである etcd にアクセスできるのは、api-serverのみ。

## Reconciliation Loop
controller-managerがDeploymentやServiceなどのObject管理を行う。controller-manager野中ではDeployment, ServiceなどのControllerが一つのバイナリに纏まっている

**ここでk8sのあるべき姿と実際の状態を比較して実際の状態をあるべき状態へと近づけるReconciliation Loopと呼ばれる処理時が実行される。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265549/8c9813f6-8eb2-602c-f0aa-c071f3047ec1.png)

処理の流れ
1.現在のResourceのStatusを読み込む
2.Desired Stateに合わせてCurrentStateを変更する
3.現在のResourceのStatusを更新する

### ReplicaSet ControllerからPodが作成される一連の流れ
1.ReplicaSet Controller は、ReplicaSetが作成されたことを検知し、メインロジックの中で spec.nodeName が空のPodを作成
2. Schedulerがspec.nodeNameが空であることを検知し、Scheduler queueに追加
3. kubeletが新しいPodの存在を検知するが、spec.nodeNameが空なのでコンテナ起動をスキップ
4. SchedulerがPodをScheduler queueから取り出して、スケジューリング可能かつSystem Resourceが充分なNodeにアサインする。アサイン時に、spec.nodeNameをapi-server経由で更新
5. kubeletがPodの更新Eventを検知し、spec.nodeNameが自身のNodeと合致するかを比較。合致していたらコンテナを起動し、api-server 経由でstatusを更新します
6. Pod が(何らかの理由で)Terminating
7. kubeletがapi-server経由で、Podのstatus.condition を"terminated"に更新
8. ReplicaSet ControllerはPodのTerminateを検知し、api-server経由でPodを削除し、新しくPodを生成。
　　　　　　　　　-この処理の繰り返し-


>Kubernetes is more jazz improv than orchestration.（By Kubernetes co-founder Jooe Beda)
>>api-serverが指揮者となって全てを命じるのではなく、各コンポーネントがプレイヤーとなり演奏をし、それらが重なることで即興楽曲のように成立している


## CRDとは
k8sでは、表中のResourceに加え、ユーザーが独自のResourceを定義し、クラスタ上で利用できる。それがCRD（Custom Resource Definition）

sample CRD

```yaml
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.2.5
  creationTimestamp: null
  name: guestbooks.webapp.my.domain
spec:
  group: webapp.my.domain
  names:
    kind: Guestbook
    listKind: GuestbookList
    plural: guestbooks
    singular: guestbook
  scope: Namespaced
  validation:
    openAPIV3Schema:
      description: Guestbook is the Schema for the guestbooks API
      properties:
        apiVersion:
          description: 'APIVersion defines the versioned schema of this representation
            of an object. Servers should convert recognized schemas to the latest
            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
          type: string
        kind:
          description: 'Kind is a string value representing the REST resource this
            object represents. Servers may infer this from the endpoint the client
            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
          type: string
        metadata:
          type: object
        spec:
          description: GuestbookSpec defines the desired state of Guestbook
          properties:
            foo:
              description: Foo is an example field of Guestbook. Edit Guestbook_types.go
                to remove/update
              type: string
          type: object
        status:
          description: GuestbookStatus defines the observed state of Guestbook
          type: object
      type: object
......

```

## CRDの作成

```bash

kubectl apply -f webapp.my.domain_guestbooks.yaml  

kubectl get crd
NAME                          CREATED AT
guestbooks.webapp.my.domain   2020-07-19T18:03:39Z

```

ただし、コントローラがなければ何も効果がないObjectが生成されるだけ。


# ControllerとCRDを自作するために

## kubebuilder以外のCRD作成手段

- Operator SDK	

プロジェクトテンプレート生成、ビルド、デプロイをするCLIツール。AnsibleでもOperatorを書けるのが面白い。Operator FrameworkとしてLifecycle Managerなどが提供されていたり、OperatorHub.ioというコミュニティサイトがあったり、エコシステムが充実している。kubebuilder同様必要なもの一式が提供される。Helm Chart,Ansibleなどのサポートもある

- Kubernetes way
Client-go,code-generatorを使用する方法（Kubernetes内部にある既存のControllerの多くがこの方法で実装されており、伝統的らしい）低レベルAPIを扱うため、実装の自由度が高いが覚えることがたくさんある。

# Kubebuilderを使ってみる


## インストール
```bash

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/${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

```

## アプリケーションの作成
```bash
kubebuilder init --domain my.domain
kubebuilder create api --group webapp --version v1 --kind Guestbook



## 以下のようなファイルが生成される
.
├── Dockerfile
├── Makefile
├── PROJECT
├── api
│   └── v1
│       ├── groupversion_info.go
│       ├── guestbook_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── manager
├── config
│   ├── certmanager
│   │   ├── certificate.yaml
│   │   ├── kustomization.yaml
│   │   └── kustomizeconfig.yaml
│   ├── crd
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   │       ├── cainjection_in_guestbooks.yaml
│   │       └── webhook_in_guestbooks.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
│   │   ├── guestbook_editor_role.yaml
│   │   ├── guestbook_viewer_role.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   └── role_binding.yaml
│   ├── samples
│   │   └── webapp_v1_guestbook.yaml
│   └── webhook
│       ├── kustomization.yaml
│       ├── kustomizeconfig.yaml
│       └── service.yaml
├── controllers
│   ├── guestbook_controller.go
│   └── suite_test.go
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── main.go

```

- api/
CRDに相当するtypeを書く。これをもとにCRD manifestが生成される
生成されるテンプレをもとに定義したいspec,statusをかく

- controllers/
Reconcile 関数にロジックを書く
生成されるテンプレレートをもとにcontroller-runtimeを使って実装

-config
各種manifestファイル、kustomizeのAppが生成される
ソースに書いたアノテーションをもとにcontroller-toolsが生成してくれる
 
# 実行
```bash

make install
# Install CRDs into a cluster
install: manifests
	kustomize build config/crd | kubectl apply -f -

make run
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet manifests
	go run ./main.go

```


# controller-toolsとcontroller-runtime

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265549/d2b33700-933c-6e2b-1b18-7be52cf6bba8.png)


### controller-tools

controller-toolsには内部にcontroller-genというコマンドがあり、これがKubernetesマニフェスト群などを生成してくれる。
controller-genがGoファイルのコードを解析して自動生成する
// +kubebuilder:object:root=trueのような箇所が解析対象であることを示している。

```go

// +kubebuilder:object:root=true

// Guestbook is the Schema for the guestbooks API
type Guestbook struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   GuestbookSpec   `json:"spec,omitempty"`
	Status GuestbookStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// GuestbookList contains a list of Guestbook
type GuestbookList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Guestbook `json:"items"`
}

```

### controller-runtime

Kubernetes CRD と集約/組み込みの Kubernetes API の両方を操作する Kubernetes スタイルのコントローラを構築するためのツールを提供

Kubernetes API を実装しており、Operator、Workload API、Configuration API、Autoscalers などを構築するための基盤になっている。

controller-runtimeを利用することで、実装stageがk8s特有のコンポーネントを意識して実装する必要がなくなる。

#### Controller-Manager

controller-managerが複数のCustom Controllerを監督する

```go
	ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

//Manager作成
	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:             scheme,
		MetricsBindAddress: metricsAddr,
		Port:               9443,
//リーダー選出機能
		LeaderElection:     enableLeaderElection,
		LeaderElectionID:   "bce3f8f2.my.domain",
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}

	if err = (&controllers.GuestbookReconciler{
		Client: mgr.GetClient(),
		Log:    ctrl.Log.WithName("controllers").WithName("Guestbook"),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Guestbook")
		os.Exit(1)
	}

//guestbook_controller.go

func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
//監視のリソースを指定これによりEventが監視対象になる。これによりEvent発生時にReconcile処理が呼び出される
	return ctrl.NewControllerManagedBy(mgr).
		For(&webappv1.Guestbook{}).
		Complete(r)
}

```

### Reconcile関数

```go


// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.my.domain,resources=guestbooks/status,verbs=get;update;patch

func (r *GuestbookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
	_ = context.Background()
	_ = r.Log.WithValues("guestbook", req.NamespacedName)

	// your logic here

	return ctrl.Result{}, nil
}

```

Controller起動中はこの関数のLoopが延々と続く


# どのようにAPIをデザインしていくかby公式（直訳）


Kubernetesでは、APIの設計方法にいくつかのルールがあるシリアル化されたフィールドはすべてキャメルケースでなければならないので、これを指定するためにJSON構造タグを使用する。また、フィールドが空の場合はシリアライズから省略されるべきであることをマークするために omitempty 構造タグを使用することも可能。

フィールドはほとんどのプリミティブ型を使用することができるが、数値は例す。API の互換性のために、整数には int32 と int64、小数には resource.Quantityの3つの形式の数値を受け入れている。

もう一つの特別な型がある。それは metav1.Time。固定されたポータブルなシリアル化フォーマットを持っていることを除いて、time.Timeと同じように機能する


基本的にCronJobに必要な要素。

- スケジュール(CronJobのcron)
- 実行するジョブのテンプレート(CronJob内のジョブ)
- ユーザーの生活が楽になるようなおまけもいくつか欲しいところです。
- 仕事を開始するための期限（この期限を逃すと、次の予定時間まで待つことになります
- 複数のジョブが一度に実行される場合はどうすればいいのか(待つのか、古いものを止めるのか、両方実行するのか？)
- 何か問題がある場合に備えて、CronJobの実行を一時停止する手段


追加のメタデータを指定するために、いくつかのマーカー (// +comment) を使用する。これらは、CRD マニフェストを生成する際に controller-tools によって使用される。GoDocの書き方も使える

https://book.kubebuilder.io/cronjob-tutorial/api-design.html