KubernetesのGPUノードをPrometheusで監視する

# はじめに

近年のDeep Learningブームの中で、多数のGPUノードをKubernetesで管理し、複数人で共有するといった運用形態を良く耳にするようになりました。弊社でもオンプレミス環境にGPUクラスタを構え、Deep Learningを用いたAI開発を行っています(開発プラットフォームとしては[OSSのKAMONOHASHI](https://github.com/KAMONOHASHI/kamonohashi)を利用しています)。

GPUクラスタを共有し利用していく中で、GPUノードの利用状況をメトリクスとして収集し、過去の利用過程を可視化したいという要望が出てきました。そこで[NVIDIA DCGM exporter for Prometheus](https://github.com/NVIDIA/gpu-monitoring-tools/tree/master/exporters/prometheus-dcgm)と[Prometheus](https://prometheus.io/)を用いて、GPUのメトリクスを収集してみました。

なお、本記事で利用するファイルは[GitHub](https://github.com/hama1080/prometheus-gpu)に置いています。

# ゴール

GPUノードが含まれるKubernetesクラスタに、PrometheusとDCGM exporterを導入し、PrometheusでGPUのメトリクスが収集できるようにします。

![prometheus.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61029/59c268ab-3d90-c453-d29a-8a08b9cfdc7d.png)


# バージョン情報

利用した各コンポーネントのバージョンは以下の通りです。

- Kubernetes: v1.14.1
- helm: v2.13.0
- [NVIDIA DCGM exporter for Prometheus](https://github.com/NVIDIA/gpu-monitoring-tools/tree/master/exporters/prometheus-dcgm): d39e0cc8675c0b10aab14d4f66191eedf5029f66
- [Prometheus](https://github.com/helm/charts/tree/master/stable/prometheus): 2.13.1(helm chartはprometheus-9.3.1を利用)
- [nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress): v0.26.1(helm chartはnginx-ingress-1.24.7を利用)

Prometheus, nginx-ingressに関してはHelmを用いて導入しました。

# クラスタの構成

クラスタに含まれるノードは、以下のものとします。

- master: Kubernetesのmasterノード
- gpu1: NVIDIA Tesla K80搭載のGPUノード
- gpu2: NVIDIA Tesla K80搭載のGPUノード
- kamonohashi: KAMONOHASHIで利用のノード。今回は関係なし

※既存のKAMONOHASHIクラスタを利用しています。KAMONOHASHI導入時、Kubernetesはkubesprayを利用して構築しています。

今回はmasterノードにPrometheusをデプロイし、Ingressを通してアクセスします。Ingress ControllerにはNginx Ingress Controllerを利用します。

またDCGM exporterが動く要件は以下が提示されているため、各GPUノードでこれらの要件を満たすようにしておきます。

> ### Prerequisites
> * NVIDIA Tesla drivers = R384+ (download from [NVIDIA Driver Downloads page](http://www.nvidia.com/drivers))
> * nvidia-docker version > 2.0 (see how to [install](https://github.com/NVIDIA/nvidia-docker) and it's [prerequisites](https://github.com/nvidia/nvidia-docker/wiki/Installation-\(version-2.0\)#prerequisites))
> * Optionally configure docker to set your [default runtime](https://github.com/NVIDIA/nvidia-container-runtime#daemon-configuration-file) to nvidia


# NVIDIAが提供しているGPUのモニタリングツール

NVIDIAが提供しているGPUのモニタリングツールは、GitHubの[NVIDIA/gpu-monitoring-tools](https://github.com/NVIDIA/gpu-monitoring-tools)から入手できます。この中の[NVIDIA DCGM exporter for Prometheus](https://github.com/NVIDIA/gpu-monitoring-tools/tree/master/exporters/prometheus-dcgm)が、PrometheusでGPUのメトリクスを取る際のexporterとして利用できるものとなっています。DCGMは[NVIDIA Data Center GPU Manager](https://developer.nvidia.com/dcgm)の略で、GPUクラスタのメトリクスを取るために用意されているツール群のことを表します。

このリポジトリですが、少し中身が分かりづらいため、ここで整理しておきます。
[NVIDIA DCGM exporter for Prometheus](https://github.com/NVIDIA/gpu-monitoring-tools/tree/master/exporters/prometheus-dcgm)内の`k8s/node-exporter`が、exporterのDaemonSetのマニフェストが格納されたディレクトリとなっています(`k8s/pod-gpu-metrics-exporter`は、exporter自体のソースコードが格納されています)。

`k8s/node-exporter`内には、以下の3つのマニフェストファイルが格納されています。

**gpu-node-exporter-daemonset.yaml**
GPUノードのメトリクスをCPU等の情報も含めて取得するexporterのDaemonSet

**gpu-only-node-exporter-daemonset.yaml**
GPUノードのメトリクスをGPU情報のみを取得するexporterのDaemonSet

**pod-gpu-node-exporter-daemonset.yaml**
PodごとのGPU利用状況のメトリクスを取得するexporterのDaemonSet

これらのDaemonSetは、それぞれ以下のexporterの組み合わせにより構成されています。

**prometheus/node-exporter**
Prometheusが提供している一般的なexporterです。

**nvidia/dcgm-exporter**
NVIDIAが提供しているGPUノードのメトリクスを取るexporterです。このexporterを用いることで、GPUノードごとのGPU利用率やGPUメモリ利用量が取得できます。`prometheus/node-exporter`とこのexporterを統合したものが、`gpu-node-exporter-daemonset`、`gpu-only-node-exporter-daemonset`で利用されています。

**nvidia/pod-gpu-metrics-exporter**
PodごとのGPU利用状況のメトリクスを取るexporterです。このexporterを用いることで、GPUノードに立っているPodごとのGPU利用率やGPUメモリ利用量が取得できます。`prometheus/node-exporter`および`nvidia/dcgm-exporter`と、このexporterを統合したものが、`pod-gpu-node-exporter-daemonset`で利用されています。
kubeletを利用してpodの情報を取得するため、FeatureGatesのKubeletPodResourcesが有効になっている必要があります(Kubernetes v1.15以降はデフォルトで有効になっています)

本記事では`pod-gpu-node-exporter-daemonset.yaml`を利用して、PodごとのGPU利用状況のメトリクスを取得してみます。

# 手順

### 1. kubeletの設定変更

今回用いたKubernetesはv1.14.1であるため、PodごとのGPU利用状況のメトリクスを取得するためには、各GPUノードにおいてkubeletのFeatureGatesの機能を有効にする必要があります。
各GPUノードで`/etc/systemd/system/kubelet.service`を編集し、ExecStartの部分を修正します。

```diff
ExecStart=/usr/local/bin/kubelet \
+                --feature-gates KubeletPodResources=true \
```

そして以下のコマンドでkubeletを再起動します。

```Bash
systemctl daemon-reload
systemctl restart kubelet
```

これらの作業により、上述のexporterがPodの情報を取得できるようになります。

### 2. namespaceの作成

今回デプロイするリソース用のnamespaceを作成します。名前は`monitoring`とします。

```Bash
kubectl create namespace monitoring
```

### 3. GPUノードに対するラベル付与

`pod-gpu-node-exporter-daemonset`ではnodeSelectorとして`hardware-type: NVIDIAGPU`が設定されており、このラベルが付与されたノードに対してexporterが配置される設定となっています。
そこで以下のように、GPUノードに対してラベル付けを行います(\<gpu-node-name>をgpu1などのgpuノードのホスト名に変更)。

```bash
kubectl label nodes <gpu-node-name> hardware-type=NVIDIAGPU
```

ラベルの付与状況は、以下のコマンドで確認できます。

```
kubectl get nodes --show-labels
```

### 4. Prometheusのデータ保存先を作成

Prometheusをデプロイする際には、収集したデータの保存先を指定する必要があります。今回はmasterノードの`/var/lib/prometheus`配下に保存することとします。以下のコマンドでディレクトリを作成し、ownerをnobodyに設定します。Prometheus serverはnobodyユーザで実行されるため、ownerの変更が必要となります。

```
mkdir /var/lib/prometheus
chown nobody /var/lib/prometheus
```

### 5. 各種リソースのデプロイ

以下のリソースをデプロイします。利用したファイルは[GitHub](https://github.com/hama1080/prometheus-gpu)に置いています

- GPUノード用のexporterを配置するためのDaemonSet(`pod-gpu-node-exporter-daemonset.yaml`)
- exporterのPodにアクセスするためのservice(`exporter-service.yaml`)
- Prometheusで利用するPersistent Volume, Persistent Volume Claim(`prometheus-pv-pvc.yaml`)

GPUノード用のexporterに関しては、元のリポジトリにある`pod-gpu-node-exporter-daemonset.yaml`のnamespaceを`kube-system`から`monitoring`に変更したものを用います。またPrometheusがexporterのPodから情報を収集できるようにするためserviceも作成します。さらにPrometheusのデータ保存用のPVおよびPVCを、8GB(Prometheusチャートのデフォルト値)の容量で作成します。

これら3つのマニフェストファイルを以下のコマンドで適用します。

```
kubectl apply -f pod-gpu-node-exporter-daemonset.yaml -f exporter-service.yaml -f prometheus-pv-pvc.yaml
```

exporterのデプロイ後、正常にGPUノードのメトリクスが出力されているかは以下のコマンドで確認できます。

```
curl <gpu-node-name>:9100/metrics | grep dcgm
```

`dcgm_gpu_utilization`等が出力されていれば、正常にexporterが起動しています。一度ここで確認することをおすすめします。

### 6. Prometheusのインストール

Helmを利用してPrometheusのインストールを行います。
以下のコマンドでprometheusの設定ファイルをダウンロードし、編集します。

```Bash
helm inspect values stable/prometheus > prometheus-values.yaml
```

今回は、ひとまず動作させることを目的にしているため、ほぼ全ての機能を無効化した状態でデプロイします。

- Prometheus serverのみ有効化
- `http://master-hostname/prometheus`のような形でアクセスするためにprefixURLとbaseURLを変更
- prometheus serverのingressを有効化し、ingress.classとしてprometheus-nginxを指定
- masterノードにデプロイするようnodeSelectorを設定
- persistentVolumeとして上記で作成したものを指定

編集が完了後、以下のコマンドでPrometheusのインストールを行います。

```
helm install --name prometheus --namespace monitoring --version 9.3.1 -f prometheus-values.yaml stable/prometheus
```

### 7. Ingress Controllerのインストール

Prometheus ServerのIngress用に、Nginx Ingress Controllerをインストールします。
以下のコマンドで設定ファイルをダウンロードし、編集します。

```Bash
helm inspect values stable/nginx-ingress > nginx-ingress-values.yaml
```

編集内容は次のようなものです。

- ingressClassをprometheus-nginxに変更
- scopeを有効にし、namespaceとしてmonitoringを指定
- defaultBackendServiceとして`monitoring/prometheus-server`を指定
- masterノードにデプロイするようnodeSelectorを設定
- externalIPsとしてmasterノードのIPを指定

編集が完了後、以下のコマンドでIngress Controllerのインストールを行います

```
helm install stable/nginx-ingress --name prometheus-nginx --namespace monitoring --version 1.24.7 -f nginx-ingress-values.yaml
```

### 8. Prometheusの利用

ブラウザでPrometheusデプロイ時に設定したURLにアクセスすることで、Prometheusでのメトリクス取得状況を見ることができます。gpu関連のメトリクスはdcgm_...というもので取得できます。
![prometheus.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61029/59c268ab-3d90-c453-d29a-8a08b9cfdc7d.png)


## まとめ

NVIDIA DCGM exporter for PrometheusとPrometheusを組み合わせることで、GPUリソースの利用状況の監視が可能となりました。Grafanaと組み合わせることで、Prometheusでの収集結果を美しく可視化することも可能です。

実際に運用するには、メトリクスの保存領域のサイズ等を詳しく検討する必要がありますが、似たようなことを実現したい方の参考になれば幸いです。