microk8sとkubeflowで機械学習開発基盤を構築(MLOps)

microk8sとkubeflowを使って機械学習開発基盤を構築します(いわゆるMLOps)。
kubernetesを試してみるにあたってはminikubeがよく利用されてると思いますが、将来的にきちんとしたマルチノードクラスタを構築したいなと思ったのでmicrok8sを使っています。

## 1.Install microk8s
まずはmicrok8sをインストールしましょう。  
今回は1.20をインストールします。  
もっと新しいバージョンもありますが、gpuのaddonがうまく動かなかったりするので安定動作した1.20を利用します。
```bash
$ sudo snap install microk8s --classic --channel=1.20/stable
```
そしてユーザにmicrok8sを操作するための権限を付与しましょう
```bash
$ sudo usermod -a -G microk8s $USER
$ sudo chown -f -R $USER ~/.kube
```
ここからはmicrok8sのコンポーネントの設定をしていきます。  
まずはkubernetesのAPIを提供しているコンポーネントであるkube-apiserverを設定します。
```bash
$ sudo vim /var/snap/microk8s/current/args/kube-apiserver
#以下の項目を追加
######
#~Enable the aggregation layer
--enable-admission-plugins=AlwaysPullImages
--service-account-signing-key-file=${SNAP_DATA}/certs/serviceaccount.key
--service-account-issuer=kubernetes.default.svc
--feature-gates=TTLAfterFinished=true
```

続いて、Podを管理するkubeletの設定をします。
```bash
$ sudo vim /var/snap/microk8s/current/args/kubelet
###既存の内容を書き換え
--container-runtime=docker
#--container-runtime-endpoint=${SNAP_COMMON}/run/containerd.sock　コメントアウト
### 追加
--cluster-domain=cluster.local
--cluster-dns=10.152.183.10
$ exit # re login
```

設定を反映するためにmicrok8sを再起動します。
```bash
$ microk8s stop
$ microk8s start
```

microk8sでdnsやgpuを利用するための設定を実施します。
```bash
$ microk8s enable dns storage gpu
```

## 2.Install kubeflow
ここからはkubeflowをインストールしていきます。  
まず、kubeflowを動かすための設定を行なっていきます。
```bash
$ sudo microk8s.kubectl config view --raw > ~/.kube/config
$ export KUBECONFIG=$HOME/.kube/config
$ cd ~
$ wget https://github.com/kubeflow/kfctl/releases/download/v1.2.0/kfctl_v1.2.0-0-gbc038f9_linux.tar.gz
$ tar -xvf kfctl_v1.2.0-0-gbc038f9_linux.tar.gz
$ export PATH=$PATH:$HOME
$ export BASE_DIR=/opt/
$ export KF_NAME=k8s
$ export KF_DIR=${BASE_DIR}/${KF_NAME}
$ export CONFIG_URI="https://raw.githubusercontent.com/kubeflow/manifests/v1.2-branch/kfdef/kfctl_istio_dex.v1.2.0.yaml"
$ mkdir ~/kf_installation_temp
$ cd ~/kf_installation_temp
$ wget $CONFIG_URI
$ kfctl build -V -f $CONFIG_URI
```

ユーザ認証の設定を行います。
```bash
$ vim ~/kf_installation_temp/kustomize/dex/kustomization.yaml
#以下のように設定する
# <your ip address>はkubeflowをインストールするPCのIPアドレスを指定してください
apiVersion: kustomize.config.k8s.io/v1beta1
configMapGenerator:
- behavior: merge
  literals:
  - issuer="http://<your ip address>/dex"
  name: dex-parameters
kind: Kustomization
resources:
- ../../.cache/manifests/manifests-1.2.0/stacks/kubernetes/application/dex-auth
```
```bash
$ vim ~/kf_installation_temp/kustomize/oidc-authservice/kustomization.yaml
#以下のように設定する
# <your ip address>はkubeflowをインストールするPCのIPアドレスを指定してください
apiVersion: kustomize.config.k8s.io/v1beta1
configMapGenerator:
- behavior: merge
  literals:
  - oidc_provider=http://<your ip address>/dex
  name: oidc-authservice-parameters
kind: Kustomization
resources:
- ../../.cache/manifests/manifests-1.2.0/stacks/kubernetes/application/oidc-authservice
```

一部docker imageがgcrから消されてしまったので、image名の訂正をします。
```bash
$ vim ~/kf_installation_temp/.cache/manifests/manifests-1.2.0/kfserving/upstream/kfserving.yaml
4429行目gcr.io/kfserving/kfserving-controller:v0.4.1をkfserving/kfserving-controller:v0.4.1に変更 
# vim苦手な方人向け　4429を入力後Gを入力すると飛べます vscode等で操作してもらっても大丈夫です！
$ vim ~/kf_installation_temp/.cache/manifests/manifests-1.2.0/kfserving/installs/generic/kustomization.yaml
22,23行目のgcr.io/kfserving/kfserving-controllerをkfserving/kfserving-controllerに変更
```

