kubeflowでChainerしてみる

KubeflowでChainerを動かす環境をつくってみます。
途中まではKubeflowのインストール手順になってます。
https://www.kubeflow.org/docs/started/getting-started-gke/

## Kubeflow？

Kubernetes上で機械学習環境を動かすものです。
このへん見てもらった方がいいかも
https://www.slideshare.net/shunyaueta/kubeflow-devfest18

## Chainer?

日本のPreferred Networksが主導で開発している、機械学習のOSSライブラリです。
https://chainer.org/

自分が所属する[AlpacaJapan株式会社](https://www.alpaca.ai/ja/)では、ChainerのエバンジェリストがいてChainerがよく使われているみたいなので、この環境をつくってみました。
https://www.wantedly.com/companies/alpacadb/post_articles/114959



## 手順

k8s環境はGKEに、環境を構築するクライアントはMacという前提です。

### Macの準備

[ksonnet](https://ksonnet.io/) というk8s manifestを管理するフレームワークが必要になるので、brewでインストールします。

```
$ brew install ksonnet/tap/ks
```



## GKEでCloudIAP設定

kubeflowにJupyterHub環境へのログインはGCPの[CloudIAP](https://cloud.google.com/iap/)を使って認証します。

`APIとサービス` → `認証情報` → `OAuth同意画面` を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/aa38ff08-3d77-2d7d-f56d-674ea20f8fad.png)

認証情報のページで下記を入力して保存します。

- アプリケーション名
    - 任意
- サポートメール
    - メールアドレス(自分のが入ってるはず)
- 承認済みドメイン
    - `GCPのProjectID`.cloud.goog
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/73231199-7dae-351c-be51-dd9a0a4c469b.png)

`認証情報を作成` → `OAuthクライアントID` に進みます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/b4347ad4-b274-be3f-c2c4-2191c7f7002c.png)

CloudIAPの情報を入力します。

- アプリケーションの種類
    - ウェブアプリケーション
- 名前
    - 任意
- 承認済みのリダイレクトURI
    - https://`KFAPPの名前`.endpoints.`GCPのProjectID`.cloud.goog/_gcp_gatekeeper/authenticate

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/b0e69683-b40d-1e6d-0b4d-b32a890e0edd.png)

クライアントIDとクライアントシークレットが表示されます。
この情報が次のステップで必要になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/b1fce19c-0673-418f-9444-52df592e4ea7.png)

## k8sクラスタをつくってkubeflowを動かす

ここからはMacでの作業です。
Mac側で環境変数を定義します。

```bash:
$ export PROJECT=#GCPのProjectID
$ export KUBEFLOW_SRC=#kubeflowをインストールするディレクトリ
$ export KUBEFLOW_TAG=v0.3.1
$ export KFAPP=#KFAPPの名前 前ステップの承認済みのリダイレクトURIのKFAPPの名前と合わせる)
$ export CLIENT_ID=#前ステップで出力されたクライアントID
$ export CLIENT_SECRET=#前ステップで出力されたクライアントシークレット
```

kubeflowをダウンロードして、kfctl.shを実行します。

```bash:
$ mkdir ${KUBEFLOW_SRC}
$ cd ${KUBEFLOW_SRC}
$ curl https://raw.githubusercontent.com/kubeflow/kubeflow/${KUBEFLOW_TAG}/scripts/download.sh | bash
```

`kuctl.sh init` でKFAPPを作成します。

```bash:
${KUBEFLOW_SRC}/scripts/kfctl.sh init ${KFAPP} --platform gcp --project ${PROJECT}
cd ${KFAPP}
```

`env.sh` という変数定義ファイルが作成されます。
Zoneやk8sのバージョンなんかはこのファイルで修正できそうです。

```sh:
PLATFORM=gcp
:
K8S_NAMESPACE=kubeflow
KUBEFLOW_CLOUD=gke
:
DEPLOYMENT_NAME=kf
:
ZONE=us-central1-a
:
KUBEFLOW_IP_NAME=kf-ip
KUBEFLOW_ENDPOINT_NAME=kf
:
CLUSTER_VERSION=1.10.9-gke.7
```

`env.sh`をもとに設定ファイルを作成します。

```bash:
$ ${KUBEFLOW_SRC}/scripts/kfctl.sh generate platform
```

`gcp_config`、 `k8s_specs`に設定ファイルができます。

```
gcp_config
├── cluster-kubeflow.yaml
├── cluster.jinja
├── cluster.jinja.schema
└── iam_bindings.yaml
k8s_specs
├── agents.yaml
├── daemonset-preloaded.yaml
└── rbac-setup.yaml
```

