写点什么

DCGM: 监控 Kubernetes 集群的 GPU 资源

用户头像
DCOS
关注
发布于: 2021 年 03 月 22 日


一、背景介绍


对于 SRE 团队来说,实现监控 AI、高性能计算平台上大规模 GPU 资源,至关重要。SRE 团队可以通过 GPU 指标了解工作负载等相关性能,从而优化资源分配,提升资源利用率及异常诊断,以提高数据中心资源的整体效能。除了 SRE 及基础设施团队之外,不管你是从事 GPU 加速方向的研究人员,还是数据中心架构师,都可以通过相关监控指标,了解 GPU 利用率和工作饱和度以进行容量规划及任务调度等。


随着 AI/ML 工作负载的容器化,调度平台采用具备动态扩缩特性的 Kubernetes 解决方案,针对其监控的急迫性日益提升。在这篇文章中,我们将介绍 NVIDIA 数据中心 GPU 管理器(DCGM),以及如何将其集成到 Prometheus 和 Grafana 等开源工具中,以实现 Kubernetes 的 GPU 监控的整体解决方案。


二、NVIDIA DCGM


NVIDIA DCGM 是用于管理和监控基于 Linux 系统的 NVIDIA GPU 大规模集群的一体化工具。它是一个低开销的工具,提供多种能力,包括主动健康监控、诊断、系统验证、策略、电源和时钟管理、配置管理和审计等。


DCGM 提供用于收集 GPU 遥测的 API。特别值得关注的是 GPU 利用率指标、内存指标和流量指标。DCGM 提供了各种语言的客户端,如 C 和 Python。对于与容器生态系统的集成,提供基于 DCGM APIs 的 Go 绑定实现。




二、DCGM exporter


监控系统通常由指标采集器、用于存储指标的时间序列数据库和可视组件组成。例如 CNCF 毕业项目 Prometheus,它和 Grafana 一起构成监控集成方案。其中 Prometheus 还包括 Alertmanager 来创建和管理警报。Prometheus、kube-state-metrics 及 node_exporter 一起部署,以获取 Kubernetes API 对象的集群指标和 CPU 利用率等节点指标。下图为 Prometheus 的示例架构。


在前面介绍的 Go API 基础上,可以通过 DCGM 向 Prometheus 暴露 GPU 指标。NVIDIA 为此构建了 dcgm-exporter 的项目。


dcgm-exporter 使用 Go 绑定从 DCGM 收集 GPU 遥测数据,然后通过 http 接口 (/metrics) 向 Prometheus 暴露指标。


dcgm-exporter 可以通过使用 csv 格式的配置文件来定制 DCGM 收集的 GPU 指标。




三、Kubernetes 集群中的每个节点 GPU 指标


dcgm-exporter 收集了节点上所有可用 GPU 的指标。然而,在 Kubernetes 中,当一个节点请求 GPU 资源时,可能不能确定哪些 GPU 会被分配给 pod。从 v1.13 开始,Kubelet 增加了一个设备监控功能,可以通过 pod-resources 套接字了解分配给 pod 的设备,其中包括 pod 名称、pod 命名空间和设备 ID。


dcgm-exporter 中的 http 服务连接到 kubelet 中的 pod-resources 服务(/var/lib/kubelet/pod-resources)来识别 pod 上运行的 GPU 设备,并将 GPU 设备的 pod 相关信息添加到收集的指标中。




四、GPU 监控方案


下面是一些设置 dcgm-exporter 的示例。如果使用 NVIDIA GPU Operator,那么 dcgm-exporter 同样是部署组件之一。


文档中包含了设置 Kubernetes 集群的步骤。为了简洁起见,假定已经存在一个运行着 NVIDIA 软件组件的 Kubernetes 集群,例如,驱动程序、容器运行时和 Kubernetes 设备插件等。在使用 Prometheus Operator 部署 Prometheus 时,还可以方便地部署 Grafana。在该篇文章中,为了简单起见,使用了单节点 Kubernetes 集群。


