kubernetes v1.23.6/Nvidia GPUと戯れる その1

# この記事で伝えたいこと
- 自分環境(=オンプレミスのサーバー環境)で、k8sを導入し、jupyterhubを動作させ、外部のブラウザからアクセスできるようにする手順です。
- k8s(kubernetes)は、パブリッククラウドのサービスを利用すれば、非常に簡単に動作環境を構築できるので、何気なく使った経験はあるものの、機能や概念についての理解が不足していると感じていたことが動機です。
- GPUを使用するJupyterhubコンテナのイメージを起動し、外部からアクセスして、変更したデータを保存するという、AI/MLでは当然の環境を作るために結構な時間を要しましたので、自分と同じようなモヤモヤ感を持っている方々に、スクリーンショットやコマンドとその出力結果を通じて、導入の雰囲気を楽しんでいただけたら幸いです。

# 目次
1. 作業環境の準備 ←ココ
2. [Nvidia CUDA Toolkit、docker、Nvidia Container Toolkit](https://qiita.com/yoshiono/items/4efb6492fe721011ff0a)
3. [kubeadmでクラスター構築](https://qiita.com/yoshiono/items/7cb4359fa6d10e129ead)
4. [helmとGPU Operator](https://qiita.com/yoshiono/items/51fff88d0e493fa83b45)
5. [IstioとMetalLB](https://qiita.com/yoshiono/items/4da34a6cf8e52dca0895)
6. [Jupyterhub](https://qiita.com/yoshiono/items/e84830d56cc15faed55f)

# 1. 作業環境の準備
# 1.1 動作環境のHWリソース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104507/eb57c99c-6f73-aca7-8d09-232a39bc14a8.png)
- コンテナ環境では、コンテナが停止するとユーザーが作成したデータは消えてしまいます。消えると困るデータ(例 Jupyterhubのipynbファイル)は、永続ボリューム(PV:Persistent Volume)に保存する必要があります。
- この作業環境では、PVとしてNFSを利用することにしました。
    - 複数のk8sノードからアクセスできる
    - 比較的簡単にストレージ環境を作れる
    - PVとしての、hostPathやLocalといったボリュームは複数のk8sノードからのアクセスが難しいし、その他のボリュームは特別なHW、ライセンス、サブスクリプションが必要になりそう
- VMに割り当てるHDDは、Nvidia GPU Driver、CUDA、GPU Operatorを導入し、さらにjupyterhubのコンテナイメージを格納するためには、少なくとも、100GB以上が望ましいです。50GB程度だと、GPU Operator動作が不安定になり、コンテナが再起動を繰り返すことになります。
    - 最低限動作すればよいので、CPU/Memのリソースの割り当ては適当です。
    - リソース割り当てとは無関係なのですが、vSphere 7環境でGPUをパススルー(動的DirectPath I/O)やvGPUとして使用するためには、NVIDIA AI Enterprise Software for VMWare vSphereをESXiにインストールする必要があり、そのためには、Nvidia社との[契約](https://resources.nvidia.com/en-us-nvaie/quick-start-guide-nvaie?lx=Pwzn9v)が必要です。

# 1.2 この環境のKubernetesの機能・オブジェクト
- 試行錯誤の結果、下記になりました。最初にこの図を作って、作業を行ったのではないです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104507/3323d740-22a1-a45d-5264-5f5c61c69e2d.png)
- [マニュアル](https://kubernetes.io/ja/docs/concepts/overview/what-is-kubernetes/)によると、「Kubernetesは、宣言的な構成管理と自動化を促進し、コンテナ化されたワークロードやサービスを管理するための、ポータブルで拡張性のあるオープンソースのプラットフォーム」となっています。"プラットフォーム"だけに、コンテナ、内部・外部の通信、ストレージへのアクセスは、k8sの仕様を満たす商用SWやオープンソースで提供されており、別々にインストール・構成する必要があります。
- コンテナを束ねたオブジェクトがPodであり、Pod同士はCNI(Container Network Interface) pluginを介して通信しています。kubeadm initでk8s Node(control, master)のインストール直後の状態では、以下の状態(NotReady状態、かつ、corednsがPending)になっていて、ネットワークの設定(= CNI Pluginのインストールと設定)を行わないと、それ以降の作業ができません。
- k8sのネットワークのマニュアルは[こちら](https://kubernetes.io/ja/docs/concepts/cluster-administration/networking/)です。様々なネットワークモデル(= CNI Plugin)が紹介されていますが、私にとっては、ライセンスや使用料が不要かつ、オンプレ環境での情報が豊富という理由でCalicoを選択しています。
```OS:kubeadm init直後
root@k8s01:~# kubectl get nodes
NAME                  STATUS     ROLES                  AGE     VERSION
k8s01.dcws.dell.com   NotReady   control-plane,master   9m22s   v1.23.5
root@k8s01:~# 
root@k8s01:~# kubectl get pods --all-namespaces
NAMESPACE     NAME                                          READY   STATUS    RESTARTS   AGE
kube-system   coredns-64897985d-c86lv                       0/1     Pending   0          12m
kube-system   coredns-64897985d-m7w9s                       0/1     Pending   0          12m
kube-system   etcd-k8s01.dcws.dell.com                      1/1     Running   0          12m
kube-system   kube-apiserver-k8s01.dcws.dell.com            1/1     Running   0          12m
kube-system   kube-controller-manager-k8s01.dcws.dell.com   1/1     Running   0          12m
kube-system   kube-proxy-brp6f                              1/1     Running   0          12m
kube-system   kube-scheduler-k8s01.dcws.dell.com            1/1     Running   0          12m
```
- Ingressは、外部からのk8sに対するhttp要求を処理します。Ingressを設定しないとPodは外部と通信できません。[マニュアル](https://kubernetes.io/ja/docs/concepts/services-networking/ingress/)によると「クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトです。Ingressは負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供します。」です。
    - k8sが提供するのはIngressというAPIオブジェクトなので、実際に負荷分散、SSL終端といった機能を提供するIngressコントローラーを導入する必要があります。上記マニュアルにも、「Ingressを提供するためには[Ingressコントローラー](https://kubernetes.io/ja/docs/concepts/services-networking/ingress-controllers/)が必要です。Ingressリソースを作成するのみでは何の効果もありません。」と記載され、各種Ingressコントローラーが紹介されています。
    - 続いて、「プロジェクトとしてのKubernetesは現在、AWS、GCE、およびnginxのIngressコントローラーをサポート・保守しています。」となっています。
    - オンプレ環境でAWS、GCEというのはどうなのか？、nginxの[Webサイト](https://www.nginx.co.jp/products/nginx-ingress-controller/)ではF5との連携をアピールしているので何らかの契約が必要になるのでは…、などと考え、それなりに有名で、それなりにWeb検索で日本語の情報が多くヒットするIstio Ingressを選択しています。
- Dynamic Volume Provisioningは、PVの要求があった場合に、kubernetes(正確には、Provisioner)が、対応するPVCとSCに基づいてPVを作成します。これにより、k8s管理者が、手動で要求されたPVを作成する必要がありません。Jupyterhub for kubernetesでは、[Dynamic Volume Provisioning](https://kubernetes.io/ja/docs/concepts/storage/dynamic-provisioning/)を前提にしているらしく(マニュアルに記述が見当たらない)、SC(Stoage Class)を作成し、PVC(Persistent Vlume Clame)で、そのSCを指定しています。
    - NFS Provisionerとしてはnfs-subdir-external-provisionerを選択しています。特に、理由はないです。

# 1.3 準備作業
- 上記の動作環境のHWリソースの環境を構築します。手順は[こちら](https://www.server-world.info/query?os=Ubuntu_20.04)を参考にしています。本筋ではないので作業内容と出力は割愛します。
  - 各VMのhost名の設定
  - 固定ipアドレス割り当て
  - rootログイン可(権限やアクセス設定に関するトラブルを避けるため)
  - ntp/sshを使用可
  - linux02でnfs serverを有効にする
  - k8s01/02でnfs clientを有効にする

次回に続きます。