PythonでKubernetes Operatorを作る

# 概要
KubernetesのOperatorはCustomResourceを通してKubernetesの機能を自動化したり拡張したりできる便利な機能です。
Operatorを作成するにあたって主に使われている言語はgoですが、goでしかかけないというわけではありません。
この記事ではPythonでOperatorを作成するフレームワークと、簡単な使い方について紹介していきます。

# kubernetes operatorとは
KubernetesのOperatorは自分で作成したカスタムリソースの定義に従い、DBやwebサーバなどのアプリケーションを自動でデプロイしてくれる拡張機能です。
通常のコンテナの作成に加え、自作したコードに従った自動化を行うことができるため、Kubernetesのデフォルト機能を使うのみに対して、外部クラウドサービスとやKubernetes外のデータベースへの連携といったもう一歩踏み込んだ自動化を実現することができます。
Operatorを作成するフレームワークには[operator-framework](https://github.com/operator-framework/getting-started)などがあり、go言語で作成することが多いです。

参照：kubernetes公式ドキュメント(https://kubernetes.io/ja/docs/concepts/extend-kubernetes/operator/)

# kopfについて
kopf(Kubernetes Operator Pythonic Framework)はPyhonでKubernetes Operatorを作成するためのフレームワークです。
自動化したいコードにデコレータをつけるだけで、Kubernetesのリソース変化に合わせてコードを実行することが可能になっています。

参照：https://github.com/nolar/kopf

# インストール方法
実施環境：
 * Ubuntu18.04
 * python3.7.0

インストールはpipからできます。

```bash
pip install kopf
```

# 使い方
実行方法は簡単で、インストール時に導入されたkopfコマンドで実行したいOperatorのコードを指定するだけです。

```bash
kopf run your_code.py --verbose
```
TIPS:
* OperatorがKubernetesAPIにアクセスするためにKubeconfigを用意する必要があります。デフォルトでは`$HOME/.kube/config`を参照します。
* カスタムリソースを参照するOperatorの場合、Operator起動前にリソースを作成しておかないと404エラーとなります。

# サンプルコード
githubに多数の[サンプルコード](https://github.com/nolar/kopf/tree/master/examples)が用意されているため、基本的にはそこを見ると機能を理解できるかと思います。
以下にサンプルコードの一部を引用して解説していきたいと思います。

#### リソースの作成に反応

```python:kopf/examples/01-minimal/example.py
import kopf


@kopf.on.create('zalando.org', 'v1', 'kopfexamples')
def create_fn(spec, **kwargs):
    print(f"And here we are! Creating: {spec}")
    return {'message': 'hello world'}  # will be the new status
```
kopfでは関数にデコレータをつけることにより、kubernetesのリソースの変化に応じてコードを実行することができます。このサンプルコードでは`kopfexamples`リソースが作成された時に標準出力にリソースの定義を出力します。
また、返り値は作成されたリソースのstatusに保存され、`kubectl describe`コマンドで内容を確認することができます。

#### 子リソースの作成

```python:kopf/examples/02-children/example.py 
import kopf
import pykube
import yaml


@kopf.on.create('zalando.org', 'v1', 'kopfexamples')
def create_fn(spec, **kwargs):

    # Render the pod yaml with some spec fields used in the template.
    doc = yaml.safe_load(f"""
        apiVersion: v1
        kind: Pod
        spec:
          containers:
          - name: the-only-one
            image: busybox
            command: ["sh", "-x", "-c"]
            args: 
            - |
              echo "FIELD=$FIELD"
              sleep {spec.get('duration', 0)}
            env:
            - name: FIELD
              value: {spec.get('field', 'default-value')}
    """)

    # Make it our child: assign the namespace, name, labels, owner references, etc.
    kopf.adopt(doc)

    # Actually create an object by requesting the Kubernetes API.
    api = pykube.HTTPClient(pykube.KubeConfig.from_env())
    pod = pykube.Pod(api, doc)
    pod.create()
    api.session.close()

    # Update the parent's status.
    return {'children': [pod.metadata['uid']]}
```

リソースの作成時に、リソース内容に応じた定義を持ったPodを新たに作成しています。
ポイントは`kopf.adopt(doc)`で、これを実行すると作成したPodの`ownerReferences`フィールドにトリガーとなったリソースの情報が記録されます。
この情報を持つことでトリガーとなったリソースと作成されたPodに親子関係が形成され、親リソースが削除されると子のPodも同時に削除されるようになります。

# まとめ
kopfのフレームワークを使ったPythonでkubernetesのOperator作成を紹介してみました。
goは手をつけてないけどPythonならという方もこれを機にKubernetesのOperator作成をしてみるのはいかがでしょうか。