CustomResourceDefinitions の Validation の紹介

本エントリは [Kubernetes Advent Calender 2017](https://qiita.com/advent-calendar/2017/kubernetes) の 16 日のエントリです。
本エントリでは、Kubernetes 1.8 より追加された CustomResourceDefinitions の Validation について紹介したいと思います。

# CustomResourceDefinitions(CRD) とは

CustomResourceDefinitions は Kubernetes のリソースとして独自のカスタムリソースを追加する方法の一つです。[昨年の Advent Calender](https://qiita.com/shmurata/items/58d7b010babf6476bb79) で紹介した ThirdPartyResource の後継にあたる機能です。
CRD リソースを Kubernetes API Server を通して作成すると、作成したリソースの CRUD のAPI が Kubernetes API Server に追加されます。
例えば以下のようなリソースを作成すると

```hw.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: helloworlds.stable.example.com
spec:
  group: stable.example.com
  version: v1
  scope: Namespaced
  names:
    plural: helloworlds
    singular: helloworlds
    kind: HelloWorld
    shortNames:
    - hw
```

以下のような API が追加されます。

```
$ kubectl proxy &
$ curl http://localhost:8001/apis/stable.example.com/v1
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "stable.example.com/v1",
  "resources": [
    {
      "name": "helloworlds",
      "singularName": "helloworlds",
      "namespaced": true,
      "kind": "HelloWorld",
      "verbs": [
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "create",
        "update",
        "watch"
      ],
      "shortNames": [
        "hw"
      ]
    }
  ]
}
```

この API を利用して kubectl などで作成したカスタムリソースの作成・編集・削除が行えます。
例えばリソースの作成を行う場合は以下のようにすることで作成できます。

```hw-crd.yaml
apiVersion: stable.example.com/v1
kind: HelloWorld
metadata:
  name: crd
spec:
  world: CustomResourceDefinition
```

```
$ kubectl apply -f hw-crd.yaml
helloworlds "crd" created
$ kubectl get hw
NAME      AGE
crd       7m
```

# ThirdPartyResource(TPR) との違い

機能的に大きく変わったのは以下の点になります。

- Validation (v1.8 alpha)
- Finalizers
    - カスタムリソースの削除前に終了処理を入れるための機能が追加された
- shortnames が登録できる
- cluster-scoped なリソースが作成できるようになった
    - Node などの Namespace を持たないリソースのことです

細かな点は [こちら](https://github.com/kubernetes/features/issues/95)を参照ください。


# Validation

[1.8](https://qiita.com/superbrothers/items/5a6a34c5eb919ce872aa#customresourcedefinitions-crds-alpha-json-scheme-%E3%81%AB%E3%82%88%E3%82%8B%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88) から alpha として CustomResourceDefinition に Validation が追加されました。
Validation を有効にするには Kubernetes API Server の引数に `--feature-gates=CustomResourceValidation=true` を追加します。

そして CRD のリソースを例えば以下のように変更します。

```hw.yaml
    shortNames:
    - hw
+ validation:
+   openAPIV3Schema:
+     required:
+     - spec
+     properties:
+       spec:
+         properties:
+           world:
+             type: string
+             enum:
+             - CustomResourceDefinition
+             - ThridPartyResource
+         required:
+         - world
```

これは `spec.world` フィールドを必須フィールドにし、そのフィールドには CustomResourceDefinition または ThridPartyResource 以外の値は入力できないようにバリデーションを追加しています。

この状態で以下の HelloWorld カスタムリソースを作成しようとしてみます。

```hw-crd.yml
apiVersion: stable.example.com/v1
kind: HelloWorld
metadata:
  name: crd
spec:
  world: CustomResourceDefinition
```
```
$ kubectl apply -f hw-crd.yaml
helloworlds "crd" created
```
これは上記の条件を満たしているので作成が成功します。
次に値が条件を満たしていないカスタムリソースを作成します。

```hw-hoge.yaml
apiVersion: stable.example.com/v1
kind: HelloWorld
metadata:
  name: hoge
spec:
  world: hoge
```
```
$ kubectl apply -f hw-hoge.yaml
The HelloWorld "hoge" is invalid: []: Invalid value: map[string]interface {}{"spec":map[string]interface {}{"world":"hoge"}, "apiVersion":"stable.example.com/v1", "kind":"HelloWorld", "metadata":map[string]interface {}{"selfLink":"", "uid":"7f498dbb-d24f-11e7-aebb-fa163e62dc0c", "clusterName":"", "creationTimestamp":"2017-11-26T02:14:13Z", "deletionGracePeriodSeconds":interface {}(nil), "deletionTimestamp":interface {}(nil), "initializers":interface {}(nil), "resourceVersion":"6107835", "annotations":map[string]interface {}{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"stable.example.com/v1\",\"kind\":\"HelloWorld\",\"metadata\":{\"annotations\":{},\"name\":\"hoge\",\"namespace\":\"default\"},\"spec\":{\"world\":\"hoge\"}}\n"}, "generation":0, "name":"hoge", "namespace":"default"}}: validation failure list:
spec.world in body should be one of [CustomResourceDefinition ThridPartyResource]
```

条件を満たしていないため、意図通り作成にバリデーションエラーになりました。

次に必須フィールドが入力されていないカスタムリソースを作成します。

```hw-empty.yaml
apiVersion: stable.example.com/v1
kind: HelloWorld
metadata:
  name: empty
```

```
$ kubectl apply -f hw-empty.yaml
The HelloWorld "empty" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"stable.example.com/v1", "kind":"HelloWorld", "metadata":map[string]interface {}{"selfLink":"", "creationTimestamp":"2017-11-26T02:33:12Z", "deletionGracePeriodSeconds":interface {}(nil), "deletionTimestamp":interface {}(nil), "generation":0, "name":"empty", "namespace":"default", "resourceVersion":"6109058", "annotations":map[string]interface {}{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"stable.example.com/v1\",\"kind\":\"HelloWorld\",\"metadata\":{\"annotations\":{},\"name\":\"empty\",\"namespace\":\"default\"}}\n"}, "clusterName":"", "initializers":interface {}(nil), "uid":"26336cc2-d252-11e7-aebb-fa163e62dc0c"}}: validation failure list:
.spec in body is required
```

spec フィールドが存在しないためバリデーションエラーになりました。

このように様々なバリデーションが定義できるようになりました。バリデーションは [OpenAPI v3 schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject) のフォーマットで定義できるため様々なバリデーションが定義できると思います。また、カスタムコントローラで CustomResourceDefinitions のバリデーションの定義を動的に変更することで動的なバリデーションも実現できるのではないかと思います。

# おわりに

今回は v1.8 で追加された CustomResourceDefinition の Validation について紹介しました。バリデーションを追加することでカスタムリソースの作成時にバリデーションエラーが発見できるためよりユーザフレンドリーに CRD を利用できるようになりました。 CRD と CRD を使ったカスタムコントローラを作成することでイベントドリブンで堅牢なアプリケーションが簡単に作成でき、Kubernetes の世界が広がりますので是非利用を検討してみてください。
また、[Z Lab Advent Calendar 2017](https://qiita.com/advent-calendar/2017/zlab) のほうでカスタムコントローラの実装例も紹介する予定ですのでよかったらこちらも参照ください。

# 参考文献

- https://kubernetes.io/docs/concepts/api-extension/custom-resources/
- https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/
- https://coreos.com/blog/custom-resource-kubernetes-v17
- https://github.com/kubernetes/features/issues/95
- https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md