Prometheusとdcgm-exporterを使ってGPUの監視をする
Prometheus を使うとなったとき、 node-exporter を使ってマシンの CPU 使用率やメモリの使用量を監視すると思います。 しかし、GPU の監視をしようとしたとき、node-exporter だけでは GPU のメトリクスを取ることができません。 そこで NVIDIA/gpu-monitoring-tools にある dcgm-exporter という exporter を使うことで GPU のメトリクスを取ることができます。
dcgm-exporter は何をしているのか
dcgm-exporter 自身は NVIDIA が公開している Data Center GPU Manager(DCGM) というデータセンター向けの GPU 管理ツールを用いて GPU のメトリクスを取得しています。そのデータを Shell Script で node-exporter の Textfile Collector にメトリクスを渡している仕組みです。
インストール
(ここは基本的にNVIDIA/gpu-monitoring-toolsの README と同じことが書いてあります)
dcgm-exporter は systemd と Docker Image (nvidia/dcgm-exporter)が提供されています。
dcgm-exporter を使うには systemd と Docker の両方の場合で NVIDIA Driver のバージョン 384 以上を先にインストールしておく必要があります。
README には NVIDIA Tesla drivers と書いてありますが GeForce でも使えることを確認しています。ただし、EEC メモリなどに対応してないため一部のメトリクスは取得できません。
systemd
systemd を使った dcgm-exporter のインストールには別途 DCGM の rpm パッケージもしくは deb パッケージを https://developer.nvidia.com/data-center-gpu-manager-dcgm からダウンロードしてくる必要があります。
インストールには make のスクリプトが用意されています。
git clone https://github.com/NVIDIA/gpu-monitoring-tools cd path/to/gpu-monitoring-tools/exporters/prometheus-dcgm # Download and install DCGM, then sudo make install sudo systemctl start prometheus-dcgm
を実行すればバックグラウンドで dcgm-exporter のデーモンが動くので /run/prometheus
以下に dcgm.prom
というファイルが生成されます。
これを node-exporter が読み込み、 Prometheus 側にメトリクスを渡してくれます。
アンインストールするときは
cd path/to/gpu-monitoring-tools/exporters/prometheus-dcgm sudo systemctl stop prometheus-dcgm sudo make uninstall
をすればいいです。
Docker
Docker で dcgm-exporter を使う場合、Docker の runtime を nvidia-docker2 にする必要があるので、インストールしておきましょう。
docker-compose で動かすならすでにあるので docker-compose up
で動かせばすぐ使えます。
git clone https://github.com/NVIDIA/gpu-monitoring-tools
cd path/to/gpu-monitoring-tools/exporters/prometheus-dcgm
docker-compose up
Kubernetes
Kubernetes で動かす場合、リポジトリにある node-exporter-daemonset.yaml
を動かせば良いです。
しかし Helm を使って prometheus をデプロイした場合は node-exporter の DaemonSet にまとめることはできません。 そこで node-exporter と dcgm-exporter の DaemonSet を分けることで使えるようになります。
まず dcgm-exporter のみの Pod をデプロイする DaemonSet を作成します。
apiVersion: extensions/v1beta1 kind: DaemonSet metadata: labels: app: prometheus chart: prometheus-<version> component: node-exporter heritage: Tiller release: prometheus name: prometheus-dcgm-exporter namespace: <yournamespace> spec: selector: matchLabels: app: prometheus component: node-exporter release: prometheus template: metadata: labels: app: prometheus component: node-exporter release: prometheus spec: nodeSelector: nvidia.com/gpu: true containers: - image: nvidia/dcgm-exporter:<version> name: nvidia-dcgm-exporter securityContext: runAsNonRoot: false runAsUser: 0 volumeMounts: - name: collector-textfiles mountPath: /run/prometheus hostNetwork: true hostPID: true serviceAccount: prometheus-node-exporter serviceAccountName: prometheus-node-exporter volumes: - hostPath: path: /host/path/to/dcgm-exporter name: collector-textfiles
ここで重要なのは securityContext
と volumeMounts
の部分です。
securityContext
に runAsNonRoot
と runAsUser
には dcgm-exporter が GPU のプロセス情報を取ってくるために Root ユーザーでコンテナを動かす設定です。
volumeMounts
には dcgm-exporter のデフォルトアウトプット先である /run/prometheus
を指定します。これを別の場所に指定しまうと node-exporter 側でメトリクスが取れなくなってしまいます。
他にも、 volumes
は dcgm-exporter が取得したメトリクスのファイルを node-exporter へ渡すために使います。
なので、node-exporter の values にも同じ path を指定します。
nodeSelector
は GPU が入っている Node と GPU がない Node が混在している場合に、 nodeSelector
を用いて GPU がない Node には dcgm-exporter をデプロイしないような設定が必要です。
yaml が書けたら kubectl
コマンドでデプロイします。
続いて、 stable/prometheus
の values に node-exporter が dcgm-exporter のメトリクスファイルを取得する設定を追加していきます。
nodeExporter: ## Additional node-exporter container arguments ## extraArgs: collector.textfile.directory: /srv/txt-collector ## Additional node-exporter hostPath mounts ## extraHostPathMounts: - name: collector-textfiles mountPath: /srv/txt-collector hostPath: /host/path/to/dcgm-exporter readOnly: true
stable/prometheus
の values には dcgm-exporter のメトリクスファイルを取得するために extraArgs
と extraHostPathMounts
を指定します。
extraHostPathMounts
にはマウントさせる名前とコンテナにマウントする Path、ホスト側の Path、readOnly にするかどうかを指定します。
ここで name
と mountPath
は自由に決めても大丈夫です。 hostPath
に関しては dcgm-exporter の DaemonSet に書かれた volumes
の Path を指定してください。
extraArgs
には https://github.com/prometheus/node_exporter#textfile-collector にあるように collector.textfile.directory
のオプションに対して extraHostPathMounts
で指定した mountPath
を指定します。
書けたら helm install
で Prometheus をデプロイします。
helm install --namespace <yournamespace> --name prometheus -f apps/prometheus/helm.yml stable/prometheus
しばらくすると Prometheus 側で dcgm_gpu_temp
のようなメトリクスが取得できます。
取得するメトリクスをカスタマイズする
ほとんどの場合、すでに組み込まれているメトリクスのみで十分ですが、GPU のブランド名や GPU ファンの回転率などのメトリクスを取りたい場合には独自でカスタマイズをする必要があります。
NVIDIA/gpu-monitoring-tools
にある dcgm-exporter の Dockerfile を見てみると、dcgm-exporter
というスクリプトを実行していることがわかります。
実際に dcgm-exporter
のスクリプトをみると 43 行から 130 行のところに Data Center GPU Manager のコマンドラインツールである dcgmi
というコマンドを使ってメトリクスを生成しています。
よく読むと e オプションで指定されている ID らしきものを追加で指定することで新しいメトリックを取ることが出来そうです。
ですがその ID が何の意味を持つのか分からないので、一覧で表示させます。
dcgm-exporter のコンテナに bash でログインして dcgmi dmon -l
を実行するとそれぞれのフィールドの Long name、Short name、Field Id が一覧でみることができます。
## コンテナにログイン kubectl -n <yournamespace> exec -it prometheus-dcgm-exporter-768gf bash\ ## 以下 コンテナ内 root@hostname:/% dcgmi dmon -l ___________________________________________________________________________________ Long Name Short Name Field Id ___________________________________________________________________________________ driver_version DRVER 1 nvml_version NVVER 2 process_name PRNAM 3 device_count DVCNT 4 name DVNAM 50 brand DVBRN 51 nvml_index NVIDX 52 serial_number SRNUM 53 uuid UUID# 54 minor_number MNNUM 55 oem_inforom_version OEMVR 56 pci_busid PCBID 57 pci_combined_id PCCID 58 pci_subsys_id PCSID 59 ... vgpu_instance_vm_id VVMID 520 vgpu_instance_vm_name VMNAM 521 vgpu_instance_type VITYP 522 vgpu_instance_uuid VUUID 523 vgpu_instance_driver_version VDVER 524 vgpu_instance_memory_usage VMUSG 525 vgpu_instance_license_status VLCST 526 vgpu_instance_frame_rate_limit VFLIM 527 vgpu_instance_enc_stats VSTAT 528 vgpu_instance_enc_sessions_info VSINF 529 nvlink_recovery_errors RECER 600 nvlink_fatal_errors GNVFTL 601
これをみると、ドライバのバージョンを取得するときは ID に 1
を追加することで取得できることがわかります。
独自のメトリックを取る
今回はファンのスピードを追加してみます。
dcgmi dmon -l
コマンドでみたファンのスピードを取得する ID は 191
です
root@hostname:/% dcgmi dmon -l | grep fan fan_speed FANSP 191
なので dcgm-exporter
に以下の設定を追加します。
dcgmi dmon -d "${COLLECT_INTERVAL_MS}" -e \ "54,"\ "100,101,"\ - "140,150,155,"\ + "140,150,155,191,"\ "202,203,204,206,207,"\ "230,240,241,242,243,244,245,246,"\ "251,252,"\ "310,311,312,313,"\ "390,391,392,"\ "409,419,429,439" | \ awk -v "out=${OUTPUT_FILE}" -v "ngpus=$(nvidia-smi -L | wc -l)" ' function metric(name, type, help, value) { if (value !~ "N/A") { if (gpu == 0) { printf "# HELP dcgm_%s %s\n", name, help > out".swp" printf "# TYPE dcgm_%s %s\n", name, type > out".swp" } printf "dcgm_%s{gpu=\"%s\",uuid=\"%s\"} %s\n", name, gpu, uuid, value > out".swp" } } (NF && NR > 2 && !($1 ~ "^#" || $1 ~ "^Id")) { # Labels i = 1 gpu = $(i++) # field 0 (implicit) uuid = $(i++) # field 54 # Clocks metric("sm_clock", "gauge", "SM clock frequency (in MHz).", $(i++)) # field 100 metric("memory_clock", "gauge", "Memory clock frequency (in MHz).", $(i++)) # field 101 # Temperature metric("memory_temp", "gauge", "Memory temperature (in C).", $(i++)) # field 140 metric("gpu_temp", "gauge", "GPU temperature (in C).", $(i++)) # field 150 # Power metric("power_usage", "gauge", "Power draw (in W).", $(i++)) # field 155 #metric("total_energy_consumption", "counter", "Total energy consumption since boot (in mJ).", $(i++)) # field 156 TOBEFIXED + # Fan + metric("fan_speed", "gauge", "Fan speed (in %).", $(i++)) # field 191 # PCIe #metric("pcie_tx_throughput", "counter", "Total number of bytes transmitted through PCIe TX (in KB)", $(i++)) # field 200 TOBEFIXED #metric("pcie_rx_throughput", "counter", "Total number of bytes received through PCIe RX (in KB)", $(i++)) # field 201 TOBEFIXED metric("pcie_replay_counter", "counter", "Total number of PCIe retries.", $(i++)) # field 202 # ... #metric("nvlink_bandwidth_total", "counter", "Total number of NVLink bandwidth counters for all lanes", $(i++)) # field 449 TODO # Flush output file and move it for atomicity if (gpu == ngpus - 1) { close(out".swp") system("mv "out".swp "out) } }' &
metric
を追加するときは、 e オプションに追加した ID とコメントで書かれた field ID の順番を同じにしないと追加した箇所以下のメトリックが全て違う値になってしまうので注意してください。
この方法では GPU の製品名(GeForce 1080 Ti など)を取ることができますが、awk のデフォルトの区切り文字がスペース or tab なのでメトリックがずれていくことになるので、別途その修正を入れる必要があります。
例えば、GeForce 1080 Ti の場合、GeForce と 1080 と Ti で分割されることになりずれます。
じゃあ awk の-F
オプションで区切り文字を変えればいいじゃないかと考えてしまいますが image のベースになっている Ubuntu では awk の実装に mawk を用いているので -F
オプションでの正規表現の range をサポートしていません。なので sed などで 2 つ以上のスペースを tab に変換するなどの作業が必要です。
image を作る
欲しいメトリックが取れるようなスクリプトを作ったら docker build
コマンドで image を作ります。
docker build -t dcgm-exporter .
できたイメージを Docker Hub などに Push して dcgm-exporter の DaemonSet の image を作った image に変えてもらうことで独自のメトリクスを取ることができます。