docker imageの変更内容を設定ファイルに書き込みます。  
複数の変更箇所を指し示すのはしんどいので、ファイル全体を記載します。  
コピペして設定ファイルを完成させてください。
```basd
$ rm rm kfctl_istio_dex.v1.2.0.yaml
$ vim kfctl_istio_dex.v1.2.0.yaml
# 以下をコピペ
apiVersion: kfdef.apps.kubeflow.org/v1
kind: KfDef
metadata:
  clusterName: microk8s-cluster
  creationTimestamp: null
  name: kf_installation_temp
  namespace: kubeflow
spec:
  applications:
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: namespaces/base
    name: namespaces
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: application/v3
    name: application
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/istio-1-3-1-stack
    name: istio-stack
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/cluster-local-gateway-1-3-1
    name: cluster-local-gateway
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: istio/istio/base
    name: istio
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/cert-manager-crds
    name: cert-manager-crds
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/cert-manager-kube-system-resources
    name: cert-manager-kube-system-resources
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/cert-manager
    name: cert-manager
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: metacontroller/base
    name: metacontroller
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/oidc-authservice
    name: oidc-authservice
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes/application/dex-auth
    name: dex
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: admission-webhook/bootstrap/overlays/application
    name: bootstrap
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: spark/spark-operator/overlays/application
    name: spark-operator
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: stacks/kubernetes
    name: kubeflow-apps
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: knative/installs/generic
    name: knative
  - kustomizeConfig:
      repoRef:
        name: manifests
        path: kfserving/installs/generic
    name: kfserving
  repos:
  - name: manifests
    uri: https://github.com/kubeflow/manifests/archive/v1.2.0.tar.gz
  version: v1.2-branch
status:
  reposCache:
  - localPath: '".cache/manifests/manifests-1.2.0"'
    name: manifests
```

これでkubeflowの設定が終わりましたので、デプロイしていきましょう！
kfctl_istio_dex.v1.2.0.yamlが変わっている
```bash
$ kfctl apply -V -f kfctl_istio_dex.v1.2.0.yaml
```
数分程度で利用可能になると思います。  
利用できるようになったかは以下のコマンドで得られるSTATUSがRunningとCompletedにのみになっているかで確認してください。  
mpi-operatorはCrashLoopBackOffになっていると思いますが、notebookを利用する際は特に問題ないので一旦放置します(解決策を知っている方、教えてください！)
```bash
$ microk8s.kubectl get pods -A
```
kubeflowのデプロイ自体はこれで終わりです。  
これ以降はkubeflowの詳細設定を行います。

## 3.Enable metallb
kubeflowに外部からアクセスできるようにmetallbを設定していきます。
```bash
# change <ip> to your computer ip
$ microk8s enable metallb:<your ip address>-<your ip address>
$ microk8s.kubectl patch service -n istio-system istio-ingressgateway -p '{"spec": {"type": "LoadBalancer"}}'
```
 
## 4.Setup users for basic auth
利用するユーザの設定を行います。
```bash
$ microk8s.kubectl get configmap dex -n auth -o jsonpath='{.data.config\.yaml}' > dex-config.yaml
# email, username (e.g., test), passwordを変更してください
# パスワードはハッシュで記載します。オンラインツール等で設定してください。
# 今回はデフォルトの12341234のままにします。
# また、email, usernameはtestとします
$ vim dex-config.yaml
$ microk8s.kubectl create configmap dex --from-file=config.yaml=dex-config.yaml -n auth --dry-run=client -oyaml | microk8s.kubectl apply -f -
$ microk8s.kubectl rollout restart deployment dex -n auth
```

## 5.Create a user
```http://<your ip address>```でkubeflowのログイン画面にアクセスすると下のような画面が開かれると思います。   
設定したメールアドレスとパスワードを入力するとログインできます。  
私と同じ設定をしていると、メールアドレスはtest、パスワードは12341234でログインできます。
![ログイン画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/f3ca2f3c-df6a-5389-ad94-39a9c9ff7dab.png)


初回ログイン時にnamespaceの設定を聞かれます。今回はtestで設定しようと思います。

|![welcom.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/6815c7f6-7872-d732-d0b5-7f9a7844bb80.png)|![namespace.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/6c90db16-d834-031d-717c-be4304350a99.png)|
|:---:|:---:|
|初回ログイン画面<br>Start Setupをクリック|NameSpaceの設定<br>testを入力しfinishをクリック|

ここまで設定するとkubeflowのダッシュボード画面に行けます。
![dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/4e14eb06-4855-22dd-e337-209a37b32269.png)


## 6. Create Notebook Server
最後に機械学習やデータ分析で使えるnotebookを立てましょう！  
Dashboardの左側にあるNotebook Serversをクリックし、遷移後の画面の左上にある`+NEW SERVER`をクリックしましょう。
設定の参考例は下の画像になります。
![notebook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125512/6278c015-fb93-751f-553c-7f7ef544b7cb.png)

notebookが立ち上がり、`CONNECT`がクリックするとノートブックの画面に遷移できます。  
これでjupyterをつかって開発できるようになりました。

## 7. おわりに
今回はkubeflowの環境構築方法に関して共有しました。  
利用方法はNotebook Serveresしか試せていませんが、Pipelinesはkaggleで複数の実験を連続で回すのに有用なので、また別の記事で共有の機会を設けたいなと考えています。  
また、せっかくmicrok8sで環境構築したのでマルチノードクラスタも試して記事を書きたいと思います(PC足りない。。。)。
あと、kubernetesのruntimeをdockerにするのは非推奨となっているので訂正して、最新バージョンのkubeflowの環境構築も試してみたいです。(kubeflowでやりたいこと多すぎ問題)

## 8. 付録

### 1. GPU利用方法
dockerのruntimeをnvidiaに変更していないとGPUを認識しません。  
GPUを利用したい場合は変更をお願いします。

### 2. Custom Imageの利用方法
```bash
$ KUBE_USER=<user name> # usernameは新たに作成したユーザー名を入力する
$ sudo microk8s.kubectl create secret generic regcred --from-file=.dockerconfigjson=~/.docker/config.json --type=kubernetes.io/dockerconfigjson -n $KUBE_USER
$ microk8s.kubectl patch serviceaccounts default-editor -p "{\"imagePullSecrets\": [{\"name\": \"regcred\"}]}" -n $KUBE_USER
```