KubernetesのPythonクライアントを触ってみる

最初に結論を書いておくと、`kubectl`でやったほうが基本的には楽です。
止むに止まれずPythonからKubernetes操作をする必要が出た方向けです。
使い方を調べてもあまり日本語の記事が出てこなかったので。

使用するのはKubernetes公式のクライアントです。
以下でインストールできます。

```shell
pip install kubernetes
```

https://github.com/kubernetes-client/python

このライブラリは（おそらく）ドキュメントも含めて大部分が自動生成されたものです。
コードが読みにくかったり、知りたい説明が書いてなかったりするのが辛いポイントです。
一方で、examplesは結構充実しているのでそこを見れば色々解決する可能性は高いです。

# コード例

動かす前にKubernetesの設定ファイルを用意する必要があります。
`~/.kube/config`にファイルが置いてある状態であれば大丈夫です。
例として載せるコードでは以下でkubeconfigを読み込んだ前提で書いていきます。
また、マニフェストファイルをPythonの辞書として読み込んでおく必要があるのでそれも行います。
マニフェストファイルの内容はDeploymentの話だったら`kind: Deployment`に合わせた内容みたいな感じで、それぞれのコードの内容に合わせていい感じに書いてあるものとし、namespaceは全て`default`とします。

```python
import yaml
from kubernetes import config, client

config.load_kube_config()
with open('path/to/manifest.yaml') as f:
    manifest = yaml.safe_load(f)
namespace = 'default'
```

ちなみにリソース作成については以下で書く話を全部やってくれそうなサンプルコードがあったのでそれでいいかもしれません。
以下の`apply_from_*.py`系です。

https://github.com/kubernetes-client/python/tree/master/examples

## Deploymentを操作する

Deploymentの操作は`AppsV1Api`から行えます。
[ドキュメント](https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md) 上を操作したいリソースの名前で検索するとどのAPIのクラスを使えばいいかわかります。

```python
api = client.AppsV1Api()

# Deploymentを作成
api.create_namespaced_deployment(namespace, manifest)

# namespace内のDeployment一覧を取得
api.list_namespaced_deployment(namespace)

# 作成したDeploymentを削除
name = 'metadata.nameで設定した名前'
api.delete_namespaced_deployment(name, namespace)
```

createで返却される`V1Deployment`を受け取って色々情報を見るとかもできます。

## Serviceを操作する

Serviceの操作は`CoreV1Api`から行えます。

```python
api = client.CoreV1Api()

# Serviceを作成
api.create_namespaced_service(namespace, manifest)

# namespace内のService一覧を取得
api.list_namespaced_service(namespace)

# 作成したServiceを削除
name = 'metadata.nameで設定した名前'
api.delete_namespaced_service(name, namespace)
```

createで返却される`V1Service`を受け取って色々情報を見るとかもできます。
もうおわかりかと思いますが、どのリソースについても同じような書き方で操作できます。
操作説明の記事も自動生成できそう。

## Custom Objectを操作する

Kubernetesにはデフォルトで使えるDeploymentやService以外の独自のリソースを定義できます。
ここでは例としてSpark Operatorを操作してみます。

https://github.com/kubeflow/spark-operator

Custom Objectの操作は`CustomObjectsApi`から行えます。
今までよりも少し引数が増えます。

```python
# カスタムリソースの種類を特定するための情報
group = 'sparkoperator.k8s.io'
version = 'v1beta2'
plural = 'sparkapplications'

api = client.CustomObjectsApi()

# Custom Objectを作成
api.create_namespaced_custom_object(group, version, namespace, plural, manifest)

# namespace内のCustom Object一覧を取得
api.list_namespaced_custom_object(group, version, namespace, plural)

# 作成したCustom Objectを削除
name = 'metadata.nameで設定した名前'
api.delete_namespaced_custom_object(group, version, namespace, plural, name)
```

## リソースが特定の状態になるまで待つ

`kubectl wait`相当のことができます。
以下ではSpark Operatorで作成された特定のSpark Applicationのステータスが`RUNNING`になるのを待ち、そのSpark ApplicationのIDを取得するコードです。

```python
from kubernetes import watch

# カスタムリソースの種類を特定するための情報
group = 'sparkoperator.k8s.io'
version = 'v1beta2'
plural = 'sparkapplications'
name = 'metadata.nameで設定した名前'

api = client.CustomObjectsApi()

# Custom Objectを作成
api.create_namespaced_custom_object(group, version, namespace, plural, manifest)

# ステータスがRUNNINGになるまで待機
# field_selectorでnameを絞り込んでいる
w = watch.Watch()
for event in w.stream(
    api.list_namespaced_custom_object,
    group=group,
    version=version,
    namespace=namespace,
    plural=plural,
    field_selector=f'metadata.name={name}'
):
    if 'status' in event['object'] and event['object']['status']['applicationState']['state'] == 'RUNNING':
        spark_application_id = event['object']['status']['sparkApplicationId']
        w.stop()
```

試したのはCustom Objectだけですが、一覧を取得する`list_namespaced_custom_object`はwaitで使えて特定のオブジェクトを取得する`get_namespaced_custom_object`は使えなかったので、使えるのは`list_*`系だけかもしれません。

# おまけ: プロキシを経由してクラスタにアクセスする

Kubernetesクラスタのアクセスにプロキシを経由する必要がある場合、自分の環境ではそのままだとできませんでした。
あまりきれいではないですが回避策を見つけたので書いておきます。


```python
from kubernetes import config, client

config.load_kube_config()
client.Configuration._default.proxy = 'プロキシのURL'
```

issueでも同じ解決をしてたので他にないかもしれませんが、もっといいやり方があれば教えて下さい。

https://github.com/kubernetes-client/python/issues/333