Google Cloud Functions(Python)からGoogle Kubernetes Engine(GKE)のジョブを登録する

## 概要

Google Kubernetes Engine(GKE)でバッチジョブを実行することができるのですが、Cloud Functionsからジョブ実行できたらCloud Storageなどのイベントをトリガーにできて捗るなぁと思いついて実際に可能か試してみました。

GKEのジョブ実行については下記を参照ください。

GKEを使ったバッチジョブ実行
http://otiai10.hatenablog.com/entry/2017/12/19/162430


## 前提

下記記事の環境を流用しています。
実際に環境構築する際は、合わせてご参考ください。

Google Kubernetes EngineでUnity ML-Agentsを動かしてみる(V0.5.0対応)
https://qiita.com/kai_kou/items/3a4d860f8353ff1b3110

- ローカルにDockerがインストール済み
- ローカルに```gcloud```、```kubectl``` がインストール済み
- GKEでクラスタ作成済み
- ジョブ実行するDockerイメージがGoogle Container RegistryにPUSH済み


## Cloud Functions関数の実装

Cloud Functionsにデプロイするファイルを用意します。

kubernetesのジョブ登録をPythonから行うのに、公式のClient Libraryを利用しました。

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

kubernetesのClient Libraryが利用できるようにします。

```txt:requirements.txt
kubernetes
```

GKEへジョブ登録するのに必要な情報をYamlファイルとして用意します。
内容は先の記事のものを流用しています。

```yaml:job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: GKEクラスタのpod名(任意)
spec:
  template:
    spec:
      containers:
      - name: unity-ml-agents-on-gke
        image: gcr.io/[GCPのプロジェクトID]/unity_ml_agents_on_gke:latest
        command: ["mlagents-learn", "trainer_config.yaml", "--env", "3DBall", "--train"]
      restartPolicy: OnFailure
```

GKEのクラスタへアクセスできるようにconfigファイルを用意します。

```sh
> gcloud container clusters get-credentials [GKEのクラスタ名]
```

上記コマンドで、クラスタへの接続情報が```~/.kube/config``` ファイルに保存されます。
このファイルをCloud Functionsにデプロイする際に含めるようにします。

ローカル環境でジョブ登録などのコマンドを実行していると、ファイルに```access-token``` 、```expiry``` が含まれている可能性があります。

Cloud Functionsで実行する場合、GKEへアクセスするのに必要な```access-token``` はCloud Functionsのデフォルトのサービス アカウントを参照してくれるので、自前でサービスアカウントを作成する必要はありませんでした。

configファイルにも```access-token``` 、```expiry``` は不要となるので、行ごと削除して問題ありません。

サーバー間での本番環境アプリケーションの認証の設定
https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-python

**configファイルにはクラスタのIPアドレスなどが含まれるため、実運用の際にはCloud KMSなどを利用してファイルを暗号化しておきましょう。**

https://cloud.google.com/kms/


```sh
> cp ~/.kube/config .
```

Cloud Functionsで実行するソースコードです。

```python:main.py
import os
from kubernetes import client, config
import subprocess
import yaml

def create_gke_job(request):

  subprocess.check_output(['cp', './config', '/tmp/config'])
  config.load_kube_config('/tmp/config')

  with open(os.path.join(os.path.dirname(__file__), "./job.yaml")) as f:
    dep = yaml.load(f)

    k8s = client.BatchV1Api()
    resp = k8s.create_namespaced_job(body=dep, namespace="default")
    print("Deployment created. status='%s'" % str(resp.status))
```

ポイントとしては、kubernetesのconfigファイルを```load_kube_config``` メソッドで呼び出すと、ファイルの読み込みだけでなく```access-token``` などの追記がされるので、```/tmp``` ファルダにコピーして書き込み可能にする必要がありました。

job.yamlに定義している、```metadata.name``` がpodの名称となり一意である必要があるため、今回の実装だと、関数を複数回実行すると、pod名の重複エラーとなるため、```yaml.load``` 後に、書き換えが必要となります。


kubernetesのClient Libraryについては、APIが多くて、どのAPIを利用すればよいのか(非常に)わかりにくいですが、ドキュメントがしっかり用意されているので、探せば大抵のことはわかるっぽいです(未確認)

python/kubernetes/README.md
https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md


あとはCloud Functionsにデプロイして良しなに。
今回は関数が実行できたら良いだけので、トリガを```--trigger-http``` にしています。

```sh
> gcloud functions deploy create_gke_job --trigger-http --runtime=python37
```

※いつのまにか```gcloud functions``` コマンドで、```--runtime=python37``` の指定ができるようになっていました。(Google Cloud SDK 216.0.0)


## 参考

GKEを使ったバッチジョブ実行
http://otiai10.hatenablog.com/entry/2017/12/19/162430

Google Kubernetes EngineでUnity ML-Agentsを動かしてみる(V0.5.0対応)
https://qiita.com/kai_kou/items/3a4d860f8353ff1b3110

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

python/kubernetes/README.md
https://github.com/kubernetes-client/python/blob/master/kubernetes/README.md

サーバー間での本番環境アプリケーションの認証の設定
https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-python

Cloud FunctionsでPython利用記事まとめ
https://qiita.com/kai_kou/items/2f65db5305ba280ad81e