【NetApp DataOps Toolkit】 JupyterLabからデータセットのスナップショットを取得する+α

# この記事の要約
### NetApp DataOps Toolkitを使ってみた
- k8s基盤上にAI/MLな開発環境(JupyterLab+永続ストレージ)を払い出してみた
- JupyterLab上からデータセットのスナップショットを取得してみた

### どんなメリットがあるの？
- オープンソース(無料)です
- データサイエンティスト向けに設計・開発されているため、インフラ管理者の手を借りずに、AI/MLにまつわる様々なインフラ管理タスクをこなすことができます


注：本投稿はNetApp DataOps Toolkit **v2.4.0**に基づきます

# AIの開発に必要なデータマネジメントとは
:::note warn
私見かつ少々お堅い話が続きます。ツールの使い方だけ気になる方は[こちら](#使ってみた)へどうぞ。
:::

AI/ML、特にディープラーニングの分野においては、膨大なデータを格納するための大容量、モデルの学習などの高負荷なワークロードに耐えうる性能といった観点から**エンタープライズグレードなストレージの需要が大きい**です。


また一般的なアプリケーション開発と比較して、開発者(データサイエンティストなど)がより密接にデータに関わる必要があるというところもAIという分野の特徴です。
例えばAIモデルの開発においては、出来上がった**モデルの再現性や説明可能性**を担保する上で「モデルがどのようなコードでどの時点のデータから学習したのか」という記録を残しておくことが重要とされています。
ソースコードの管理はGitをはじめとするバージョン管理システムの領域ですが、**データのバージョン管理**は主にストレージの領域であり、一般的には**ストレージのスナップショット機能**などで実現することが多いです。

そのような状況で「今からモデルの学習を始めるからデータセットのスナップショットを撮って」と逐次ストレージ管理者にお願いをするのは**極めて非効率的**であり、AI/MLの現場ではストレージ管理者ではなく**開発者主導でデータを管理する仕組みが必要**なのではないかと考えます。

一方で、開発者自身がストレージ技術に精通しているということは稀であり、なおかつ本業ではない**ストレージそのものの学習コストは最小限に留めたい**というところも本音じゃないでしょうか？

やや回りくどい説明になりましたが、AI/MLな開発・運用現場では、「**エンタープライズなストレージ**」を「**開発者主導**」で「**なるべくストレージへの学習コストをかけずに**」管理できるようになるのが理想ではあると考えます。

# NetApp DataOps Toolkitとは
**NetApp DataOps Toolkit**(以下、NDTと表記)は、データサイエンティストやDevOpsエンジニアなど、**AI/ML分野の開発者向けにNetAppが開発・提供しているデータマネジメントツール**です。

上記で提起した様な「**エンタープライズなストレージ**」を「**開発者主導**」で「**なるべくストレージへの学習コストをかけずに**」管理できるツールとも言えます。

例えばAI/MLに特化した開発環境を払い出したり、データセットのバージョン管理を行なったり、S3オブジェクトを管理したりと、AIの開発に便利なデータ管理機能が多数備わっています。
また本ツールはPythonライブラリとして提供されているため、**Pythonで書かれることの多い機械学習のコードからシームレスに利用することができ、ストレージのGUI/CUIなどを習得する必要がない**というところも特徴です。

## 主な機能
NDTは**Kubernetes環境向け**(NetApp DataOps Toolkit for Kubernetes)と**ONTAP等の従来環境向け**(NetApp DataOps Toolkit for Traditional Environments)でライブラリが2つに分かれています。
開発環境や推論用サーバのインフラとしてk8sを使うことができる場合は場合は前者を、その他の環境(スタンドアロンなサーバなど)では後者を使用することが公式ドキュメントでは推奨されています。


### Kubernetes環境向け
https://github.com/NetApp/netapp-dataops-toolkit/tree/main/netapp_dataops_k8s

- [ワークスペース管理機能](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/workspace_management.md)
    - 永続ストレージ(PV)を備えた開発環境([JupyterLab](https://jupyterlab.readthedocs.io/en/stable/))をk8s基盤上に払い出し、管理する機能
    - ワークスペース単位のスナップショットを取得することも可能
- [ボリューム管理機能](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/volume_management.md)
    - 永続ストレージ(PV)の作成・削除やPV単位のスナップショットを取得する等の機能
- [データ管理機能](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/data_movement.md)
    - S3バケットの作成・削除やS3バケットへのオブジェクトのアップロード・ダウンロードを行う機能
- [NVIDIA Triton Inference Serverの管理機能](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/inference_server_management.md)
    - 推論用のサーバ([NVIDIA Triton Inference Server](https://developer.nvidia.com/nvidia-triton-inference-server))をk8s基盤上に払い出し、管理する機能

### その他環境向け
https://github.com/NetApp/netapp-dataops-toolkit/tree/main/netapp_dataops_traditional

- ボリューム管理機能
    - ONTAP上へのボリューム作成や既存ボリュームをクローンする等の機能
- スナップショット管理機能
    - ONTAP上のボリュームのスナップショットを取得するなどの機能
- データファブリック管理機能
    - SnapMirrorやCloud Syncといったストレージサービス間のデータ同期を管理する機能
    - S3オブジェクトのアップロード・ダウンロードを行う機能

## ツールの動作環境

### Kubernetes環境向け
- NDT自身は**Linux**または**MacOS**上で実行可能
- 操作対象のKubernetesクラスタは**k8s 1.17以降** or **OpenShift 4.4以降**であること
- 操作対象のKubernetesクラスタのストレージプラグインとして、**Astra Trident**または**BeeGFS**がインストールされていること
    - Astra Tridentの場合は、Trident Backendにて以下いずれかのストレージドライバが指定されていること
        - ontap-nas
        - ontap-nas-flexgroup
        - gcp-cvs
        - azure-netapp-files
    - BeeGFSを使用する場合は、一部機能が利用できなくなる点に注意

### 従来環境向け
- NDT自身は**Linux**または**MacOS**上で実行可能
- 操作対象のストレージプラットフォームは以下であること
    - NetApp AFF (ONTAP 9.7以降)
    - NetApp FAS (ONTAP 9.7以降)
    - NetApp Cloud Volumes ONTAP (ONTAP 9.7以降)
    - NetApp ONTAP Select (ONTAP 9.7以降)


# 使ってみた

今回は「**学習用のデータも集まったしこれからAIの開発をはじめるぞ！**」というシチュエーションを勝手に設定して、以下2つのユースケースでNDTを使ってみます。
検証環境にはk8sクラスタが構築済みですので、今回使うライブラリは**NetApp DataOps Toolkit for Kubernetes**になります。

1. 開発環境(JupyterLab + 永続ストレージ)の払い出し
1. データのバージョン管理(PVのスナップショット取得)

![スクリーンショット 2022-12-07 11.08.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/2ae500c2-9ec2-6e25-b04d-726df8eb96d8.png)




## バージョン情報など

[ツール動作環境](#ツールの動作環境)の項に記載したように、対象のk8sクラスタにストレージプラグイン(Astra Trident or BeeGFS)が導入済みである必要があります。
今回の検証環境として使用するk8sクラスタならびにAstra Tridentは、以下のように構成しています。

```bash
# k8sバージョン
$ kubectl version --short=true
Client Version: v1.23.7
Server Version: v1.23.7

# 作成済みのストレージクラス（CSIドライバーとしてAstra Tridentを使用）
$ kubectl get sc
NAME                      PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
ontap-flexgroup           csi.trident.netapp.io   Delete          Immediate           false                  11d
ontap-flexvol (default)   csi.trident.netapp.io   Delete          Immediate           false                  11d

# Trident Backendの一覧
$ kubectl get tridentbackend -n deepops-trident
NAME        BACKEND           BACKEND UUID
tbe-wp8gj   ontap-flexvol     ca9c5a6e-ab66-4631-86e8-5754f9c6f20b
tbe-zgvbv   ontap-flexgroup   0fc804d0-ffe8-4dda-bdb7-786daa9e21b2

# 各バックエンドのストレージドライバ（NDTの要件を満たすストレージドライバが使われている）
$ kubectl get tridentbackend -n deepops-trident tbe-wp8gj -o yaml | grep storageDriverName
    storageDriverName: ontap-nas
$ kubectl get tridentbackend -n deepops-trident tbe-zgvbv -o yaml | grep storageDriverName
    storageDriverName: ontap-nas-flexgroup

# volumeSnapshot関連のCRDが導入済み
$ kubectl get crd | grep volumesnapshot
volumesnapshotclasses.snapshot.storage.k8s.io         2022-11-24T09:55:59Z
volumesnapshotcontents.snapshot.storage.k8s.io        2022-11-24T09:56:00Z
volumesnapshots.snapshot.storage.k8s.io               2022-11-24T09:56:01Z

# volumeSnapshotClassが作成済み
$ kubectl get volumesnapshotclass
NAME            DRIVER                  DELETIONPOLICY   AGE
csi-snapclass   csi.trident.netapp.io   Delete           12d
```

## 使用例① 開発環境(JupyterLab + 永続ストレージ)の払い出し
[JupyterLab](https://jupyterlab.readthedocs.io/en/stable/index.html)とは、主にデータサイエンティスト向けの統合開発環境（IDE）です。
主にJupyter Notebook(.ipynbファイル)を用いてデータの解析や機械学習コードを作成することに特化しています。

![interface_jupyterlab.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/fa131f6d-d8e3-7edb-fb15-1a8cff3cb16d.png)

k8s基盤上でJupyterLabをホストする場合はコードやデータを格納するための永続ストレージ(PV)が別途必要となりますので、こちらも併せて払い出します。

### ツールのインストール
まずはk8sクラスタにアクセスできる任意の端末(Linux or Mac)にNetApp DataOps Toolkit for Kubernetesをインストールします。
NDTはpipでのインストールに対応しています。

```bash
python3 -m pip install netapp-dataops-k8s
```
### ツールの利用
インストールされたスクリプト```netapp_dataops_k8s_cli.py```を実行してJupyterLabと永続ストレージを払い出します。
今回は必要最小限のオプションのみを指定しましたが、コンテナイメージを変更する```--image```や、GPUなどのリソースを開発環境にアサインするための```--allocate-resource```などが利用できます。
その他オプションの詳細は[公式ドキュメント](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/workspace_management.md#create-a-new-jupyterlab-workspace)をご覧ください。

```bash
$ netapp_dataops_k8s_cli.py create jupyterlab --storage-class=ontap-flexgroup --workspace-name=netapp --size=1000Gi
Setting workspace password (this password will be required in order to access the workspace)...
Enter password:
Verify password:

Creating persistent volume for workspace...
Creating PersistentVolumeClaim (PVC) 'ntap-dsutil-jupyterlab-netapp' in namespace 'default'.
PersistentVolumeClaim (PVC) 'ntap-dsutil-jupyterlab-netapp' created. Waiting for Kubernetes to bind volume to PVC.
Volume successfully created and bound to PersistentVolumeClaim (PVC) 'ntap-dsutil-jupyterlab-netapp' in namespace 'default'.

Creating Service 'ntap-dsutil-jupyterlab-netapp' in namespace 'default'.
Service successfully created.

Creating Deployment 'ntap-dsutil-jupyterlab-netapp' in namespace 'default'.
Deployment 'ntap-dsutil-jupyterlab-netapp' created.
Waiting for Deployment 'ntap-dsutil-jupyterlab-netapp' to reach Ready state.
Deployment successfully created.

Workspace successfully created.
To access workspace, navigate to http://10.128.214.225:32456
```



デプロイが完了しpodがReadyになると、上記の様に開発環境へアクセスするためのURLが出力されます(To access workspace…の行)

もしURLを忘れてしまった場合は```netapp_dataops_k8s_cli.py list jupyterlab```と実行することで払い出したワークスペースの一覧が確認できます。

```bash
$ netapp_dataops_k8s_cli.py list jupyterlab
Workspace Name    Status    Size    StorageClass     Access URL                   Clone    Source Workspace    Source VolumeSnapshot
----------------  --------  ------  ---------------  ---------------------------  -------  ------------------  -----------------------
netapp            Ready     1000Gi  ontap-flexgroup  http://10.128.214.225:32456  No
```

URLにアクセスし、スクリプト実行時に指定したパスワードを入力するとJupyterLabのUIが表示されます。
ちなみにJupyterLabの払い出し時に```--image```オプションを省略した場合には、NVIDIAが提供しているTensorFlowのイメージ(nvcr.io/nvidia/tensorflow)をもとにpodが作成されます。

![スクリーンショット 2022-12-06 18.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/b0d6442f-4726-a686-37aa-3a855c714811.png)

またJupyterLab上でターミナルを開いて```df```コマンドなどを実行すれば、```/workspace```ディレクトリに永続ストレージがマウントされていることがわかります。
このディレクトリ配下に作成したファイルはたとえ開発環境(pod)を削除してもそのまま残り、再利用できるようになります。

```bash
# 払い出したJupyterLab上のターミナルからdfコマンド実行
$ df -h
Filesystem                                                        Size  Used Avail Use% Mounted on
overlay                                                           196G   63G  123G  34% /
tmpfs                                                              64M     0   64M   0% /dev
tmpfs                                                             7.9G     0  7.9G   0% /sys/fs/cgroup
10.128.214.226:/trident_pvc_156e709d_49b1_4723_b788_bfe21e5ed2a1 1000G  4.1G  996G   1% /workspace
/dev/mapper/ubuntu--vg-ubuntu--lv                                 196G   63G  123G  34% /etc/hosts
shm                                                                64M     0   64M   0% /dev/shm
tmpfs                                                              16G   12K   16G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                                                             7.9G     0  7.9G   0% /proc/acpi
tmpfs                                                             7.9G     0  7.9G   0% /proc/scsi
tmpfs                                                             7.9G     0  7.9G   0% /sys/firmware
```

ということで開発環境が無事デプロイできていることが確認できました。
確認のためのコマンド操作等を除くと、**実質的には1コマンドでAI/MLな開発環境を払い出すことができました。**

ちなみにk8s上に払い出されたリソースの実態はこんな感じです。

```bash
# Service, Deployment等の確認
$ kubectl get all -l app=ntap-dsutil-jupyterlab-netapp
NAME                                                 READY   STATUS    RESTARTS   AGE
pod/ntap-dsutil-jupyterlab-netapp-5795cb8d85-9np8h   1/1     Running   0          9m28s

NAME                                    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/ntap-dsutil-jupyterlab-netapp   NodePort   10.233.12.163   <none>        8888:32456/TCP   9m29s

NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ntap-dsutil-jupyterlab-netapp   1/1     1            1           9m28s

NAME                                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/ntap-dsutil-jupyterlab-netapp-5795cb8d85   1         1         1       9m28s

# PVCの確認
$ kubectl get pvc -l app=ntap-dsutil-jupyterlab-netapp
NAME                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
ntap-dsutil-jupyterlab-netapp   Bound    pvc-156e709d-49b1-4723-b788-bfe21e5ed2a1   1000Gi     RWX            ontap-flexgroup   9m48s
```

## 使用例② データのバージョン管理(PVのスナップショット取得)

つぎにNDTを使ってデータのバージョン管理を行います。

冒頭で述べた様に、AIの開発において**データセットのバージョンを適切に管理することはモデルの再現性や説明可能性を確保する上で重要なプロセス**になります。
そしてその具体的な手法としてはストレージのスナップショット機能を利用することが一般的であり、NDTでもk8sの**volumeSnapshot**の仕組みを用いてこれを実現します。

また実際に開発フェーズで本ツールを使用することを想定すると、普段開発者が使い慣れているインタフェース(JupyterLab)を用いてデータを管理を完結できる方が望ましいはずです。
ということでこの使用例では**k8s基盤上に払い出されたpodからNDTを使用する**ことを前提に進めていきたいと思います。

### ClusterRole/RoleBindingの作成
技術的な観点で先ほどの使用例①と最も異なる点は、 **NDTをインストール・実行するサーバがk8sクラスタ内に作成されたpod**であるという点です。
NDT for Kubernetesはk8sのAPIサーバを介して各種リソース(PVやvolumeSnapshotなど)の操作を行うことで、ツールとしての機能を実現します。
一方でNDTでデプロイしたものも含め、k8s上に払い出されたpodは標準では必要最小限の権限(ServiceAccount: Default)しか持たず、これらの操作を行うことができません。

podに対して必要な権限を割り当て、NDTの機能を利用できるようにする為に、k8s上に**ClusterRole**ならびに**RoleBinding**というリソースを作成する必要があります。

```bash
# ClusterRoleのマニフェスト
$ cat ndt-cluster-role.yml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: netapp-dataops
rules:
- apiGroups: [""]
  resources: ["persistentvolumeclaims", "persistentvolumeclaims/status", "services"]
  verbs: ["get", "list", "create", "delete"]
- apiGroups: ["snapshot.storage.k8s.io"]
  resources: ["volumesnapshots", "volumesnapshots/status", "volumesnapshotcontents", "volumesnapshotcontents/status"]
  verbs: ["get", "list", "create", "delete"]
- apiGroups: ["apps", "extensions"]
  resources: ["deployments", "deployments/scale", "deployments/status"]
  verbs: ["get", "list", "create", "delete", "patch", "update"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list"]

# マニフェストの適用
$ kubectl apply -f ndt-cluster-role.yml
clusterrole.rbac.authorization.k8s.io/netapp-dataops created
```

```bash
# RoleBindingのマニフェスト
$ cat ndt-role-binding.yml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: netapp-dataops
  namespace: default # NDTを実行するpodが存在するnamespace
subjects:
- kind: ServiceAccount
  name: default # ClusterRoleを紐づけるサービスアカウント
roleRef:
  kind: ClusterRole
  name: netapp-dataops # 前項で作成したClusterRole
  apiGroup: rbac.authorization.k8s.io

# マニフェストの適用
$ kubectl apply -f ndt-role-binding.yml
rolebinding.rbac.authorization.k8s.io/netapp-dataops created
```

:::note warn
**RoleBinding作成時の注意事項**
- RoleBindingはNDTを使用するnamespaceごとに作成する必要があります
- NDTで払い出した開発環境からNDTを使用する場合、対象のサービスアカウント(subjects.name)は```default```を指定する必要があります
    - 現状のNDTの仕様上、JupyterLabのpodを払い出す際に任意のサービスアカウントを指定するオプションが存在せず、```ServiceAccount:deafult```が固定となっているためです
:::

### ツールのインストール
k8s podからNDTを使用する場合、使用例①のようにpipでライブラリをインストールしただけではpodを作り直すたびに再度ツールのインストールが必要になってしまいます。
以下いずれかの方法を取れば、pod再作成後もライブラリを継続利用できます。

1. 永続ストレージ内にライブラリをインストールする
    ```bash
    pip install netapp-dataops-k8s -t <PVをマウントした任意のディレクトリ>
    ```
1. NDTインストール済みのコンテナイメージを作成する
    ```Dockerfile
    # NDT標準のコンテナイメージにNDTを追加インストールするDockerfile作成例
    FROM nvcr.io/nvidia/tensorflow:22.05-tf2-py3
    RUN pip install --upgrade pip
    RUN pip install numba==0.56.4 # NDTと一緒にインストールされるnumpyとベースイメージにインストールされているnumbaがコンフリクトするためアップグレード
    RUN pip install netapp-dataops-k8s
    ```
    ```bash
    # イメージのビルド
    $ docker build . -t hoge/tensorflow:22.05-tf2-py3-ndt

    # イメージのプッシュ
    $ docker push hoge/tensorflow:22.05-tf2-py3-ndt
    ```
    ```bash
    # 作成したイメージを使って開発環境(JupyterLab)を払い出し
    $ netapp_dataops_k8s_cli.py create jupyterlab --image=hoge/tensorflow:22.05-tf2-py3-ndt --storage-class=ontap-flexgroup --workspace-name=netapp --size=1000Gi
    ```

### ツールの利用
ここから実際に開発環境(k8s pod)上でNDTを実行し、PVのスナップショットを取得していきます。

使用例①ではPythonスクリプトとしてターミナルからNDTを実行する方法をお見せしました。
一方でこちらの使用例では開発環境内で作成した**Jupyter Notebookからライブラリをインポートする手法**を用います。

以下、サンプルコードです。
```python
# NDTのインポート
import netapp_dataops.k8s as ndt

# 永続ボリュームの一覧取得
ndt.list_volumes(namespace="default", print_output=True)

# スナップショットの取得
ndt.create_volume_snapshot(pvc_name="ntap-dsutil-jupyterlab-netapp", volume_snapshot_class="csi-snapclass", namespace="default", print_output=True)

# 取得したスナップショットの確認
ndt.list_volume_snapshots(pvc_name="ntap-dsutil-jupyterlab-netapp", namespace="default", print_output=True)
```

Notebook形式で見るとこんな感じ。
![スクリーンショット 2022-12-07 16.32.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/20b828a4-ccc6-97b4-a85a-9fc33a3fe39f.png)

```netapp_dataops.k8s```という名称のライブラリをインポートし、必要な関数を呼び出して実行します。
例えば今回の様にPVのスナップショットを取得したい場合には```create_volume_snapshot```という関数を呼び出します。

ライブラリ関数の詳細(関数名や引数などの仕様)は以下のリンクから確認できます。

- [ワークスペース管理機能のリファレンス](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/workspace_management.md#advanced-set-of-functions)
- [ボリューム管理機能のリファレンス](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/volume_management.md#advanced-set-of-functions)
- [データ管理機能のリファレンス](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/data_movement.md#s3-data-mover-basic-usage)
- [NVIDIA Triton Inference Server管理機能のリファレンス](https://github.com/NetApp/netapp-dataops-toolkit/blob/main/netapp_dataops_k8s/docs/inference_server_management.md#advanced-set-of-functions)

このようにJupyter NotebookからNDTを利用することで、**普段お使いの機械学習コードからシームレスにデータの管理**を行うことができます。

# おわりに
今回は最も代表的と思われる2つのユースケースに絞ってNetApp DataOps Toolkitを使ってみました。

NDTを使えば開発環境の準備やデータのバージョン管理など**工数的な負担が大きかったり、インフラ管理者の手を借りなければいけなかったタスクを開発者自身でこなすことができる様になる**と思います。ぜひお試しください。