Kyvernoをさわってみた

# この記事について
業務でKyvernoを使ってますが、一部機能しか使っておらず
全機能を把握できてないため調査のためにこの記事を書いてます。

# Kyvernoとは
KyvernoとはKubernetes向けのポリシーエンジンです。
ポリシーとはk8sリソースを作成する際のルールです。
例えば・・・

* Podには必ず○○というLabelがついていないといけない
* latestタグの利用禁止
* などなど

OpenPolicyAgentとの大きな違いとして、
Regoのような特別な言語を用いずにポリシーを定義できる点だと思います。

## Kyvernoでできること
* リソースが新規作成された際に別のリソースを新規作成できる
* リソースが新規作成・更新された際にパッチを当てられる
* リソースの作成された際に、ポリシーに則ってるか検証し、作成を拒否することができる

これらの機能をそれぞれ触ってみたいと思います！

# インストール
マニフェストがGitHubで用意されてるのでそれを使います。
ちなみに今回使うKyvernoのバージョンは `v1.4.1` です。

```bash
$ kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno/v1.4.1/definitions/install.yaml
```

# さわってみた
## Generate Resources
Generate Resourcesとは、リソースの新規作成 or 更新されたのをトリガーに
新たなリソースを作成する機能です。
https://kyverno.io/docs/writing-policies/generate/

とりあえずやってみましょう！

各Namespaceで同じSecret情報を使いたいことがあると思います。
そういうシーンを想定して、Namespaceが作成されたのをトリガーにすでに存在するSecretをコピーするポリシーを書いてみます。

まずコピー元となるSecretを`default` namespaceに作成します。

```yaml:secret-sample.yaml
apiVersion: v1
kind: Secret
metadata:
  name: sample-secret
  namespace: default
type: Opaque
data:
  password: aG9nZQo= # hoge
```

次にこのSecretをコピーするポリシーを定義します。

```yaml:secret-copy-policy.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: sync-secrets
  annotations:
    policies.kyverno.io/title: Sync Secrets
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Secret
    policies.kyverno.io/description: >-
      copy sample secret from default namespace to requested namespace.
spec:
  background: false
  rules:
  - name: sync-sample-secret
    match:
      resources:
        kinds:
        - Namespace
    generate:
      kind: Secret
      name: sample-secret
      namespace: "{{request.object.metadata.name}}"
      synchronize: true
      clone:
        namespace: default
        name: sample-secret
```

ではNamespaceを作成して動作を確認してみましょう。

```bash
$ kubectl create namespace test
namespace/test created

# 関係あるところだけ抜粋
$ kubectl get secret sample-secret -o yaml -n test
apiVersion: v1
data:
  password: aG9nZQo=
kind: Secret
metadata:
  name: sample-secret
  namespace: test
type: Opaque
```

ということで、やりたいことは出来ました。

### 疑問
やってみて出てきた疑問とその答えを書きます。

#### spec.rules.generate.synchronizeとは？
今回のサンプルでは `synchronize: true` としました。これは一体なにを意味するのでしょう？

今回コピーして作られたリソースはソースコード管理ができません。
マニフェストをYamlで定義してGit管理してGitOpsをしてる場合、このリソースは直接編集できてしまいます。
それを防止するために、`synchronize` を `true` にすると、コピー元のリソースと同期をしてくれます。

```bash:コピー先を編集して同期されるか確認する
# コピーしたSecretリソースのパスワード部分変更
$ k edit secret sample-secret -n test
secret/sample-secret edited

# コピー元
$ k get secret sample-secret -o yaml -n default | grep password | head -1
  password: aG9nZQo=

# コピー先。editで更新したのに元に戻ってる
$ k get secret sample-secret -o yaml -n test | grep password | head -1
  password: aG9nZQo=
```

さて、先ほどはコピー先を編集してみましたが、コピー元を編集するとどうなるでしょうか。

```bash:コピー元を編集して同期されるか確認する
# コピー元のSecretリソースのパスワード部分変更
$ k edit secret sample-secret -n default
secret/sample-secret edited

# コピー元
$ k get secret sample-secret -o yaml -n default | grep password | head -1
  password: YWFhCg==

# コピー先
$ k get secret sample-secret -o yaml -n test | grep password | head -1
  password: YWFhCg==
```

こちらも同様に同期してくれます。便利ですね！

#### コピー（Clone）以外にできることは？
今回はCloneという機能を使いました。

```yaml
clone:
   namespace: default
  name: sample-secret
```

あとはマニフェストの定義をそのまま書くことも出来ます。

```yaml
    generate:
      synchronize: true
      kind: ConfigMap
      name: zk-kafka-address
      # generate the resource in the new namespace
      namespace: "{{request.object.metadata.name}}"
      data:
        kind: ConfigMap
        metadata:
          labels:
            somekey: somevalue
        data:
          ZK_ADDRESS: "192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181"
          KAFKA_ADDRESS: "192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092"
```

## Mutate Resources
Mutate Resourcesとは、マッチしたリソースに対してパッチをあてる機能です。
https://kyverno.io/docs/writing-policies/mutate/

ではさわって行きたいと思います。
今回想定するシーンは、「あるNamespace配下のPodには必ず共通するConfigMapを読み込ませたい」です。

ではまずNamespaceを作成しましょう。

```bash
$ kubectl create namespace test2
namespace/test2 created
```

次に読み込ませたいConfigMapを作成します。