GPUインスタンスを使いたいので、`cluster-kubeflow.yaml` の

```
    cpu-pool-initialNodeCount: 2
    gpu-pool-initialNodeCount: 0
```

を

```
    cpu-pool-initialNodeCount: 2
    gpu-pool-initialNodeCount: 2
```

に変更します。
これでCPUノードが2台、GPUノードが2台の計4台のインスタンスが起動します。

Zoneについての注意点なのですが、`gcp_config/cluster.jinja` を見ると、

```
        minCpuPlatform: 'Intel Haswell'
        accelerators:
          - acceleratorCount: 1
            acceleratorType: nvidia-tesla-k80
```

という記述があります。
Zoneによって使えるCPUやGPUが違うので、`env.sh`でZoneを変えるとこのへんも修正しないといけないようです。
https://cloud.google.com/compute/docs/regions-zones/
https://cloud.google.com/compute/docs/gpus/
日本ではまだGPUインスタンスが提供されていないので、`env.sh` を `asia-northeast1-a` にして変更すると次のようなエラーになります。

```
message: '{"ResourceType":"container.v1.nodePool","ResourceErrorCode":"400","ResourceErrorMessage":{"code":400,"message":"Accelerator
    type \"nvidia-tesla-k80\" does not exist in zone asia-northeast1-a.","status":"INVALID_ARGUMENT","statusMessage":"Bad
    Request",・・・
```

下記のコマンドでk8sのクラスタの作成からkubeflowのデプロイまでできます。
完了まで20〜30分かかります。

```bash:
$ ${KUBEFLOW_SRC}/scripts/kfctl.sh apply platform
$ ${KUBEFLOW_SRC}/scripts/kfctl.sh generate k8s
$ ${KUBEFLOW_SRC}/scripts/kfctl.sh apply k8s
```



Compute EngineのところをみるとGPUインスタンスも起動してます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/fc71c1d5-8f27-c556-9ba2-738cef70a55b.png)

`Kubernetes Engine` の `サービス` を開くと、接続を受け付ける `envoy-ingress` が作られますので、ステータスがOKになったら、`https://(KFAPP名).endpoints.(GCPのProjectID).cloud.goog` にアクセスしてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/d26ce1c9-63d2-779e-b7e2-547665e6e011.png)

CloudIAPで認証が通れば、こんな画面が開きますので、JUPYTERHUBを開きます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/155d2451-8f97-8cc6-cb95-8db881e941db.png)

Start My Serverを押します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/95c86be6-8a00-5c77-9646-0f491bdf8413.png)

詳しくは知りませんが、とりあえず `Spawn`を。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/0bd016d8-9711-0f69-06fa-b4900d184f9e.png)

なんかが動いて・・・
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/f4d1187c-81c8-5ec4-7298-4033bcaab398.png)

起動完了！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/3246/f2148a22-0faf-77f4-8293-709e1e8b11a0.png)


## Chainerを動かしてみる

つくったKubeflow環境でChainerを使えるようにしてみます。

手順はここ
https://www.kubeflow.org/docs/guides/components/chainer/

まず、Chainer Operator っていうものをインストールします。

```bash:
$ cd ks_app                                                                                                       $ ks pkg install kubeflow/chainer-job
$ ks generate chainer-operator chainer-operator
```

`environment` を確認します。

```bash:
$ ks env list
NAME    OVERRIDE KUBERNETES-VERSION NAMESPACE SERVER
====    ======== ================== ========= ======
default          v1.10.9            kubeflow  https://35.193.63.167
```

`default` っていう名前らしいので、

```bash:
$ ks apply default -c chainer-operator                                                                          [14:54:05]
INFO Applying customresourcedefinitions chainerjobs.kubeflow.org
INFO Creating non-existent customresourcedefinitions chainerjobs.kubeflow.org
INFO Applying clusterroles chainer-operator
INFO Creating non-existent clusterroles chainer-operator
INFO Applying serviceaccounts kubeflow.chainer-operator
INFO Creating non-existent serviceaccounts kubeflow.chainer-operator
INFO Applying clusterrolebindings kubeflow.chainer-operator
INFO Creating non-existent clusterrolebindings kubeflow.chainer-operator
INFO Applying deployments kubeflow.chainer-operator
INFO Creating non-existent deployments kubeflow.chainer-operator
```                                      

これでインストールは完了です。

