Kyvernoで特定条件にマッチするKuberentesリソースにMutationポリシーを適用する


# Kyvernoとは？

[Kyverno](https://kyverno.io/)は、Validation(検証機能)とMutation(ミューテーション = 追加、更新、削除)機能を備えたKubernetesポリシーエンジンです。いわゆるPolicy-as-code(PaC)ソリューションの１つです。もともとNirmataからのオープンソースプロジェクトであり、後にCNCFに寄贈されているようです。

https://github.com/kyverno/kyverno

追記 2022/7/13: KyvernoはCNCF incubating Projectになりました - [Kyverno moves to the CNCF Incubator](https://www.cncf.io/blog/2022/07/12/kyverno-moves-to-the-cncf-incubator/)


## Kuberentesにおける代表的なPolicy as Code (PaC)ソリューションの紹介

ちなみに、[2022 InfoQ Trends Report](https://www.infoq.com/articles/devops-and-cloud-trends-2022/) によると、Policy as Code (PaC)は Early Adoptersとして報告されています。参考までにKyverno以外で有名なPolicy as Code (PaC)ソリューションを２つ紹介します。

https://twitter.com/yokawasa/status/1543813746229145600


### Pod Security Policies (PSP)
Kubernetesのポリシー機能といえば、 [Pod Security Policies (PSP)](https://kubernetes.io/docs/concepts/security/pod-security-policy/)があります。 PSPはPodとその属性に対して一連のValidation機能を提供します。その名前のとおり、Podに対する機能であり、Podの作成や更新に対して制御はできるが、Mutation機能は提供しません。残念ながらPSPはKubernetes v1.21でdeprecatedとなり、v1.25で削除の予定となっています。

### Open Policy Agent (OPA) / Gatekeeper

Open Policy Agent (以下、OPA)は汎用的なポリシーエンジン機能を提供するOSSで、Regoと呼ばれるカスタム言語を介してKubernetesだけでなく、さまざまなプラットフォームで汎用的な利用が可能です。これもPolicy-as-code(PaC)ソリューションの１つです。[Gatekeeper](https://github.com/open-policy-agent/gatekeeper)は、Kubernetesに特化したOPAの実装ソリューションであり、リクエストのValidation(検証機能)と、最近ではMutation(ミューテーション = 追加、更新、削除)機能も提供します。


### KyvernoとOPA/Gatekeeperの機能比較

[Kubernetes Policy Comparison: OPA/Gatekeeper vs Kyverno](https://neonmirrors.net/post/2021-02/kubernetes-policy-comparison-opa-gatekeeper-vs-kyverno/)記事に分かりやすい機能比較テーブルがあったのでそれを貼り付けておきます。

![スクリーンショット 2022-07-04 5.30.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103420/c92f2164-bf83-a2b6-45f1-d248df2ff5ff.png)
(Source: [Kubernetes Policy Comparison: OPA/Gatekeeper vs Kyverno](https://neonmirrors.net/post/2021-02/kubernetes-policy-comparison-opa-gatekeeper-vs-kyverno/))

# 事前準備

それではようやく本題であるKyvernoを活用して特定条件にマッチするKuberentesリソースにポリシーを適用する試験を行っていきます。 まずは、デプロイ先となるk8sクラスタとkyvernoのインストールを行います。

## KINDでKubernetesクラスタ作成

k8sクラスタを[kind](https://github.com/kubernetes-sigs/kind) (Kubernetes in Docker)でローカルに構築します。
まずは、次のようなcontrol planeノード x 1、 workerノード x 1のk8sクラスタのためのkindクラスタ構成ファイル(cluster.yaml)を作成します。

```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4 
nodes:
- role: control-plane
- role: worker
```

次に、kind cliコマンドで、k8s v1.21.10のノードイメージでk8sクラスタを作成します。

```bash
K8S_NODE_IMAGE=v1.21.10
kind create cluster --name my-kind-cluster \
--image=kindest/node:${K8S_NODE_IMAGE} \
--config cluster.yaml
```

クラスタ構築が完了したら念の為にクラスタにアクセスしてみます。

```bash
kubectl get node
NAME                            STATUS   ROLES    AGE   VERSION
my-kind-cluster-control-plane   Ready    master   37m   v1.21.10
my-kind-cluster-worker          Ready    <none>   37m   v1.21.10
```

## kyvernoのインストール

[QuickStart](https://kyverno.io/docs/introduction/#quick-start)に従い下記のように作成したクラスタにkyvernoをインストールします。なお、他にもHelm chartからのインストールも可能です。詳しくは[QuickStart](https://kyverno.io/docs/introduction/#quick-start)ください。

```
kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/main/config/install.yaml
```

# kyvernoで特定ネームスペース作成されるPodにラベルを追加する

いよいよ本題の試験を行います。kyvernoのk8sリソースのMutateルール機能をつかって特定ネームスペース作成されるPodにラベルを追加してみます。
なお、mutateルールは、指定する条件にマッチするリソースを変更するために使用でき、以下のRFC　6902　JSONパッチ、もしくはStrategic mergeパッチのいづれかの方式が利用可能です。

- **patchesStrategicMerge**
  - [Strategic Merge Patch](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md)というKubernetes独自のマージの方法
  - 追加、置き換え、削除が可能。patchesJson6902よりはシンプルに記述が可能
- **patchesJson6902**
  -  [JSONPatch - RFC 6902 format](https://jsonpatch.com/)フォーマットでマージ記述が可能。
  -  patchesStrategicMergeと比べて細やかな指定が可能。追加、置き換え、削除が可能


また、補足情報として、ポリシーとルールの関係を説明します。
- ポリシーはルールの集合である。 
- 各ルールは `match`宣言と、オプショナルで`exclude`宣言と,`validate`, `mutate`, `generate`, もしくは`verifyImages`宣言のいづれか1つで構成される
- ポリシーは、クラスター全体のリソース（kind: `ClusterPolicy`）またはネームスペースごとのソース（kind: `Policy`）として定義できる。

![スクリーンショット 2022-07-04 6.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103420/e037171a-51ac-411c-b5ec-30d7eadd5c92.png)
（Source: https://kyverno.io/docs/kyverno-policies/）

## ClusterPolicyポリシーをデプロイ（patchesStrategicMerge）

ここでは、`match`と`mutate`宣言で構成されるルールを含む`ClusterPolicy`リソースを作成します。また、パッチ方式には`patchesStrategicMerge`を指定します。
次のように、`set-label-policy`という名前のClusterPolicyリソースをクラスタにデプロイします。 このClusterPolicyリソースにはネームスペース`testns1`に作成されるPodに、`foo=bar`というラベルを追加するルールを定義しています。

```yaml
kubectl apply -f - << EOF
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: "set-label-policy"
spec:
  rules:
    - name: "set-label"
      match:
        any:
        - resources:
            kinds:
            - Pod
            namespaces:
            - testns1
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              foo: bar
EOF
```

## サンプルPodをデプロイしポリシーが適用されることを確認する

まずは、テストで利用するネームスペース（`testns1`と`testns2`）を作成します。

create namespaces
```yaml
kubectl apply -f - << EOF
apiVersion: v1
kind: Namespace
metadata:
  name: testns1
---
apiVersion: v1
kind: Namespace
metadata:
  name: testns2
EOF
```

次に、サンプルアプリケーションのマニフェストをapp.yamlに出力します。

```yaml
cat << EOF > app.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: httpbin
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      serviceAccountName: httpbin
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80
EOF
```

`testns1`と`testns2`の両ネームスペースにサンプルアプリケーションをデプロイします。

```
kubectl apply -f app.yaml -n testns1
kubectl apply -f app.yaml -n testns2
```

最後に、下記のように両ネームスペースにデプロイされたPodのラベルをチェックします。 

```
kubectl get po -n testns1 --show-labels

NAME                       READY   STATUS    RESTARTS   AGE     LABELS
httpbin-6fdcdc89fc-b9wl6   1/1     Running   0          8m56s   app=httpbin,foo=bar,pod-template-hash=6fdcdc89fc

kubectl get po -n testns2 --show-labels

NAME                       READY   STATUS    RESTARTS   AGE     LABELS
httpbin-75bc8bcf78-p7m4l   1/1     Running   0          9m58s   app=httpbin,pod-template-hash=75bc8bcf78
```

無事、ClusterPolicy`set-label-policy`のルールが適用され、testns1に作成されたPodに`foo=bar`というラベルを追加されていることがわかります。

## patchesJson6902パッチ方式のClusterPolicyポリシー

参考までに、上述しているClusterPolicyポリシーと同じ振る舞いをpatchesJson6902パッチ方式で定義すると次のようになります。

```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: "set-label-policy"
spec:
  rules:
    - name: "set-label"
      match:
        any:
        - resources:
            kinds:
            - Pod
            namespaces:
            - testns1
      mutate:
        patchesJson6902: |-
          - path: "/metadata/labels/foo"
            op: add
            value: "bar"
```


# その他補足情報

## Policy制御のためのオプション

Policyにはルールとは別に下記のPolicyの振る舞いを制御するオプションの設定が可能です。

- **validationFailureAction**: Validationポリシールールを追加した場合、その失敗により、Admission Reviewリクエストをブロック（`enforce`）するか、Admission Reviewリクエストを許可（`audit`）して、ポリシーレポートでポリシーの失敗を報告するかを制御する。 デフォルトは`audit`
- **validationFailureActionOverrides**: ネームスペースごとにClusterPolicy属性として指定するvalidationFailureAction。 特定ネームスペースに指定されたvalidationFailureActionをオーバーライドする。
- **background**: バックグランドのスキャンにより既存のリソースに対してもポリシー適用するかどうかを指定.デフォルトは`true`
- **schemaValidation**: Policyのvalidationチェックを適用するかどうかを制御。 デフォルトは`true`。KyvernoはPolicyのスキーマのValidationを試み、そのリソースのOpenAPIスキーマ定義を満たしていると判断できない場合は失敗する。 ValidationまたはMutationのPolicyが該当。 スキーマのValidationをスキップするには`false`に設定する
- **failurePolicy**: defines the API server behavior if the webhook fails to respond. Allowed values are “Ignore” or “Fail”. Defaults to “Fail”. Webhookが応答失敗した場合のAPIサーバーの挙動を定義。 `Ignore` or `Fail`を指定可能。 デフォルトは`Fail`。
- **webhookTimeoutSeconds**: このPolicy適用のタイムアウトを秒単位で指定。 デフォルトのタイムアウトは`10秒`（値：1〜30秒）

(Source: [Policy Settings]( https://kyverno.io/docs/writing-policies/policy-settings/))

特に個人的に重要だと思ったオプションとして、backgroundがあります。これを`true`にすると、既存のリソースに対してもポリシーの適用ができます。また、validationFailureActionやfailurePolicyも忘れず見ておきたいオプションかと思います。