```yaml:configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-configmap
  namespace: test2
data:
  environment_name: staging
```

次に `test2` 配下のPodに対して上記ConfigMapを読み込ませるポリシーを書きましょう。
ConfigMapに記載されている値が環境変数になるような感じで！

```yaml:add-configmap-policy.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-configmap-policy
  annotations:
    policies.kyverno.io/title: add configmap
    policies.kyverno.io/category: Sample
    policies.kyverno.io/subject: Pod
    policies.kyverno.io/description: >-
      Add env to pods which place to test2 namespace.
spec:
  rules:
  - name: "Set common environment variables"
    match:
      resources:
        kinds:
        - Pod
        namespaces:
        - test2
    mutate:
      overlay:
        spec:
          containers:
          - (name): "*"
            env:
              - name: ENVIRONMENT
                valueFrom:
                  configMapKeyRef:
                    name: sample-configmap
                    key: environment_name
```

最後にテスト用のDeploymentを作成します。

```yaml:test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
  namespace: test2
spec:
  selector:
    matchLabels:
      app: test
  replicas: 1
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
```

このDeploymentで作成されるPodに対してConfigMapで定義した値を環境変数に設定する設定が加わってるはずです。
見てみましょう。

```bash
$ kubectl describe pod -n test2 test-deployment-c49854788-s2wg4 | grep -i ENVIRONMENT
    Environment:
      ENVIRONMENT:  <set to the key 'environment_name' of config map 'sample-configmap'>  Optional: false
```

このように自動でenv設定を挿入するような設定が出来ました。

### 疑問
#### spec.rules.mutate.overlayとは？
mutateは以下の3つのパッチの当て方を指定できます。

* overlay
    *  追加・置き換えたいマニフェストを記述する方法です
* Strategic Merge Patch
    * overlayと同じく、追加や置き換えの記述が出来ます
    * 削除も可能です
* RFC 6902 JSONPatch
    * リソースに対して json patchを当てる方法です。kustomizeでいう `patchesJson6902`
    * Strategi Merge Patchでは実現できない更新をしたいときに使います
        * 例えばarrayの一部要素を書き換えたいとか


#### matchで指定できるものって？
ここに詳しく書いてあります。
https://kyverno.io/docs/writing-policies/match-exclude/

リソースの名前やラベルでも対象を制限できますし、除外設定も行えます。

## Validate Resources
新規・既存リソースが定義したポリシーを満たしているかを検査する機能です。

今回想定するシーンは「Podには必ずresources.requests/limitsを入れる。入れてない場合Podを生成しない」です。
まずその制限をするポリシーを書きます。

```yaml:require-req-limit-policy.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: all-containers-need-requests-and-limits
spec:
  validationFailureAction: enforce
  background: false
  rules:
  - name: check-container-resources
    match:
      resources:
        kinds:
        - Pod
        namespaces:
        - test2
    validate:
      message: "All containers must have CPU and memory resource requests and limits defined."
      pattern:
        spec:
          containers:
          - name: "*"
            resources:
              limits:
                memory: "?*"
                cpu: "?*"
              requests:
                memory: "?*"
                cpu: "?*"
```

mutateのとき使ったDeploymentを適用してみます。
すると・・・

```bash
$ kubectl apply -f test.yaml
Error from server: error when creating "test.yaml": admission webhook "validate.kyverno.svc" denied the request:

resource Deployment/test2/test-deployment was blocked due to the following policies

all-containers-need-requests-and-limits:
  autogen-check-container-resources: 'validation error: All containers must have CPU
    and memory resource requests and limits defined. Rule autogen-check-container-resources
    failed at path /spec/template/spec/containers/0/resources/limits/'

# Podは作成されていない
$ kubectl -n test2 get pods
No resources found in test2 namespace.
```

ということで、やりたいことは実現できました。

### 疑問
#### spec.backgroundとは？
ここを `true` にすると、既存リソースに対してもポリシーのチェックを行うようになります。
https://kyverno.io/docs/writing-policies/background/

既存リソースがポリシー違反になった場合、既存リソースはそのままですが、
`ClusterPolicyReport` または `PolicyReport` というリソースが作成されます。

```bash:既存リソースがポリシー違反になった例
$ kubectl get policyreport -A
NAMESPACE   NAME            PASS   FAIL   WARN   ERROR   SKIP   AGE
test2       polr-ns-test2   0      1      0      0       0      67s
```

中身を見てみると、エラーになった理由が記載されています。

この `spec.background` はデフォルトはtrueで、mutateやgenerateには影響ない項目です。

#### spec.validationFailureActionで指定できるものは？
* enforce
    * ポリシー違反の場合、新規リソースの場合は、そのリソースは作成されません
* audit
    * ポリシー違反の場合、`ClusterPolicyReport` または `PolicyReport` というリソースが作成されます
    * ポリシー違反でもそのリソースは作成されます

## その他便利機能
今回はさわってないですが、使いこなすときに便利そうな機能があったので紹介しておきます。

### 変数
リクエストされたリソースの情報を変数として使えます。
https://kyverno.io/docs/writing-policies/variables/

またConfigMapの情報も呼び出せます。
これはConfigMapで定義した値を元にValidationを行ったりすることができるというものです。
https://kyverno.io/docs/writing-policies/external-data-sources/

### 前提条件
`match` や `exclude` よりもさらに細かい条件指定を行いたい場合に
`preconditions` というものが使えます。
https://kyverno.io/docs/writing-policies/preconditions/