TopoLVMによるPVC-basedなRook/Ceph with Pod Topology Spread Constraints

はじめに

2020/07/03にJapan Rook Meetup #3 - connpassにて「ML環境でのRook/Ceph」と題してお話させていただきました.

speakerdeck.com

話の中でCSIにTopoLVMを使用したOSDs on PVCs環境を用いたデモを行ったのですが,その際使用した環境を構築した手順を残しておきます. また,本記事で紹介する構築はシングルマスター構成K8sクラスタですが,マスター冗長構成K8sクラスタにおいても同様の方法で構築することができます.

前提条件

スライド中にも記載しているのですが,以下の環境で構築します. パブリッククラウドを用いた環境ではないためパブリッククラウドを使用される際はご注意ください.

TopoLVMの導入

はじめにTopoLVMについて軽く紹介した後に実際に構築していきます.

TopoLVMとは

Kubernetes(以下K8s)でストレージを扱う際,マネージドK8sサービスではCSIドライバが用意されており,K8sでのダイナミックプロビジョニングなど非常に容易に行えるようになっています.(例えばEKSのAmazon EBS CSIドライバー)しかしながらベアメタルサーバなどの,特にローカルストレージを束ねてK8sのストレージとして使用しようとすると何らかのCSIドライバを用意してあげなくてはいけません.

TopoLVMではPVCが発行されるとLinuxのLogical Volumeの仕組みを利用し,TopoLVM用のK8s拡張スケジューラによって適切にPVを作成する Nodeを選択しLogical VolumeのVGからLVを切り出してダイナミックプロビジョニングしてくれます.

dynamic_provisioning_by_topolvm
図1: TopoLVMによるダイナミックプロビジョニングの様子

PVC-basedなRook/Cephを使う場合hostpathを利用して手動でPVを作成することも可能ですが,今回はTopoLVMをCSIドライバとして使用することでダイナミックプロビジョニングを可能にします.

より詳しいTopoLVMのアーキテクチャなどについてはTopoLVMを作成しているサイボウズさんのブログに詳しくまとめられているのでそちらをご覧ください.

blog.cybozu.io

TopoLVM準備

基本的に以下の公式ドキュメントに則って構築していきますので適宜参照してください.またこの節で行う工程はストレージを使用する全てのNodeで必須です.

github.com

まずはじめに使用ポートを開放します.

$ sudo ufw allow 9251

次にlvm2をインストールし,volume groupを作成していきます. 今回volume groupに使用する物理デバイス名は「/dev/disk/by-id/」を参照させます.物理デバイス名は各環境に合わせて以下作業を行ってください.

$ sudo apt install -y lvm2
$ sudo vgcreate myvg scsi-360022480166e76746b201caa42ba7db7 \
scsi-3600224804f19c35d51d9ec43cb37351a

上記ではvg名をmyvgとしていますが,任意の名前を使用可能です.

次にsystemdとしてlvmdを動かしていきます. 事前に以下からlvmdのビルド済みファイルをダウンロードして おいてください.本記事では執筆時点で最新版のv0.5.0を使用していきます.

github.com

次に以下からlvmd.serviceをダウンロードしてきてください.

github.com

ダウンロードできたら配置し,起動していきます.

$ sudo mkdir /opt/sbin
$ mv lvmd /opt/sbin/lvmd
$ mv lvmd.service /etc/systemd/system/lvmd.service
$ sudo systemctl start lvmd

topolvm-schedulerの導入

topolvmではkube-schedulerを拡張するscheduler-extenderを使用しています.本節ではkubeadmを使用してK8sクラスタを構築する前提でtopolvm-schedulerの準備をしていきます.

まずはじめに使用するファイルを以下からダウンロードしてきてください.

github.com

次にダウンロードしてきたフォルダの「deploy/scheduler-config/scheduler-donfig.yaml」「deploy/scheduler-config/scheduler-policy.cfg」を以下のように配置します.