ChainerのJobをつくります。

```yaml:example-job-mn.yaml 
apiVersion: kubeflow.org/v1alpha1
kind: ChainerJob
metadata:
  name: example-job-mn
spec:
  backend: mpi
  master:
    mpiConfig:
      slots: 1 
    activeDeadlineSeconds: 6000
    backoffLimit: 60
    template:
      spec:
        containers:
        - name: chainer
          image: everpeace/chainermn:1.3.0
          command:
          - sh
          - -c
          - |
            mpiexec -n 3 -N 1 --allow-run-as-root --display-map  --mca mpi_cuda_support 0 \
            python3 /train_mnist.py -e 2 -b 1000 -u 100
  workerSets:
    ws0:
      replicas: 2
      mpiConfig:
        slots: 1
      template:
        spec:
          containers:
          - name: chainer
            image: everpeace/chainermn:1.3.0
            command:
            - sh
            - -c
            - |
              while true; do sleep 1 & wait; done
```

つくったyamlをk8sに食わせます。

```bash:
$ kubectl create -f example-job-mn.yaml
chainerjob.kubeflow.org "example-job-mn" created

$ kubectl get pods -l chainerjob.kubeflow.org/name=example-job-mn
NAME                             READY     STATUS            RESTARTS   AGE
example-job-mn-master-lv96v      0/1       Init:1/2          0          1m
example-job-mn-workerset-ws0-0   0/1       PodInitializing   0          1m
example-job-mn-workerset-ws0-1   0/1       PodInitializing   0          1m

$ kubectl get pods -l chainerjob.kubeflow.org/name=example-job-mn
NAME                             READY     STATUS        RESTARTS   AGE
example-job-mn-master-lv96v      0/1       Completed     0          1m
example-job-mn-workerset-ws0-0   1/1       Terminating   0          1m
example-job-mn-workerset-ws0-1   1/1       Terminating   0          1m
```

終わったっぽい

```bash:
$ PODNAME=$(kubectl get pods -l chainerjob.kubeflow.org/name=example-job-mn,chainerjob.kubeflow.org/role=master -o name)
$ kubectl logs -f ${PODNAME}
 Data for JOB [34829,1] offset 0

 ========================   JOB MAP   ========================

 Data for node: example-job-mn-workerset-ws0-0  Num slots: 1    Max slots: 0    Num procs: 1
        Process OMPI jobid: [34829,1] App: 0 Process rank: 0 Bound: UNBOUND

 Data for node: example-job-mn-workerset-ws0-1  Num slots: 1    Max slots: 0    Num procs: 1
        Process OMPI jobid: [34829,1] App: 0 Process rank: 1 Bound: UNBOUND

 Data for node: example-job-mn-master-lv96v     Num slots: 1    Max slots: 0    Num procs: 1
        Process OMPI jobid: [34829,1] App: 0 Process rank: 2 Bound: UNBOUND

 =============================================================
Warning: using naive communicator because only naive supports CPU-only execution
Warning: using naive communicator because only naive supports CPU-only execution
Warning: using naive communicator because only naive supports CPU-only execution
==========================================
Num process (COMM_WORLD): 3
Using hierarchical communicator
Num unit: 100
Num Minibatch-size: 1000
Num epoch: 2
==========================================
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.68391     0.898411              0.525          0.812094                  1.47055
2           0.59408     0.393784              0.84855        0.894968                  3.65354

$ kubectl get -o yaml chainerjobs example-job-mn
apiVersion: kubeflow.org/v1alpha1
kind: ChainerJob
metadata:
  clusterName: ""
 :
status:
  completionTime: 2018-12-10T05:58:21Z
  conditions:
  - lastProbeTime: 2018-12-10T05:58:21Z
    lastTransitionTime: 2018-12-10T05:58:21Z
    status: "True"
    type: Complete
  startTime: 2018-12-10T05:56:25Z
  succeeded: 1
```

成功したらしい

### 削除
下記のコマンドでまるっと環境を削除できます。

```bash:
$ cd ${KFAPP}
$ ${KUBEFLOW_SRC}/scripts/kfctl.sh delete platform 
```

GPUインスタンスの消し忘れには注意です！！

## まとめ

とりあえずChainerつかったk8sのJobを走らせるところまではできたみたいなので、うちのML Engineerに試してもらいます。


## ついでに
### AlpacaJapanではエンジニア募集しています！！
https://www.wantedly.com/projects/233473
https://www.wantedly.com/projects/200300
https://www.wantedly.com/projects/217110