KubernetesクラスタにKubeflowバージョン1.4をデプロイする

##１．はじめに
株式会社日立製作所 研究開発グループ サービスコンピューティング研究部の田中です。
2021年10月12日にKubeflowの新バージョン1.4がリリースされました。本記事では、この新バージョンであるKubeflowバージョン1.4をクラウド／オンプレミスを問わない一般的なKubernetesクラスタ上へデプロイする手順を紹介します。また、ひとつ前のバージョンであるKubeflowバージョン1.3をデプロイする手順も付記しています。
なお、今回紹介する手順は、以下の[Kubeflowの公式サイト](https://www.kubeflow.org/)からの情報を参考にしたものになります。

+ [Installing Kubeflow](https://www.kubeflow.org/docs/started/installing-kubeflow/#manifests)
+ [Kubeflow Manifests - Installation](https://github.com/kubeflow/manifests/blob/master/README.md#installation)

[2021年12月24日追記] Kubeflowの機能の活用例として、新たな記事を作成しました。

+ [Kubeflow 内部の MinIO サーバを使用して Kubeflow Pipelines と TensorBoard を連携させる](https://qiita.com/Akira_TANAKA/items/715578688f4fbc6d1807)

##２．Kubeflowとは
KubeflowはKubernetes上で機械学習を実行管理する環境を提供するOSSで、機械学習のモデルの作成・学習・検証、ワークフロー構築、モデルサービングといったMLOpsに関するワークロードを統合して実行することを特長とするプラットフォームです。

##３．Kubeflowのデプロイ手順
###3.1. 前提条件
今回の手順では、KubeflowをデプロイするKubernetesクラスタはあらかじめ構築されていることを前提とします。KubernetesクラスタはMasterノード上のkubectlコマンドで操作します。KubeflowとKubernetesのバージョンは以下の通りです。

+ Kubeflow バージョン1.4.0（2021年10月12日時点の最新版）
+ Kubernetes バージョン1.21（バージョン1.22以降ではKubeflowをデプロイできないため）

###3.2. オプション条件
機械学習の実行においては、ストレージを多く消費することが想定されるため、ストレージとしてローカルストレージを利用する場合に加え、Kubernetes外部のNFSサーバを利用する場合の手順も紹介します。

###3.3. デプロイ手順の動作確認
今回の手順を利用して動作確認した環境情報は以下の通りです。
####(1)	動作確認環境(1)

+ OS
  * Masterノード（1台）：Ubuntu 18.04 LTS
  * Workerノード（2台）：Ubuntu 18.04 LTS
+ Kubernetes バージョン1.21.4（[kubeadm](https://kubernetes.io/docs/reference/setup-tools/kubeadm/)でクラスタを作成）
+ Kubeflow バージョン1.4.0
+ Dynamic Volume Provisioner：nfs-subdir-external-provisioner v4.0.2

![Image01-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2200218/58ef3d0c-d4fc-e7be-38e2-d6a138ce2d54.png)

####(2)	動作確認環境(2)
+ OS
  * Masterノード（1台）+Workerノード（1台）：Ubuntu 18.04 LTS
+ Kubernetes バージョン1.21.1（[Kind](https://kind.sigs.k8s.io/)でクラスタを作成）
+ Kubeflow バージョン1.4.0
+ Dynamic Volume Provisioner：local-path-provisioner v0.0.14

![Image01-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2200218/2f1a5fba-af11-92c5-af6f-462ae75b8b69.png)

###3.4. Kubeflowのデプロイ
KubernetesクラスタへKubeflowをデプロイする手順は以下のとおりです。

+ KubernetesクラスタへDynamic Volume Provisionerをデプロイする（3.4.1節）
  1. ローカルストレージを利用する場合
  1. NFSサーバを利用する場合（オプション）
+ KubernetesクラスタのデフォルトのStorageClassを設定する（3.4.2節）
  1. ローカルストレージを利用する場合
  1. NFSサーバを利用する場合（オプション）
+ KubeflowをKubernetesクラスタへデプロイする（3.4.3節）
+ Jupyter Notebook – Kubeflow Pipelines連携を設定する（3.4.4節）
+ ユーザごとのリソースQuotaを設定する（3.4.5節）

###3.4.1. KubernetesクラスタへDynamic Volume Provisionerをデプロイする
Kubeflowデプロイの準備として、ストレージに対応したDynamic Volume ProvisionerをKubernetesクラスタへデプロイします。

####(1) ローカルストレージを利用する場合（例：動作確認環境(2)）
ローカルストレージを使用する場合は、Dynamic Volume Provisionerとして[Local Path Provisioner](https://github.com/rancher/local-path-provisioner/)をKubernetesクラスタへデプロイします。

```Bash
# Masterノード上で操作：
# Local Path Provisioner のデプロイ
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
```
####(2) NFSサーバを利用する場合（オプション）（例：動作確認環境(1)）
まず、Kubernetesクラスタを構成する全てのノードにログインし、ノードのOSに対応したNFSクライアントパッケージをインストールします。
例えば、ノードのOSがUbuntu 18.04 LTSの場合は、aptコマンドを用いてnfs-commonパッケージをインストールします。

```Bash
# Masterノード、Workerノード上で操作：
# nfs-commonパッケージのインストール（Ubuntu 18.04 LTS の場合）
sudo apt install –y nfs-common
```

続いて、NFSをKubernetesのストレージとして利用するためのDynamic Volume Provisionerである[nfs-subdir-external-provisioner](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner)をKubernetesクラスタへデプロイします。
nfs-subdir-external-provisioner をデプロイするために用いるhelmコマンドをMasterノードにインストールします。

```Bash
# Masterノード上で操作：
# helmコマンドのインストール
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
```
helmコマンドのインストール後、helmコマンドを用いてNFSサーバのホスト名（あるいは、IPアドレス）を指定してnfs-subdir-external-provisionerをデプロイします。

```Bash
# Masterノード上で操作：
# リポジトリの追加
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

# nfs-subdir-external-provisioner のデプロイ
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=＜NFSサーバのホスト名あるいはIPアドレス＞ --set nfs.path=/ --set storageClass.name=nfs
```

###3.4.2. KubernetesクラスタのデフォルトのStorageClassを設定する
Kubeflowデプロイの準備として、3.4.1節でデプロイしたDynamic Volume Provisionerに対応するStorageClassをKubernetesクラスタのデフォルトのStorageClassとして設定します。

####(1) ローカルストレージを利用する場合（例：動作確認環境(2)）
Dynamic Volume Provisioner（local-path-provisioner）に対応するStorageClass（local-path）をKubernetesクラスタのデフォルトに設定します。

```Bash
# Masterノード上で操作：
# KubernetesクラスタのデフォルトのStorageClassとして’local-path’を設定
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# 標準のデフォルトのStorageClass（standard）を解除
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
```
####(2) NFSサーバを利用する場合（オプション）（例：動作確認環境(1)）
Dynamic Volume Provisioner（nfs-subdir-external-provisioner）に対応するStorageClass（nfs）をKubernetesクラスタのデフォルトに設定します。

```Bash
# Masterノード上で操作：
# KubernetesクラスタのデフォルトのStorageClassとして’nfs’を設定
kubectl patch storageclass nfs -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# 標準のデフォルトのStorageClass（standard）を解除
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
```

###3.4.3. Kubeflowのデプロイ
KubeflowをKubernetesクラスタへデプロイします。

####(1) Kustomizeコマンドのインストール
Kubeflowのデプロイに用いるKustomizeコマンドをインストールします。

```Bash
# Masterノード上で操作：
# Kustomizeコマンドのインストール
curl -Lo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.2.0/kustomize_3.2.0_linux_amd64 && chmod +x kustomize && sudo mv kustomize /usr/local/bin/
```

####(2) Kubeflowデプロイ用Manifestファイルを取得
[GitHubのリポジトリ](https://github.com/kubeflow/manifests.git)からKubeflowのManifestファイル一式をクローンして取得します。さらにデプロイするバージョンであるバージョン1.4のタグ（v1.4.0）でチェックアウトします。

```Bash
# Masterノード上で操作：
# Kubeflow Manifestファイル一式のクローン
git clone https://github.com/kubeflow/manifests.git
# クローン先へディレクトリを移動
cd manifests
# バージョン1.4.0でチェックアウト
git checkout v1.4.0
```

####(3) Kubeflowデプロイ用スクリプトの実行
チェックアウトしたファイルを用いてKubeflowのデプロイ用スクリプトを実行します。スクリプトはループ実行されますが、エラー終了せずにコマンドラインに戻ってきた場合、Kubeflowのデプロイが成功したことになります。

```Bash
# Masterノード上で操作：
# Kubeflowのデプロイ用スクリプトの実行
while ! kustomize build example | kubectl apply -f -; do echo "Retrying to apply resources"; sleep 10; done
```

####(4) Kubeflow正常動作の確認
デプロイに成功してしばらく待った後、Kubeflowを構成するPodの実行状態を確認します。全ての Pod のSTATUSがRunning になれば、Kubeflow は正常動作していることになります。なお、Kubernetes環境によっては正常動作する場合でも全てのPodがRunningになるまでには数分～数十分の時間がかかりますので注意してください。

```Bash
# Masterノード上で操作：
# クラスタ内の全てのPodsを表示
kubectl get pods --all-namespaces
```

####(5) Kubeflowダッシュボードの公開
正常動作の確認後、WebブラウザからアクセスするKubeflowのダッシュボード（CentralDashboard）を公開するためのポートフォワード設定コマンドを実行します。

```Bash
# Masterノード上で操作：
# Masterノードのポート8080へのアクセスをKubeflowダッシュボード（istio-ingress ポート80）に転送
kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80 --address 0.0.0.0
```

ポートフォワード設定コマンド実行中に、KubernetesクラスタのMasterノードのホスト名、あるいはIPアドレスのポート8080へWebブラウザからアクセスすると、Kubeflowのダッシュボード画面にアクセスすることができます。

|ユーザログイン画面|Kubeflowダッシュボード画面|
|:---:|:---:|
|![Image01-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2200218/0149ecd3-0616-e005-7691-0d0ddc7c1f99.png)|![Image01-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2200218/93e03d10-1d8e-ee45-0805-ca0a3f7b9f14.png)|
|ユーザ名：user@example.com<br/>パスワード：12341234|（ログイン後の画面例）|

###3.4.4. Jupyter Notebook - Kubeflow Pipelines連携設定
Jupyter NotebookからKubeflow Pipelinesのパイプライン制御を可能にするためには、Kubeflowと同時にデプロイされているサービスメッシュ「Istio」に対してアクセス権限の認可ポリシー設定とネットワークフィルタ設定を追加で作成・登録する必要があります。
ここでは、Kubeflowのデフォルトで用意されているユーザ（user@example.com）に対して、Jupyter NotebookからKubeflow Pipelinesへの制御を可能にする認可ポリシー設定とネットワークフィルタ設定の設定例を示します。
なお、Kubeflowユーザ全体に対してIstioの認可ポリシー設定とネットワークフィルタ設定を行う仕組みは存在しないため、Kubeflow運用時にJupyter NotebookからKubeflow Pipelinesへの制御権限を割り当てる場合は、ユーザに対して個別にIstioのポリシー設定・ネットワークフィルタ設定を行う必要があります。


```Bash
# Masterノード上で操作：
# Istioに対するKubeflowユーザ（user@example.com）のポリシーとフィルタを設定
cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: bind-ml-pipeline-nb-kubeflow-user-example-com
 namespace: kubeflow
spec:
 selector:
   matchLabels:
     app: ml-pipeline
 rules:
 - from:
   - source:
       principals: ["cluster.local/ns/kubeflow-user-example-com/sa/default-editor"]
---
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: add-header
  namespace: kubeflow-user-example-com
spec:
  configPatches:
  - applyTo: VIRTUAL_HOST
    match:
      context: SIDECAR_OUTBOUND
      routeConfiguration:
        vhost:
          name: ml-pipeline.kubeflow.svc.cluster.local:8888
          route:
            name: default
    patch:
      operation: MERGE
      value:
        request_headers_to_add:
        - append: true
          header:
            key: kubeflow-userid
            value: user@example.com
  workloadSelector:
    labels:
      istio.io/rev: default
EOF
```

###3.4.5. ユーザごとのリソースQuotaを設定する
Kubeflowでは、ユーザごとにCPU数、メモリサイズ、ストレージ容量といったマシンリソース割り当てのQuota（上限値）を設定することができます。Quotaの設定には、Kubeflowユーザに紐づいたProfileをKubernetesクラスタに登録することで実現します。
ここでは、Kubeflowのデフォルトで用意されているユーザ（user@example.com）に対してQuotaを設定するKubernetes Profileの設定例を示します。
なお、Kubeflowユーザ全体に対してQuotaを設定する仕組みは存在しないため、Kubeflowユーザに対するリソースのQuota設定が必要な場合は、ユーザに対して個別にKubernetes Profileを作成・登録する必要があります。

```Bash
# Masterノード上で操作：
# Kubeflowユーザ（user@example.com）のQuotaを設定
cat <<EOF | kubectl apply -f - -n kubeflow
apiVersion: kubeflow.org/v1beta1
kind: Profile
metadata:
  name: kubeflow-test-example-com
spec:
  owner:
    kind: User
    name: user@example.com
  resourceQuotaSpec:
   hard:
     cpu: "2"
     memory: 2Gi
     requests.nvidia.com/gpu: "1"
     persistentvolumeclaims: "1"
     requests.storage: "20Gi"
EOF
```

###3.4.6. Kindで作成したKubernetesクラスタへの追加設定
動作確認環境(2)の場合など、Kindで作成したKubernetesクラスタへデプロイしたKubeflow 1.4上でKubeflow Pipelinesを実行するためには、パイプラインでのコンテナ実行方式をデフォルト設定のdockerから別方式（k8sapi、kubelet、pnsから選択）に変更する追加設定が必要となります。下記の示す追加設定例は、コンテナ実行方式をpns（Process Namespace Sharing）に変更するものです。

```Bash
# Masterノード上で操作：
# Kubeflow Pipelinesのコンテナ実行形式（containerRuntimerExecutor）を’pns’に設定
kubectl patch configmap workflow-controller-configmap -n kubeflow -p '{"data": {"containerRuntimeExecutor": "pns"}}'
```

##4. Kubeflowバージョン1.3をデプロイする場合
今回はKubeflowバージョン1.4のデプロイ手順を紹介しましたが、3章の手順に以下の変更を加えることで、Kubeflowバージョン1.3をデプロイすることができます。

####(1) Kubeflowデプロイ用Manifestファイルを取得時の変更
3.4.3節(2)において、GitHubのリポジトリからKubeflowのManifestファイル一式をクローンして取得した後にチェックアウトするタグをv1.3.1に変更します。

```Bash
# Masterノード上で操作：
# Kubeflow Manifestファイル一式のクローン
git clone https://github.com/kubeflow/manifests.git
# クローン先へディレクトリの移動
cd manifests
# バージョン1.3.1でチェックアウト
git checkout v1.3.1
```

####(2)	不具合回避パッチコマンドの実行
Kubeflowバージョン1.3では、3.4.3節(3)においてKubeflowデプロイ用スクリプトを実行してデプロイを成功した後も、不具合により起動に失敗するPodが存在します。対処方法がコミュニティで公開されていますので、不具合を回避して正常に起動させるパッチコマンドとして実行します。

```Bash
# Masterノード上で操作：
# dex の Deployment を修正
kubectl patch deployment dex -n auth -p '{"spec":{"template":{"spec":{"containers":[{"name":"dex","env":[{"name":"KUBERNETES_POD_NAMESPACE","value":"default"}]}]}}}}'

# mpi-operator の Deployment を修正
kubectl patch  deployment mpi-operator -n kubeflow -p '{"spec":{"template":{"spec":{"containers":[{"name":"mpi-operat
```

##5. おわりに
今回は、一般的なKubernetesクラスタ上にKubeflowバージョン1.4をデプロイする手順を紹介しました。KubeflowはMLOpsを実現するための様々な機能を持ちます。今後は、Kubeflowの機能の活用例やノウハウを紹介したいと考えています。