在设置社区提供的 Prometheus Operator 的 Helm chart 时,必须暴露 Grafana 供外部访问,并且 prometheusSpec.serviceMonitorSelectorNilUsesHelmValues 必须设置为 false。


简单来说,设置监控包括运行以下命令。


$ helm repo add prometheus-community \

https://prometheus-community.github.io/helm-charts

$ helm repo update

$ helm inspect values prometheus-community/kube-prometheus-stack > /tmp/kube-prometheus-stack.values

# Edit /tmp/kube-prometheus-stack.values in your favorite editor

# according to the documentation

# This exposes the service via NodePort so that Prometheus/Grafana

# are accessible outside the cluster with a browser

$ helm install prometheus-community/kube-prometheus-stack \

--create-namespace --namespace prometheus \

--generate-name \

--set prometheus.service.type=NodePort \

--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false


此时,集群配置如下所示,其中所有的 Prometheus pods 和服务健康运行。


$ kubectl get pods -A

NAMESPACE NAME READY STATUS RESTARTS AGE

kube-system calico-kube-controllers-8f59968d4-zrsdt 1/1 Running 0 18m

kube-system calico-node-c257f 1/1 Running 0 18m

kube-system coredns-f9fd979d6-c52hz 1/1 Running 0 19m

kube-system coredns-f9fd979d6-ncbdp 1/1 Running 0 19m

kube-system etcd-ip-172-31-27-93 1/1 Running 1 19m

kube-system kube-apiserver-ip-172-31-27-93 1/1 Running 1 19m

kube-system kube-controller-manager-ip-172-31-27-93 1/1 Running 1 19m

kube-system kube-proxy-b9szp 1/1 Running 1 19m

kube-system kube-scheduler-ip-172-31-27-93 1/1 Running 1 19m

kube-system nvidia-device-plugin-1602308324-jg842 1/1 Running 0 17m

prometheus alertmanager-kube-prometheus-stack-1602-alertmanager-0 2/2 Running 0 92s

prometheus kube-prometheus-stack-1602-operator-c4bc5c4d5-f5vzc 2/2 Running 0 98s

prometheus kube-prometheus-stack-1602309230-grafana-6b4fc97f8f-66kdv 2/2 Running 0 98s

prometheus kube-prometheus-stack-1602309230-kube-state-metrics-76887bqzv2b 1/1 Running 0 98s

prometheus kube-prometheus-stack-1602309230-prometheus-node-exporter-rrk9l 1/1 Running 0 98s

prometheus prometheus-kube-prometheus-stack-1602-prometheus-0 3/3 Running 1 92s

$ kubectl get svc -A

NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20m

kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 20m

kube-system kube-prometheus-stack-1602-coredns ClusterIP None <none> 9153/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-controller-manager ClusterIP None <none> 10252/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-etcd ClusterIP None <none> 2379/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-proxy ClusterIP None <none> 10249/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-scheduler ClusterIP None <none> 10251/TCP 2m18s

kube-system kube-prometheus-stack-1602-kubelet ClusterIP None <none> 10250/TCP,10255/TCP,4194/TCP 2m12s

prometheus alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 2m12s

prometheus kube-prometheus-stack-1602-alertmanager ClusterIP 10.104.106.174 <none> 9093/TCP 2m18s

prometheus kube-prometheus-stack-1602-operator ClusterIP 10.98.165.148 <none> 8080/TCP,443/TCP 2m18s

prometheus kube-prometheus-stack-1602-prometheus NodePort 10.105.3.19 <none> 9090:30090/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-grafana ClusterIP 10.100.178.41 <none> 80/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-kube-state-metrics ClusterIP 10.100.119.13 <none> 8080/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-prometheus-node-exporter ClusterIP 10.100.56.74 <none> 9100/TCP 2m18s

prometheus prometheus-operated ClusterIP None <none> 9090/TCP 2m12s


部署 dcgm-exporter


$ helm repo add gpu-helm-charts \

https://nvidia.github.io/gpu-monitoring-tools/helm-charts

$ helm repo update


使用 helm 安装


$ helm install \

--generate-name \

gpu-helm-charts/dcgm-exporter

可以使用以下命令观察部署情况。