$ mkdir /etc/kubernetes/topolvm/
$ mv deploy/scheduler-config/* /etc/kubernetes/topolvm/

K8sクラスタの構築

共通事前準備

ポートを開放していきます.

  • control plane node
$ sudo ufw allow 53 &&\
sudo ufw allow 6443-6444 &&\
sudo ufw allow 6789 &&\
sudo ufw allow 2379:2380/tcp &&\
sudo ufw allow 8285 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250:10252/tcp &&\
sudo ufw allow 31416
  • node
$ sudo ufw allow 53 &&\
sudo ufw allow 6444 &&\
sudo ufw allow 8225 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250 &&\
sudo ufw allow 30000:32767/tcp &&\
sudo ufw allow 31416

Control Plane Node

まずはじめに以下のようなkubeadm-configファイル「kubeadm_config.yaml」を作成してtopolvm-schedulerを使用できるK8sクラスタを構築していきます.

apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.18.3
scheduler:
  extraVolumes:
    - name: "config"
      hostPath: /etc/kubernetes/topolvm/
      mountPath: /var/lib/scheduler
      readOnly: true
  extraArgs:
    config: /var/lib/scheduler/scheduler-config.yaml
networking:
  podSubnet: "10.244.0.0/16"

上記のkubeadm-configファイルはCNIとしてflannelを使用する前提ですので,他のCNIを使用する場合は適宜変更してください.

作成したkubeadm-configを使用してK8sクラスタを構築します.

$ kubeadm init --upload-certs --config=/etc/kubeadm/kubeadm_config.yaml
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

CNIをデプロイします.

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 

Node

kubeadm initの最後に出力されたトークンを使用してK8sクラスタに参加させます.

$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
>     --discovery-token-ca-cert-hash sha256:hogehoge

topolvmのデプロイ

事前準備

次節からではhelm3とkustmizeを使用してアプリケーションをデプロイしていくため事前準備としてhelm3とkustmizeをインストールしていきます.既にインストール済みの方は飛ばしていただいて大丈夫です.

helm3

ubuntu環境では以下のドキュメントにしたがってインストールしていきます.

helm.sh

curl https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add -
$ sudo apt-get install apt-transport-https --yes
$ echo "deb https://baltocdn.com/helm/stable/debian/ all main" | \
sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
$ sudo apt-get update
$ sudo apt-get install helm

kustomize

以下の公式Githubから最新のバイナリファイルをダウンロードしてきてください. 本記事ではv3.6.1を使用しています.

github.com

ダウンロードしたら,実行権限を与えて配置します.

$ tar  -vfxz kustomize_v3.8.0_linux_amd64.tar.gz
$ chmod -x kustomize_v3.8.0_linux_amd64
$ mv  kustomize_v3.8.0_linux_amd64 /usr/local/bin/kustomize

デプロイ

まずはじめにtopolvmm-controllerが使用する自己証明書発行のためにcert-managerをデプロイします.

cert-manager.io

今回はhelm3を使用してデプロイしていきます.

$ helm repo add jetstack https://charts.jetstack.io
$ kubectl create namespace cert-manager
$ helm install cert-manager --namespace cert-manager jetstack/cert-manager \
--values $HOME/K8s_manifest/certmanager/values.yaml --version v0.15.1

次にkustomizeを用いてTopoLVMをデプロイしていきます. マニフェストtopolvm-schedulerの導入でダウンロードしてきた「topolvm/deplot/manifest/」を使用していきます.

$ kubectl label ns kube-system topolvm.cybozu.com/webhook=ignore
$ kustomize build topolvm/deploy/manifest/overlays/daemonset-scheduler | kubectl apply -f -

次に,node Podが全て正常に起動したことを確認した後に,以下を実行します.

$ kubectl get po -n topolvm-system -l app.kubernetes.io/name=node
NAME         READY   STATUS    RESTARTS   AGE
node-b6r52   3/3     Running   0          6h1m
node-d9dm4   3/3     Running   0          6h33m
node-g7d92   3/3     Running   0          5h58m
node-kk5wv   3/3     Running   0          6h33m
node-qttzx   3/3     Running   0          6h33m
node-w9nbc   3/3     Running   0          6h5m
$
$ kubectl apply -f topolvm/deploy/manifest/base/certificates.yaml
$
$ kubectl get po -n topolvm-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-6457d85455-8ks5k   5/5     Running   0          6h34m
controller-6457d85455-9tqhf   5/5     Running   0          6h34m
node-b6r52                    3/3     Running   0          6h2m
node-d9dm4                    3/3     Running   0          6h34m
node-g7d92                    3/3     Running   0          5h59m
node-kk5wv                    3/3     Running   0          6h34m
node-qttzx                    3/3     Running   0          6h34m
node-w9nbc                    3/3     Running   0          6h6m
topolvm-scheduler-4qvwm       1/1     Running   0          6h34m

以上でTopoLVMの準備は完了です.

Rook/Cephのデプロイ

事前準備

まずはじめに各Nodeにrackをラベルとして与えます. 「vm-w0」のようなところは適切なホスト名に置き換えてください.

$ kubectl label node vm-w0 topology.rook.io/rack=rackA
$ kubectl label node vm-w1 topology.rook.io/rack=rackA
$ kubectl label node vm-w2 topology.rook.io/rack=rackB
$ kubectl label node vm-w3 topology.rook.io/rack=rackB
$ kubectl label node vm-w4 topology.rook.io/rack=rackC
$ kubectl label node vm-w5 topology.rook.io/rack=rackC

デプロイ

「common.yaml」「operator.yaml」をデプロイしていきます. ここは通常のHOST-basedパターンと同様です.

$ deploy K8s_manifest/rook-1.3.5/cluster/examples/kubernetes/ceph/common.yaml
$ kubectl apply -f K8s_manifest/rook-1.3.5/cluster/examples/kubernetes/ceph/operator.yaml

次に「cluster-on-pvc.yaml」を以下のように書き換えます. 以下のマニフェストでは発表資料と同様に10GBのOSDを12個作成する構成になっています. またPodTopologySpreadConstaraintsにより,rack障害耐性,rack内node障害耐性を持たせた構成になっています. 注意点として必ず2箇所のstorageClassNameの指定はtopolvm-provisionerにしてください.

apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
  name: rook-ceph
  namespace: rook-ceph
spec:
  dataDirHostPath: /var/lib/rook
  mon:
    count: 3
    allowMultiplePerNode: false
    volumeClaimTemplate:
      spec:
        storageClassName: topolvm-provisioner
        resources:
          requests:
            storage: 3Gi
  cephVersion:
    image: ceph/ceph:v14.2.9
    allowUnsupported: false
  skipUpgradeChecks: false
  continueUpgradeAfterChecksEvenIfNotHealthy: false
  mgr:
    modules:
    - name: pg_autoscaler
      enabled: true
  dashboard:
    enabled: true
    ssl: true
  crashCollector:
    disable: false
  storage:
    storageClassDeviceSets:
    - name: topolvm-ssd-cluster
      count: 12
      portable: false
      tuneSlowDeviceClass: true
      placement:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd-prepare
              topologyKey: kubernetes.io/hostname
        topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.rook.io/rack
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          # whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare
      resources:
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          resources:
            requests:
              storage: 10Gi
          storageClassName: topolvm-provisioner
          volumeMode: Block
          accessModes:
            - ReadWriteOnce
  disruptionManagement:
    managePodBudgets: false
    osdMaintenanceTimeout: 30
    manageMachineDisruptionBudgets: false
    machineDisruptionBudgetNamespace: openshift-machine-api

CephFSのデプロイ

最後にCephFSもrack障害耐性,rack内node障害耐性を持たせてデプロイします.

以下のように「filesystem.yaml」を設定してください.

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: tenzenfs
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: rack
    replicated:
      size: 3
      requireSafeReplicaSize: true
  dataPools:
    - failureDomain: rack
      replicated:
        size: 3
        requireSafeReplicaSize: true
      compressionMode: none
  preservePoolsOnDelete: true
  metadataServer:
    activeCount: 1
    activeStandby: true
    placement:
       podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - rook-ceph-mds
            topologyKey: kubernetes.io/hostname
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-mds
              topologyKey: topology.rook.io/rack
    annotations:
    resources:

デプロイしていきます.

$ kubectl apply -f  filesystem.yaml

以上でjapan rook meetup #3 Hands onで使用した環境が出来上がります.

おわりに

今回はmeetupで使用した環境について紹介しました. 参考になれば幸いです.

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 監視環境についてみなおしてみました.だいぶ理想的な形になったのではないかと思います.ではまたお会いしましょう.

参考