KubernetesのCRDまわりを整理する。

# Kubernetes CRDまわりを整理する。

KubernetesにはCustom Resource Definitions（CRD）という機能があります。CRDはKubernetes APIを拡張して独自のリソースを定義するものです。KubernetesのリソースとはDeploymentやPodのようなもののことですが、CRDではDeploymentやPodと並ぶリソースを自分で定義し実装することが可能となっています。
本記事ではCRDについて、概念やツールを整理します（2018/12/24時点の情報をもとに）。

## リソースとオブジェクト
CRDに入る前にKubernetesのリソースとオブジェクトについて整理します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/55384/f077fc7f-b6f6-1655-9ef4-58f3e869ce2c.png)


### リソース
リソースとは何らかのオブジェクトを概念です。例えばDeploymentやPodsがリソースです。リソースはKubernetes APIを持ち、実際に配備されているオブジェクトとしてのPodsがリソースとしてのPodsに格納されます。要はクラスとインスタンスみたいなものです（リソースがクラス、オブジェクトがインスタンス）。
主なリソースとしては以下があります。

- Nodes
- Namespaces
- Configmaps
- Secrets
- Roles
- Rolebindings
- Pods
- Replicasets
- Deployments
- Daemonsets
- Jobs
- Cronjobs
- Services
- Ingresses
- Persistentvolumes
- Persistentvolumeclaims

### オブジェクト
オブジェクトとは持続的なエンティティのことで、Kubernetesクラスターの状態を定義します。簡単に言うと、デプロイされたPodsやServiceのことです。オブジェクトには以下のような状態が定義されます。

- 実行されているコンテナ・アプリケーション
- そのアプリケーションに提供されているリソース
- そのアプリケーションの稼働ポリシー（リスタート、アップグレード、フォールトトレランス）

ユーザがPodsやDeployment、ServiceをKubernetesに配備するとき、多くの場合はKubernetes APIに対してyamlファイルで定義し、 `kubectl apply -f sample.yaml` のようにオブジェクトのdesired state（所望する状態）を実装すると思います。Kubernetesではこのyamlファイルをもとにオブジェクトを配備し、desired stateを維持できるようにオブジェクトを操作します。このとき `kubectl` がアクセスして実行しているのがクラスターのKubernetes APIになります。
オブジェクトのdesired stateはyaml上で `spec` として定義されます。Kubernetesは `spec` に定義された状態 `state` を維持する役割を果たします。
以下はDeploymentオブジェクトの例ですが、最初の `spec` 配下でDeploymentの状態を定義し、二つ目の `spec` でDeployment内のPodsの状態を定義しています。

```yaml
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
```

これが配備されると、Kubernetesクラスターにはnginx:1.7.9のコンテナイメージを使ってPodsが2個立ち上がり、いずれも80ポートで公開される、という状態になります。


## Custom Resource
上記で説明した標準的にKubernetesで提供されているリソースの他に、自分でリソースを定義することができます。これがCustom Resourceと呼ばれるもので、Kubernetes APIを拡張して実装します。Custom Resourceはあくまで独自のリソースのなので、Kubernetes API（`kubectl`コマンドも使用可能）を通して利用し、オブジェクトを管理する、という点はリソースと同じです。
Custom Resource自体は構造化されたデータを格納するための箱で、実際の操作にはCustom Controllerを実装する必要があります。

