Kyvernoとは：CNCF LandscapeのSandbox Projectに飛び込む

CNCF Landscape内のあるプロジェクトで活動の第一歩を踏み出したので、その模様をお伝えします。本記事が同じような興味を持ってる方の参考になればと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/d1a62175-8a52-58b2-1f06-71f435f32f2d.png)

# 本記事で得られること

* CNCFの新しいSandbox Projectである**Kyverno**の概要
* 成長が早いCloud Native界隈のOSSプロジェクトに参加するとどうなるか、の疑似体験

# 本活動の動機

近頃Cloud Native周辺の動向をウォッチしている。本も買ってお勉強したり、オンラインになったのを機にKubeConに参加して色々セッションを聴いていると、何か自分で手を動かしてみたくなってくるのだが、特に仕事でもCloud Nativeな技術(K8s)に触れる機会は今のところなく、個人的にマイクロサービスとしてK8s上で動かして何かしたい、ということも見当たらない。ということで、数あるCNCFのプロジェクトのどれかに**個人的に**参加してみることにした。が、星の数ほどあるCNCF Landscape上のプロジェクトの中から、参加するプロジェクトを決めるだけで悩ましい。ということで、CNCFに加わって一番日の浅いSandbox Projectの中から選択してみることにした。

https://www.cncf.io/sandbox-projects/
https://github.com/cncf/toc/blob/master/docs/PROJECTS.csv