$ helm ls

NAME NAMESPACE REVISION APP VERSION

dcgm-exporter-1-1601677302 default 1 dcgm-exporter-1.1.0 2.0.10

nvidia-device-plugin-1601662841 default 1 nvidia-device-plugin-0.7.0 0.7.0


Prometheus 和 Grafana 服务暴露如下:


$ kubectl get svc -A

NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

default dcgm-exporter ClusterIP 10.99.34.128 <none> 9400/TCP 43d

default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20m

kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 20m

kube-system kube-prometheus-stack-1602-coredns ClusterIP None <none> 9153/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-controller-manager ClusterIP None <none> 10252/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-etcd ClusterIP None <none> 2379/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-proxy ClusterIP None <none> 10249/TCP 2m18s

kube-system kube-prometheus-stack-1602-kube-scheduler ClusterIP None <none> 10251/TCP 2m18s

kube-system kube-prometheus-stack-1602-kubelet ClusterIP None <none> 10250/TCP,10255/TCP,4194/TCP 2m12s

prometheus alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 2m12s

prometheus kube-prometheus-stack-1602-alertmanager ClusterIP 10.104.106.174 <none> 9093/TCP 2m18s

prometheus kube-prometheus-stack-1602-operator ClusterIP 10.98.165.148 <none> 8080/TCP,443/TCP 2m18s

prometheus kube-prometheus-stack-1602-prometheus NodePort 10.105.3.19 <none> 9090:30090/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-grafana ClusterIP 10.100.178.41 <none> 80:32032/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-kube-state-metrics ClusterIP 10.100.119.13 <none> 8080/TCP 2m18s

prometheus kube-prometheus-stack-1602309230-prometheus-node-exporter ClusterIP 10.100.56.74 <none> 9100/TCP 2m18s

prometheus prometheus-operated ClusterIP None <none> 9090/TCP 2m12s


使用 32032 端口暴露的 Grafana 服务,访问 Grafana 主页。使用 Prometheus chart 中设置的凭证登录到仪表板:prometheus.values 中的 adminPassword 字段。