### Custom Controller
Custom ControllerはCustom Resourceをコントロールするためのdeclarative API（宣言的なAPI）です。declarative APIでは、ユーザはオブジェクトのdesired stateを宣言することで配備と維持を行います。オブジェクトに対する実際の操作方法や命令はCustom Controllerが行います。Custom Controllerはオブジェクトの状態をdesired stateと一致するように継続的に操作します。
Custom Controller自体はGolangで[client-go](https://github.com/kubernetes/client-go)実装して[code-generator](https://github.com/kubernetes/code-generator)でクライアントライブラリを生成します。

[client-go](https://github.com/kubernetes/client-go)はKubernetesクラスターを操作するクライアントのGolang実装です。[ご参考。](https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899)
![client-go](https://cdn-images-1.medium.com/max/1600/1*4noxYkVSvMPmlbt1kSOIHw.png)
[code-generator](https://github.com/kubernetes/code-generator)は[KubernetesスタイルのAPI](https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md)を生成するジェネレーターです。

[Kubernetes API、Custom Controller、client-goの関係](https://medium.com/@cloudark/kubernetes-custom-controllers-b6c7d0668fdf)は以下のようになっています。

![kcc](https://cdn-images-1.medium.com/max/1600/1*dmvNSeSIORAMaTF2WdE9fg.jpeg)

Custom Controllerはclient-goを通してKubernetes APIを操作します。このCustom Controllerを実行するDocker ImageをCRDのコントローラとしてDeploymentとして配備することで、CRDのCustom ControllerをKubernetes上で稼働させることができるようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/55384/a7a16d5c-20de-484a-413d-95dc18821601.png)


Custom ResourceとCustom Controllerで独自にリソースとKubernetes APIを定義することが可能になりますが、その実装方法は2通りあります。

1. CRD：プログラミング不要でAPIを定義可能
2. API Aggregation：プログラミングが必要だが、より詳細なAPIを定義可能

### API Aggregation（AA）
AAはKubernetes APIの拡張になります。AAはKubernetes1.7以降で追加された機能で、kube-apiserverにAAのためのaggregation layerが追加されています。ユーザはAPIServiceオブジェクトを実装しAPIに登録することで（独自のAAに対するURLを設定することで）、aggregation layerがプロキシとして当該URLへのリクエストを転送します。
AAはPIServerの一部として機能します。AAを利用するためにはAPIServerに[こちら](https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server/)の拡張設定を追加する必要があります。
AAは[apiserver-builder](https://github.com/kubernetes-incubator/apiserver-builder/blob/master/README.md)または[service-catalog](https://github.com/kubernetes-incubator/service-catalog/blob/master/README.md)を利用して開発すると便利です。

### CRD
AAより手軽に独自のリソースを追加するのがCRDです。CRDはAPIServerにAPIを追加することなく実装可能です。
CRDはCustom Resourceを定義します（名前どおりです）。CRDの定義はyamlで行うことができます。
以下は[CRDのサンプル](https://github.com/kubernetes/sample-controller)から持ってきたCRDの定義例です。

```yaml
# crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: foos.samplecontroller.k8s.io
spec:
  group: samplecontroller.k8s.io
  version: v1alpha1
  names:
    kind: Foo
    plural: foos
  scope: Namespaced
```

ここでは `foos.samplecontroller.k8s.io` というエンドポイントで `Foo` というCRDを定義しています。これをもとに `kubectl apply -f crd.yaml` を実行するだけで、新たなKubernetes APIのエンドポイントが生成されます。
CRDもリソースの一種なので、上記ではCRDリソースに `Foo` というCRDオブジェクトが配備されます。
`kubectl get crd` で配備済みのCRDの一覧を取得することができます。

CRDオブジェクト `Foo` が配備された状態で、`Foo` のカスタムオブジェクトを作ることも可能になります。
以下はその例です。

```yaml
# example-foo.yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 1
```

`kubectl apply -f example-foo.yaml` を実行することで `Foo` リソースに `example-foo` というオブジェクトが配備されます。

`kubectl get foo` で配備済みの `Foo` の一覧を取得をすることができます。

### CRDの拡張機能
CRDを定義する際に[便利な機能](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/)を紹介します。

#### Finalizer
Finalizerはカスタムオブジェクト削除前の処理を定義します。Finalizerを定義することで、カスタムオブジェクトを `kubectl delete` した時、実際に削除される前に実行すべき処理を定義します。例えばDeploymentではDeploymentを削除するとPodsやReplicasetが削除されますが、このようにDeploymentの削除の前にPodsやReplicasetを削除する、というのと同じ動作をFinalizerで定義することが可能です。

#### Validation
CRDの定義に、カスタムオブジェクトの設定値のValidationを以下のように追加することができます。

```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: foos.samplecontroller.k8s.io
spec:
  group: samplecontroller.k8s.io
  version: v1alpha1
  names:
    kind: Foo
    plural: foos
  scope: Namespaced
  validation:
    openAPIV3Schema:
      properties:
        spec:
          properties:
            replicas:
              type: integer
              minimum: 1
              maximum: 10
```

ここでは `replicas` に設定可能な値をintegerで1~10の値と定義し、それ以外の値でカスタムオブジェクトを `kubectl apply` するとエラーにすることができます。

- OKな例：

```yaml
# example-foo-ok.yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 1
```

- NGな例1：

```yaml
# example-foo-ng-1.yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 1.1
```

- NGな例2：

```yaml
# example-foo-ng-2.yaml
apiVersion: samplecontroller.k8s.io/v1alpha1
kind: Foo
metadata:
  name: example-foo
spec:
  deploymentName: example-foo
  replicas: 111
```

#### Printer
Kubernetes1.11以降では、カスタムオブジェクトを`kubectl get` する際に表示するパラメータを定義することが可能です。カスタムオブジェクトはデフォルトでは、 `kubectl get` オブジェクト名しか表示されません。他のパラメータを表示するためには以下のように表示対象のパラメータをCRDで定義する必要があります。
例えば以下のように `additionalPrinterColumns` を設定することで、 `kubectl get` にレプリカ数やタイムスタンプを標準で表示するようにできます。


```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: foos.samplecontroller.k8s.io
spec:
  group: samplecontroller.k8s.io
  version: v1alpha1
  names:
    kind: Foo
    plural: foos
  scope: Namespaced
  additionalPrinterColumns:
  - name: Replicas
    type: integer
    description: The number of jobs launched by the Foo
    JSONPath: .spec.replicas
  - name: Age
    type: date
    JSONPath: .metadata.creationTimestamp
```

#### Subresource
`/status`と`/scale`というサブリソースをサポートしています。これらはCRDのAPIエンドポイントのサブリソースとして機能します。
`/status`はオブジェクトの現在の状態を意味します（desired stateは`spec`です）。`/scale`はレプリカ数です。

```yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: foos.samplecontroller.k8s.io
spec:
  group: samplecontroller.k8s.io
  version: v1alpha1
  names:
    kind: Foo
    plural: foos
  scope: Namespaced
  subresources:
    # status enables the status subresource.
    status: {}
    # scale enables the scale subresource.
    scale:
      # specReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Spec.Replicas.
      specReplicasPath: .spec.replicas
      # statusReplicasPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Replicas.
      statusReplicasPath: .status.replicas
      # labelSelectorPath defines the JSONPath inside of a custom resource that corresponds to Scale.Status.Selector.
      labelSelectorPath: .status.labelSelector
```

サブリソースを使うことで以下のようなエンドポイントを公開することが可能になります。

```
# status
/apis/samplecontroller.k8s.io/v1alpha1/namespaces/default/foos/example-foo/status

# scale
/apis/samplecontroller.k8s.io/v1alpha1/namespaces/default/foos/example-foo/scale
```


##  参考URL
### CRDの説明
- [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
- [Extend the Kubernetes API with CustomResourceDefinitions](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/)

### CRD例
- [sample-controller](https://github.com/kubernetes/sample-controller)
- [KubernetesのCRD(Custom Resource Definition)とカスタムコントローラーの作成](https://qiita.com/__Attsun__/items/785008ef970ad82c679c)
- [KubernetesのCustom Resource Definition(CRD)とCustom Controller](https://www.sambaiz.net/article/182/)

### client-go
- [client-go](https://github.com/kubernetes/client-go)
- [Building stuff with the Kubernetes API (Part 4) — Using Go](https://medium.com/programming-kubernetes/building-stuff-with-the-kubernetes-api-part-4-using-go-b1d0e3c1c899)

### code-generator
- [code-generator](https://github.com/kubernetes/code-generator)
- [Kubernetes Deep Dive: Code Generation for CustomResources](https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/)