| Project                                      | Accepted   | Stage   |About|
|----------------------------------------------|------------|---------|---------|
| [SchemaHero](https://github.com/schemahero/schemahero)                                   | 11/10/2020 | Sandbox |A Kubernetes operator for declarative database schema management|
| [Cloud Development Kit for Kubernetes (cdk8s)](https://github.com/awslabs/cdk8s) | 11/10/2020 | Sandbox |Define Kubernetes native apps and abstractions using object-oriented programming|
| [cert-manager](https://github.com/jetstack/cert-manager)                                 | 11/10/2020 | Sandbox |Automatically provision and manage TLS certificates in Kubernetes|
| [OpenKruise](https://github.com/openkruise/kruise)                                   | 11/10/2020 | Sandbox |Automate everything on Kubernetes|
| [Tinkerbell](https://github.com/tinkerbell/tink)                                   | 11/10/2020 | Sandbox |a flexible bare metal provisioning engine|
| [Pravega](https://github.com/pravega/pravega)                                      | 11/10/2020 | Sandbox |Streaming Storage Platform|
| [Kyverno](https://github.com/kyverno/kyverno)                                      | 11/10/2020 | Sandbox |Kubernetes Native Policy Management|

利用言語とプロジェクトがターゲットとしている技術エリア、コミュニティの規模などから、**Kyverno**をチョイスした。素人ながらPolicy managementにおいて現在Open Policy Agent(OPA)というのがソリューションの１つとして良く目にするが、OPAがK8sに限らず利用できるソリューションであるのに対して、K8s Nativeな形で機能を提供するという方向性にチャレンジ精神を感じたのも選択理由の１つだったりする。

# Kyvernoとは

Kubernetesの中ではPolicy Mangementといくつか種類があり、ポッド間のネットワークトラフィックをセキュアに保つための`Network Policy`、ポッドの権限やアクセスポリシーを制御する`Pod Security Policy`、リソースの使用量を管理する`Quotas and Limit Range`がある。Kyvernoでカバーする範囲は`Pod Security Policy`に該当し、Kubernetes上で動かす各ワークロードの設定をクラスタ全体で一貫したルールに則って運用したい場合に向いている。近年では、各企業が社内の開発部隊向けに自社のKubernetesプラットフォームを立ち上げて運用しているケースが多くある。このような社内システムを運用するプラットフォームチームが、社内の運用ルールを全チーム横断で展開する場合などに、本機能が有効である。

たとえば、[Kubernetesクラスタをセキュアにするために実施すべき10のプラクティス](https://thenewstack.io/10-kubernetes-best-practices-you-can-easily-apply-to-your-clusters/)などをポリシーとしてリソース化して、あらゆる操作に当てはめると漏れなく、最低限の対策が展開できる。


# まずはコミュニティにアクセスしてみる

コミュニケーションのチャネルはGoogle Group(ML)とSlackと月１回のCommunity Meetings。
Google GroupはMeeting連絡程度にしか使われておらず、Slackが主な模様。slack.k8s.io にサインアップして `#kyverno` チャンネルへ。

すぐにレスポンスをくれて、`good first issue`なissueを更新してくれて、手の付けやすいissueの用意までしてくれた。とてもinclusiveである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/8bbb9daf-0f55-1df5-37d4-f99fbfba00f7.png)

ということでログレベルの修正タスクからチャレンジしてみることにする。

* [[BUG] Turn PreConditions error to info log · Issue #1256 · kyverno/kyverno](https://github.com/kyverno/kyverno/issues/1256)

# 環境のセットアップ

## kindでclusterを立ち上げる

今まではDocker Desktop For WindowsのKubernetesクラスタをWSL2環境から使っていたが、最近観た以下のKubeConのチュートリアルセッション(おすすめ!)で使っていた[kind](https://kind.sigs.k8s.io/)上で`kyverno`を触ってみる。

* [Tutorial: Migration 101: From VMs to Kubernetes - Luke Kysow, HashiCorp - YouTube](https://youtu.be/MuX3m149FpI)

## kyvernoのインストール

Custom Resource DefinitionであるKyvernoはhelmで簡単にインストールができた

* [Resources | Kyverno](https://kyverno.io/docs/installation/)

## ポリシーリソースの構造を簡単に学ぶ

![](https://kyverno.io/images/Kyverno-Policy-Structure.png)

上記の図で十分ではあると思うが、`Policy`は１つ以上の`Rule`ブロックで構成されており、各`Rule`ブロックでは適用するリソースの条件を`Match`ブロックに記述し、ヒットした際のアクションを`Mutate`,`Validate` or `Generate`で記述する構造になっている。

## 簡単な動作テスト

Kubernetes上で動かすアプリケーションには以下のようなラベルをメタデータとして持つことを推奨しているが、決して必須ではない。ラベルがないからクラスタ上で動作しない、ということはない。が、組織内の運用上のルールで、外部ツールとの連携や効率的な情報のクエリを可能にするために、特定のメタデータについては入力必須とするようなポリシーをクラスタ横断的に制定したい場合の例である。

* [Recommended Labels | Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/)

クラスタ上のあらゆる`Pod`に対して、`app.kubernetes.io/name`のラベル付与を強制するポリシーになる。

```yaml
# require-labels.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: enforce
  rules:
  - name: check-for-labels
    match:
      resources:
        kinds:
        - Pod
    validate:
      message: "label `app.kubernetes.io/name` is required"
      pattern:
        metadata:
          labels:
            app.kubernetes.io/name: "?*"
```

これを適用する。

```shell
$ kubectl create -f require-labels.yaml
clusterpolicy.kyverno.io/require-labels created
```

試しにメタデータを一切付与せずにコマンドラインからnginxのdeploymentを作成すると、以下の通り`Validation error`となって、deploymentに失敗する。`validationFailureAction`を`audit`にすると警告ログだけ残してdeploymentは成功するようにもできる。

```shell
$ kubectl create deployment nginx --image=nginx
error: failed to create deployment: admission webhook "nirmata.kyverno.resource.validating-webhook" denied the request:

resource Deployment/default/nginx was blocked due to the following policies

require-labels:
  autogen-check-for-labels: 'Validation error: label `app.kubernetes.io/name` is required; Validation rule autogen-check-for-labels failed at path /spec/template/metadata/labels/app.kubernetes.io/name/'
```

一方、同じコマンドにラベルを付与するオプションを追加するとエラーもなく、deploymentによってpodの生成に成功する。

```shell
$ kubectl run nginx --image nginx --labels app.kubernetes.io/name=nginx
pod/nginx created
```

# まずは問題の再現から

issueは、ログレベルを`error`から`info`に修正するというものだ。まずは問題の事象を再現させて、それでコレをこんな風に直すよ、みたいな感じでコミュニティと会話しながら進めていこうと思う。K8s初級者にとっては、1つ目のハードルである事象の再現だ。

* [Create Pod Anti-Affinity](https://github.com/kyverno/kyverno/blob/main/samples/CreatePodAntiAffinity.md)

早速出てきた`Anti Affinity`であるが、アプリケーションの可用性を少しでも高めるためにレプリカは、異なるホスト上に配置すべし、というプラクティスであると理解。逆に通信性能観点で同一ゾーンへの展開を強制するのが`Affinity`らしい。

* [Inter-pod affinity and anti-affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity)

再現テストをするのに、さっそくローカルのクラスタを複数ノード構成に手直しが必要か。といっても簡単に変更できた。

```
$ cat kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
$ kind create cluster --config ./kind.yaml
$ k get nodes
NAME                 STATUS   ROLES    AGE    VERSION
kind-control-plane   Ready    master   117s   v1.19.1
kind-worker          Ready    <none>   87s    v1.19.1
kind-worker2         Ready    <none>   87s    v1.19.1
```

まずは何もポリシー適用せずに複数レプリカをデプロイしてみる。

```yaml
# multi-replicas-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: busybox
    distributed: required
  name: busybox
spec:
  replicas: 2
  selector:
    matchLabels:
      app: busybox
      distributed: required
  template:
    metadata:
      labels:
        app: busybox
        distributed: required
    spec:
      containers:
      - image: busybox:1.28
        name: busybox
        command: ["sleep", "9999"]
```

すると既に各ノードに分散してポッドがデプロイされていることが分かる。何回か試したが、毎回各ノードにポッドが１つずつデプロイされる。ポリシーがなくても良くないか?

```shell
$ kubectl apply -f multi-replicas-deploy.yaml
deployment.apps/busybox created
$ kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
busybox-6c4fc5bbb7-cxnql   1/1     Running   0          14s   10.244.1.8   kind-worker2   <none>           <none>
busybox-6c4fc5bbb7-hnppd   1/1     Running   0          14s   10.244.2.6   kind-worker    <none>           <none>
```

とりあえず、一度deploymentを削除して[anti-affinityを強制するポリシー](https://github.com/kyverno/kyverno/blob/main/samples/CreatePodAntiAffinity.md#policy-yaml)を適用してみる。結果はポリシー適用前と変わらなかったが、このポリシーが条件にヒットしたリソースをmutateするルールなので、deploymentのマニフェストを再確認するとコマンドからapplyしたマニフェストに対して、anti-affinityの設定が追加されていることが分かった。

```shell
$ git clone https://github.com/kyverno/kyverno.git
$ kubectl apply -f kyverno/samples/more/create_pod_antiaffinity.yaml
$ kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
busybox-847bbfdbdc-w465p   1/1     Running   0          27s   10.244.1.9   kind-worker2   <none>           <none>
busybox-847bbfdbdc-w55ft   1/1     Running   0          27s   10.244.2.7   kind-worker    <none>           <none>
$ kubectl get deployments.apps busybox -o yaml
    ...
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - busybox
              topologyKey: kubernetes.io/hostname
            weight: 1
    ...
```

で、この状態からkyvernoのログを確認すると、issueにあった症状が確認されました。

```
E1211 17:34:12.069581       1 notequal.go:36] EngineMutate "msg"="Failed to resolve variable" "error"="could not find variable request.object.metadata.labels.app at path " "kind"="Deployment" "name"="kyverno" "namespace"="kyvernotest" "policy"="insert-podantiaffinity" "rule"="insert-podantiaffinity" "variable"=null
```

ログの出力個所から辿って、プログラムの構造も少しだけ理解したので、ログレベルを`info`に変えて動かしてみることにする。が、ソースからkyvernoをインストールする方法が分からない。から、さっそくコミュニティに聞いてみることにした。

* Github上のwiki曰く、`kustomize`をインストールしてレポジトリのルートにて`make release`を実行すると、ローカルに`definitions/install.yaml`ができるので、これを改めapplyするとローカルビルドのkyvernoがデプロイできるようである。
  * https://github.com/kyverno/kyverno/wiki/Generate-a-Release

# 最後に

時間の都合上、issueの途中で本記事は一旦終わることにする。記事前半で見て頂けたかと思うが、コミュニティは非常に反応が早く、急に現れたKubernetes初級者の私にも時間を割いて丁寧に対応してくれた（と思っている）。新しいプロジェクトに限らず、もし興味があるプロジェクトがあるようであれば、門戸を叩いてみることを是非お勧めしたい。
本記事はissueクローズまで五月雨ではあるが更新していきたい。