现在要启动一个用于 GPU 指标的 Grafana 仪表板,请从 Grafana 仪表板(https://grafana.com/grafana/dashboards/12239)导入 NVIDIA 仪表板。


查看 DCGM 指标


现在运行一些 GPU 工作负载,为此,DCGM 社区提供了一个名为 dcgmproftester 的 CUDA 负载生成器,它可以用来生成确定性的 CUDA 工作负载,用于读取和验证 GPU 指标。


要生成一个 Pod,首先必须下载 DCGM 并将制成镜像。以下脚本创建了一个可用于运行 dcgmproftester 的容器。这个容器可以在 NVIDIA DockerHub 仓库中找到。


#!/usr/bin/env bash

set -exo pipefail

mkdir -p /tmp/dcgm-docker

pushd /tmp/dcgm-docker

cat > Dockerfile <<EOF

ARG BASE_DIST

ARG CUDA_VER

FROM nvidia/cuda:\${CUDAVER}-base-\${BASEDIST}

LABEL io.k8s.display-name="NVIDIA dcgmproftester"

ARG DCGM_VERSION

WORKDIR /dcgm

RUN apt-get update && apt-get install -y --no-install-recommends \

libgomp1 \

wget && \

rm -rf /var/lib/apt/lists/* && \

wget --no-check-certificate https://developer.download.nvidia.com/compute/redist/dcgm/\${DCGMVERSION}/DEBS/datacenter-gpu-manager\${DCGMVERSION}amd64.deb && \

dpkg -i datacenter-gpu-manager_*.deb && \

rm -f datacenter-gpu-manager_*.deb

ENTRYPOINT ["/usr/bin/dcgmproftester11"]

EOF

DIR=.

DCGMRELVERSION=2.0.10

BASE_DIST=ubuntu18.04

CUDA_VER=11.0

IMAGENAME=nvidia/samples:dcgmproftester-$DCGMREL_VERSION-cuda$CUDA_VER-$BASE_DIST

docker build --pull \

-t "$IMAGE_NAME" \

--build-arg DCGMVERSION=$DCGMREL_VERSION \

--build-arg BASEDIST=$BASEDIST \

--build-arg CUDAVER=$CUDAVER \

--file Dockerfile \

"$DIR"

popd


在 Kubernetes 集群上部署容器之前,尝试直接使用 Docker 运行它。在这个例子中,通过指定-t 1004 来使用 Tensor Cores 触发 FP16 矩阵乘法,并以-d 45(45 秒)的速度运行测试。您可以通过修改-t 参数来尝试运行其他工作负载。


Skipping CreateDcgmGroups() since DCGM validation is disabled

CUDEVICEATTRIBUTEMAXTHREADSPERMULTIPROCESSOR: 1024

CUDEVICEATTRIBUTEMULTIPROCESSORCOUNT: 40

CUDEVICEATTRIBUTEMAXSHAREDMEMORYPER_MULTIPROCESSOR: 65536

CUDEVICEATTRIBUTECOMPUTECAPABILITY_MAJOR: 7

CUDEVICEATTRIBUTECOMPUTECAPABILITY_MINOR: 5

CUDEVICEATTRIBUTEGLOBALMEMORYBUSWIDTH: 256

CUDEVICEATTRIBUTEMEMORYCLOCK_RATE: 5001000

Max Memory bandwidth: 320064000000 bytes (320.06 GiB)

CudaInit completed successfully.

Skipping WatchFields() since DCGM validation is disabled

TensorEngineActive: generated ???, dcgm 0.000 (27605.2 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28697.6 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28432.8 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28585.4 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28362.9 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28361.6 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28448.9 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28311.0 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28210.8 gflops)

TensorEngineActive: generated ???, dcgm 0.000 (28304.8 gflops)


将其部署到 Kubernetes 集群上,可以通过 Grafana 仪表板观测相应的指标。下面的代码示例:


cat << EOF | kubectl create -f -

apiVersion: v1

kind: Pod

metadata:

name: dcgmproftester

spec:

restartPolicy: OnFailure

containers:

- name: dcgmproftester11

image: nvidia/samples:dcgmproftester-2.0.10-cuda11.0-ubuntu18.04

args: ["--no-dcgm-validation", "-t 1004", "-d 120"]

resources:

limits:

nvidia.com/gpu: 1

securityContext:

capabilities:

add: ["SYS_ADMIN"]

EOF


可以看到 dcgmproftester pod 健康运行,随后指标显示在 Grafana 仪表板上。GPU 利用率(GrActive)已经达到了 98%的利用率峰值,可能还会发现其他有趣的指标,比如功率或 GPU 内存。


$ kubectl get pods -A

NAMESPACE NAME READY STATUS RESTARTS AGE

...

default dcgmproftester 1/1 Running 0 6s

...


验证指标


DCGM 最近增加了一些设备级指标。其中包括细粒度的 GPU 利用率指标,可以监控 SM 占用率和 Tensor Core 利用率。有关更多信息,可以查看 DCGM 用户指南中的 Profiling Metrics。


下图显示了 Prometheus 获取的由 dcgm-exporter 提供的监控指标。


您可以自定义 Grafana 仪表板,以包含 DCGM 的其他指标。在这种情况下,通过编辑 repo 上提供的 Grafana JSON 文件将 Tensor Core 利用率添加到仪表板中,也可以使用 Grafana 的 Web 界面进行编辑。


下面的仪表板包括 Tensor Core 利用率。重新启动 dcgmproftester 容器后,你可以看到 T4 上的 Tensor Core 已经达到了约 87%的利用率。


通过将 GPU 指标作为自定义指标和 Prometheus Adapter,可以使用 Horizontal Pod Autoscaler 根据 GPU 利用率或其他指标来扩展 Pod 数量。


https://mp.weixin.qq.com/s/TulqRFN-7xxqynsyTcYgYw



四、参考资料


1、monitoring


用户头像

DCOS

关注

还未添加个人签名 2020.09.22 加入

公众号:DCOS

评论

发布
暂无评论
DCGM:监控Kubernetes集群的GPU资源