DCGM Exporter を用いた Kubernetes における NVIDIA GPU 監視環境の構築

はじめに

以前こちらの 死活監視システムの構築 で Prometheus と Grafana を用いた GPU 監視環境の構築に関する記事をかきました. 1年も経つと様子がだいぶ変わってきているようなので再度調査しました. 使用している Kubernetes は 1.19.4 です.

prometheus_architechture
Prometheus アーキテクチャ

画像出典: Integrating GPU Telemetry into Kubernetes — NVIDIA Cloud Native Technologies documentation

構成

今回は OSS として公開されているコンポーネントのうち以下の点の物を使用していきます.また,全てパッケージ管理ツールである Helm v3 を用いて管理していきます.

使用バージョン一覧

コンポーネント APP バージョン Chart バージョン
kube prometheus stack 0.43.2 12.2.2
DCGM-Exporter 2.1.0 2.1.0
ingress nginx 0.41.2 3.11.0

kube prometheus stack

kube prometheus stack とは prometheus operatorgrafananode exporter などが含まれているコンポーネントです.kube prometheus stack をデプロイするだけでこれら全てのコンポーネントをデプロイすることができます.

DCGM-Exporter

NVIDIA が提供している prometheus 用の exporter です. GPU の温度や使用率,クロック周波数など様々なメトリクスを収集することができます.

ingress nginx

KubernetesIngress Cotroller です.今回は,prometheus,alertmanager,grafana へのアクセスに ingress を用います.

構築

ingress nginx,kube prometheus stack,DCGM-Exporter の順番で構築していきます.特に kube prometheus stack と DCGM-Exporter の構築順序を変更しないでください.逆にして構築してしまうと DCGM-Exporter デプロイ時にno matches for kind "ServiceMonitor" in version "monitoring.coreos.com/v1 とエラーになってデプロイすることができません.

また,今回は monitoring namespace に全てデプロイするので事前に作成しておいてください.

ingress nginx

helm を使用するのでお好みの設定の values.yaml を作成してデプロイするだけです.私の環境では以下のようにしました.設定可能項目はhelm show values ingress-nginx/ingress-nginx で見ることができます.

controller:
  ingressClass: nginx-monitoring
  affinity: 
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions: 
          - key: hardware-type
            operator: In
            values:
            - NonGPU
  service:
    loadBalancerIP: xxx.xxx.xxx.xxx
  replicaCount: 2  

ポイントは ingressClass の部分です.複数の ingress controller を使用する場合はここを設定する必要があります.また,loadBalancerIP の部分は固定したい場合のみ指定し,指定しない場合はランダムで割り振られます.

デプロイします.

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update
$ helm install ingress-monitoring --namespace monitoring ingress-nginx/ingress-nginx --values values.yaml

kube prometheus stack

こちらは複数のコンポーネントがまとまっているためかなり設定項目があります.まずは,それぞれのコンポーネントの設定可能値を確認することをお勧めします.

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
$ # kube prometheus の設定可能値
$ helm show values prometheus-community/kube-prometheus-stack
$ # grafana の設定可能値
$ helm show values grafana/grafana

全体的な設定

全体的な設定として以下のようにしました.

namespaceOverride: "monitoring"
defaultRules:
  rules:
    etcd: false
kubeEtcd:
  enabled: false

kubeControllerManager:
  service:
    port: 10257
    targetPort: 10257   
  serviceMonitor:
    https: "true"
    insecureSkipVerify: "true"

kubeScheduler:
  service:
    port: 10259
    targetPort: 10259
  serviceMonitor:
    https: "true"
    insecureSkipVerify: "true"
    

まず,こちらの Issue [prometheus-kube-stack] Target Kubelet 0/0 up and others are down にあるように,kubernetes では kubeScheduler やkubeApiserver や kubeControllermanager では http での通信が廃止されているにも関わらず,デフォルト設定では http でメトリクスを収集しにいく設定になっているため,https でメトリクスを収集しにいく設定にしなければなりません.また,etcd については現在うまくメトリクスを収集できないそうなので,収集しないように設定します.

さらに,kubeadm で Kubernetes クラスタを構築している場合は kubeadm conf で以下のように収集を受け付けるように設定してください.

apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.19.4
scheduler:
  extraArgs:
    bind-address: 0.0.0.0
controllerManager:
  extraArgs:
    bind-address: 0.0.0.0

kubeProxy は kubectl edit cm/kube-proxy -n kube-system で以下のように設定を変更する必要があります.

...
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0:10249
...

alertmanager の設定

以下のようにしました.

alertmanager:
  ingress:
    enabled: true
    ingressClassName: nginx-monitoring
    hosts:
      - hogehoge.hoge.hoge
    paths:
      - /alertmanager
  alertmanagerSpec:
    replicas: 2
    externalUrl: hogehoge.hoge.hoge
    routePrefix : alertmanager/  
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU

ポイントとして,ingressClassName に先ほど ingress nginx で設定したクラス名を指定する必要があります.

また,alertmanager.ingress.hostsalertmanager.alertmanagerSpec.externalUrlalertmanager.ingress.pathsalertmanager.alertmanagerSpec.routePrefix は揃える必要があります.

grafana の設定

以下のように設定しました.

grafana:
  adminPassword: hogehoge
  grafana.ini:
    server:
      domain: hogehoge.hoge.hoge
      root_url: "%(protocol)s://%(domain)s/grafana"
      serve_from_sub_path: true
    auth.anonymous:
      enabled: true
      org_role: Viewer
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "nginx-monitoring"
      nginx.ingress.kubernetes.io/rewrite-target: /$1
      nginx.ingress.kubernetes.io/use-regex: "true"
    hosts:
      - hogehoge.hoge.hoge
    path: /grafana/?(.*)
  sidecar:
    dashboards:
      enabled: true
    datasources:
      defaultDatasourceEnabled: false
  datasources: 
    createPrometheusReplicasDatasources: true
    datasources.yaml:
      apiVersion: 1
      datasources:
      - name: Prometheus
        type: prometheus
        url: http://kube-prometheus-kube-prome-prometheus:9090/prometheus
        access: proxy
        isDefault: true
        editable: false
  affinity:     
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU

grafana.ini でアクセスに関する設定と anonymous ユーザに閲覧権限のみ付与する設定を行います.

grafana.ingress では,/grafana でアクセスできるように設定しています.ただ単に grafana.ingress.path/grafana を設定した場合 /garafa/ でないとアクセスすることができないので気をつけてください.

また,prometheus ,alertmanager ともに ingressClass に対応していますが, kube prometheus stack で使用している grafana は 5.8 系列のため grafana.ingress.annotations.kubernetes.io/ingress.class で ingressClass を指定する必要がある点も注意が必要です.

次に,GPU 用のダッシュボードについてですが,NVIDIAここに 用意してくれています.Grafana Labs にもありますが,少し古いので注意が必要です.

Grafana ではドキュメント にあるように様々な形式でダッシュボードをインポートすることができます.しかしこちらの issue にある通り,ダッシュボード変数が認識されないため, NVIDIA が用意してくれているダッシュボードに微修正が必要です.そのため今回は sidecar を使った形式 で import を行います. これは, grafana_dashboard: "1" のラベルを付与した configmap を作成すると自動でインポートしてくれる優れものです.

ダッシュボードの変更箇所は以下の通りです.

... 略
  "templating": {
    "list": [
+      {
+        "hide": 0,
+        "label": "datasource",
+        "name": "DS_PROMETHEUS",
+        "options": [],
+        "query": "prometheus",
+        "refresh": 1,
+        "regex": "",
+        "type": "datasource"
+      }, 
以下略

私は configmap の生成には kustomize を使用したため 以下のような kutomization ファイル を作成しましたが,通常のマニフェストでも可能です.

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: monitoring

generatorOptions:
  disableNameSuffixHash: true
  labels:
    grafana_dashboard: "1"

configMapGenerator:
  - name: nvidia-grafana-dashboard
    files:
      - configs/dcgm-exporter-dashboard.json

Prometheus の設定

以下のように設定しました.

prometheusOperator:
  admissionWebhooks:
    patch:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: hardware-type
                operator: In
                values:
                - NonGPU
prometheus:
  ingress:
    enabled: true
    ingressClassName: nginx-monitoring
    hosts:
      - hogehoge.hoge.hoge
    paths:
      - /prometheus
  affinity:     
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU
  prometheusSpec:
    serviceMonitorSelectorNilUsesHelmValues: false
    podMonitorSelectorNilUsesHelmValues: false
    replicas: 2    
    affinity: 
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
              - key: hardware-type
                operator: In
                values:
                - NonGPU
    externalUrl: http://hogehoge.hoge.hoge/prometheus
    routePrefix : prometheus/
    additionalScrapeConfigs:
    - job_name: gpu-metrics
      scrape_interval: 1s
      metrics_path: /metrics
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
          - gpu-operator-resources
      relabel_configs:
      - source_labels: [__meta_kubernetes_pod_node_name]
        action: replace
        target_label: kubernetes_node                
    retention: 30d    
    retentionSize: 290GB
    storageSpec: 
     volumeClaimTemplate:
       spec:
         storageClassName: rook-ceph-block
         accessModes: ["ReadWriteOnce"]
         resources:
           requests:
             storage: 300Gi

DCGM-Exporter からメトリクスを収集するためには prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues は必ず false にする必要があります.また,NVIDIA のドキュメントにあるようにprometheus.prometheusSpec.additionalScrapeConfigs にも設定が必要です. さらに今回データは rook ceph から払い出した Block ストレージに吐き出すように設定し,永続化しています.

デプロイ

最終的に出来上がった values.yaml は以下のようになります.

namespaceOverride: "monitoring"
defaultRules:
  rules:
    etcd: false
# alertmanager
alertmanager:
  ingress:
    enabled: true
    ingressClassName: nginx-monitoring
    hosts:
      - hogehoge.hoge.hoge
    paths:
      - /alertmanager
  alertmanagerSpec:
    replicas: 2
    externalUrl: http://hogehoge.hoge.hoge/alertmanager
    routePrefix : alertmanager/  
    affinity:
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU
# grafana              
grafana:
  adminPassword: hogehoge
  grafana.ini:
    server:
      domain: hogehoge.hoge.hoge
      root_url: "%(protocol)s://%(domain)s/grafana"
      serve_from_sub_path: true
    auth.anonymous:
      enabled: true
      org_role: Viewer
  ingress:
    enabled: true
    annotations:
      kubernetes.io/ingress.class: "nginx-monitoring"
      nginx.ingress.kubernetes.io/rewrite-target: /$1
      nginx.ingress.kubernetes.io/use-regex: "true"
   hosts:
      - hogehoge.hoge.hoge
    path: /grafana/?(.*)
  sidecar:
    dashboards:
      enabled: true
    datasources:
      defaultDatasourceEnabled: false
  datasources: 
    createPrometheusReplicasDatasources: true
    datasources.yaml:
      apiVersion: 1
      datasources:
      - name: Prometheus
        type: prometheus
        url: http://kube-prometheus-kube-prome-prometheus:9090/prometheus
        access: proxy
        isDefault: true
        editable: false
  affinity:     
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU
prometheusOperator:
  admissionWebhooks:
    patch:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: hardware-type
                operator: In
                values:
                - NonGPU
prometheus:
  ingress:
    enabled: true
    ingressClassName: nginx-monitoring
    hosts:
      - hogehoge.hoge.hoge
    paths:
      - /prometheus
  affinity:     
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: hardware-type
              operator: In
              values:
              - NonGPU

  prometheusSpec:
    serviceMonitorSelectorNilUsesHelmValues: false
    podMonitorSelectorNilUsesHelmValues: false
    replicas: 2    
    affinity: 
      nodeAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
          nodeSelectorTerms:
            - matchExpressions:
              - key: hardware-type
                operator: In
                values:
                - NonGPU
    externalUrl: http://hogehoge.hoge.hoge/prometheus
    routePrefix : prometheus/
    additionalScrapeConfigs:
    - job_name: gpu-metrics
      scrape_interval: 1s
      metrics_path: /metrics
      scheme: http
      kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
          - gpu-operator-resources
      relabel_configs:
      - source_labels: [__meta_kubernetes_pod_node_name]
        action: replace
        target_label: kubernetes_node                
    retention: 30d    
    retentionSize: 290GB
    storageSpec: 
     volumeClaimTemplate:
       spec:
         storageClassName: rook-ceph-block
         accessModes: ["ReadWriteOnce"]
         resources:
           requests:
             storage: 300Gi
kubeEtcd:
  enabled: false
kubeControllerManager:
  service:
    port: 10257
    targetPort: 10257   
  serviceMonitor:
    https: "true"
    insecureSkipVerify: "true"
kubeScheduler:
  service:
    port: 10259
    targetPort: 10259
  serviceMonitor:
    https: "true"
    insecureSkipVerify: "true"

デプロイしていきます.

# kube prometheus stack のデプロイ
$ helm install kube-prometheus prometheus-community/kube-prometheus-stack -f values.yaml 
# dcgm 用ダッシュボード configmap のデプロイ
$ kustomize build dashboards | kubectl apply -f -

DCGM-Exporter

以下のような values.yaml を使用して GPU が刺さっている Node のみにデプロイされるようにしました.

arguments: []
affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: hardware-type
          operator: In
          values:
          - NVIDIAGPU

ポイントとしては,こちらの issue で報告されているようにTesla V100 と Tesla T4 以外を使用している場合は必ず arguments: [] の指定が必要です.

デプロイしていきます.

$ helm repo add gpu-helm-charts https://nvidia.github.io/gpu-monitoring-tools/helm-charts
$ helm repo update
$ helm install dcgm-exporter --namespace monitoring gpu-helm-charts/dcgm-exporter -f values.yaml

ダッシュボード

prometheus には http://hogehoge.hoge.hoge/prometheus,alertmanager には http://hogehoge.hoge.hoge/alertmanager grafana には http://hogehoge.hoge.hoge/grafana でアクセスできます.アクセスできない場合やメトリクスが収集できていない場合は,必要なポートが空いているかなど確認してください.

nvidia_dsgm_exporter_dashbord
NVIDIA DCGM Exporter Dashbord

最後に

今回は去年行った GPU 監視環境についてみなおしてみました.だいぶ理想的な形になったのではないかと思います.ではまたお会いしましょう.

参考