首先我们有一个 Kubernetes 集群。它可以识别节点上的 GPU 并使用它们。甚至 GPU 指标也是通过 dcgm-exporter 和 prometheus 堆栈公开的。接下来是什么?我们知道我们可以Horizontal Pod Autoscaler
根据 CPU 和内存使用情况在 Kubernetes 集群上自动扩展我们的程序。我们可以对 GPU 指标做同样的事情吗?答案是肯定的,Prometheus Adapter
可以解决问题。
本文假设您有一个Kubernetes 集群可以使用 GPU,并且您正在使用 dcgm 和 prometheus 堆栈来获取 GPU 指标。如果没有,您可以按照我之前关于如何到达那里的文章进行操作。我的 Kubernetes 版本是1.20.2
,而且我有Tesla V100
GPU。这些已经在centos7运行
。
我们让 Prometheus 充当指标提供者——我们将其配置为更早地为我们提供 GPU 指标。Prometheus Adapter 为我们提供了自定义指标 API,提取由外部指标提供者(在本例中为 Prometheus)提供的指标。然后,Horizontal Pod Autoscalers 可以使用这些自定义指标来扩展工作负载。

在我们安装 Prometheus Adapter 之前,让我们检查连接到 Prometheus
# kubectl get svc -n prometheus
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 1d
kube-prometheus-stack-1614-alertmanager ClusterIP 10.97.150.200 <none> 9093/TCP 1d
kube-prometheus-stack-1614-operator ClusterIP 10.107.120.24 <none> 443/TCP 1d
kube-prometheus-stack-1614-prometheus NodePort 10.106.243.247 <none> 9090:30090/TCP 1d
kube-prometheus-stack-1614100923-grafana NodePort 10.100.30.205 <none> 80:32197/TCP 1d
kube-prometheus-stack-1614100923-kube-state-metrics ClusterIP 10.111.133.205 <none> 8080/TCP 1d
kube-prometheus-stack-1614100923-prometheus-node-exporter ClusterIP 10.110.160.60 <none> 9100/TCP 1d
prometheus-operated ClusterIP None <none> 9090/TCP 1d
请注意,我们的服务在 port 可用的名称空间中prometheus-operated
运行。因此,适配器使用的 prometheus url 将是http://prometheus-operated.prometheus.svc,端口为 9090。prometheus
9090
一、安装 Prometheus adapter
使用 helm 安装 Prometheus Adapter 非常容易,但是需要一些配置来设置 Prometheus 指标和 Kubernetes 资源发现以及通过自定义指标 API 呈现这些指标。从技术上讲,这也可以通过https://github.com/kubernetes-sigs/prometheus-adapter 中记录的 helm install 命令来完成。但是,这对我不起作用。我最终拉了helm chart,用自定义配置修改了它的值,然后安装。以下是步骤。
将helm chart拉到一个临时文件夹中。
helm pull prometheus-community/prometheus-adapter
这将为您提供 zip 文件中最新版本的adapter helm charts — prometheus-adapter-2.15.2.tgz
. 解压缩此文件并转到prometheus-adapter
文件夹。
tar -xvzf ../prometheus-adapter-2.15.2.tgz
cd prometheus-adapter/
在您喜欢的编辑器中编辑values.yaml文件。找到 prometheus 的 url 并将其从默认更改为我们拥有的。它应该是这样的。
现在找到rules
并复制以下配置的部分。要更深入地了解这些规则,您可以查看https://github.com/kubernetes-sigs/prometheus-adapter/blob/master/docs/config-walkthrough.md。
# Url to access prometheus
prometheus:
# Value is templated
url: http://prometheus-operated.prometheus.svc
port: 9090
path: ""
这里我们使用DCGM
导出的 GPU 指标(有关详细信息,请参阅我关于 GPU 指标的另一篇文章)。在此规则配置中,我们要求提供新的指标系列 – ‘ _current
‘ 后缀将为我们提供当前值,而 ‘ _over_time
‘ 前缀为我们提供超过三分钟的平均 pod 指标。您当然可以使用此配置并想出适合您目的的东西。请注意,我们注释掉了default
规则。
rules:
#default: true
custom:
- seriesQuery: '{UUID!=""}'
resources:
overrides:
node: {resource: "node"}
exported_pod: {resource: "pod"}
exported_namespace: {resource: "namespace"}
name:
matches: ^DCGM_FI_(.*)$
as: "${1}_over_time"
metricsQuery: ceil(avg_over_time(<<.Series>>{<<.LabelMatchers>>}[3m]))
- seriesQuery: '{UUID!=""}'
resources:
overrides:
node: {resource: "node"}
exported_pod: {resource: "pod"}
exported_namespace: {resource: "namespace"}
name:
matches: ^DCGM_FI_(.*)$
as: "${1}_current"
metricsQuery: <<.Series>>{<<.LabelMatchers>>}
existing:
external: []
保存此文件,返回一个文件夹级别并安装 adapter。
cd ..
helm install prometheus-adapter prometheus-adapter/ -n kube-system
查看adapter pod.
$ kubectl get pods -n kube-system|grep prometheus
prometheus-adapter-fd8ccb5b7-5lhsm 1/1 Running 0 2m
现在是实际运行一个强调 GPU 的程序并查看它是否自动缩放的时候了。
二、扩展 HPA
创建一个 yaml 文件,例如,gpu-ray-autoscaler.yaml
包含以下内容。
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: hpa-gpu
namespace: rayexperiment
spec:
scaleTargetRef:
apiVersion: apps/v1beta1
kind: Deployment
name: ray-worker
minReplicas: 1
maxReplicas: 4
metrics:
- type: Pods
pods:
# use the metric that you used above: pods/http_requests
metricName: DEV_GPU_UTIL_current
targetAverageValue: 40
我们的目标ray-worker
是稍后要创建的 Deployment 。记住我们创建的指标——我们正在使用其中之一DEV_GPU_UTIL_current
。如果 GPU 使用率超过 40%,则会创建一个新的 ray-worker pod,最多有四个副本。如果需要,我们还可以添加其他 GPU 指标。现在让我们部署这个自动缩放器。请注意,我们正在使用rayexperiment
命名空间。
kubectl create -f gpu-ray-autoscaler.yaml -n rayexperiment
三、GPU 工作负载
当然,我们可以创建一个实际的 ML/深度学习工作负载来练习 GPU 和自动缩放器,但现在让我们坚持一个简单的。让我们创建一个 ray python 程序,用模拟负载对 GPU 施加压力。然后我们将用它创建一个 docker 容器并在一个 ray 集群中使用它。
这是一个强调 GPU 的简单 Ray 程序。一个简单的函数,模拟五分钟的高 GPU 负载,循ray.wait
分布式执行上添加了一个。
import torch
@ray.remote(num_gpus=1)
def load_gpu(x):
print("starting iteration ", x)
t_end = time.time() + 60 * 5
my_tensor = torch.linspace(0, 10, 1100**2).cuda()
while time.time() < t_end:
my_tensor*my_tensor
return x
if __name__ == '__main__':
ray.init(address='auto', _redis_password='5241590000000000')
results = []
for i in range(0, 10):
# call the ray remote method, and save the returned identifiers in a list
results.append(load_gpu.remote(i))
while len(results):
# now wait on the results
done, results = ray.wait(results)
r = ray.get(done[0])
print('done with result', r)
这是requirements.txt
一个单行文件,只有 – torch==1.6.0
。我们将使用一个包含所有其他必要包的 ray 基础镜像。
我们的Dockerfile
, ray 的 gpu 基础映像将包含必要的cuda
工具包。
FROM rayproject/ray:nightly-gpu
# Set up the dependencies
WORKDIR /app
COPY requirements.txt .
RUN pip install -U pip && \
pip install -r requirements.txt
COPY . ./
我们的.dockerignore
文件的完整性。注意ray集群部署yaml文件,我们接下来要创建。
Dockerfile
.git
.gitignore
ray-cluster-gpuload.yaml
现在让我们构建该镜像。
docker build -t ray-gpu-load .
将映像推送到 Kubernetes 集群中当前使用的映像存储库。
docker tag ray-gpu-load:latest <your-docker-repo/your-namespace>/ray-gpu-load:latest
docker push <your-docker-repo/your-namespace>/ray-gpu-load:latest
四、部署 Ray 集群
我们有一个 ray 集群部
https://github.com/ray-project/ray/blob/master/doc/kubernetes/ray-cluster.yaml
我们需要对该文件进行一些更改。我们将需要使用我们刚刚构建的镜像并请求 gpu 资源。让我们ray-cluster-gpuload.yaml
在这些更改之后保存这个文件。
对于ray-head部分:
containers:
- name: ray-head
image: <your-docker-repo/your-namespace>/ray-gpu-load:latest
以及 ray-worker 部分:
containers:
- name: ray-worker
image: <your-docker-repo/your-namespace>/ray-gpu-load:latest
在 ray-worker 部分请求 gpu 资源。
resources:
requests:
cpu: 1000m
memory: 512Mi
limits:
cpu: 1000
nvidia.com/gpu: 1
注意:您可能需要添加,imagePullSecrets
具体取决于您如何配置集群以从存储库中提取 docker 镜像。
还要将此 yaml 文件中的命名空间更改rayexperiment
为与我们迄今为止所做的相匹配。
apiVersion: v1
kind: Service
metadata:
namespace: rayexperiment
让我们用我们的镜像部署到 ray 集群中。
kubectl create -f ray-cluster-gpuload.yaml
查看并查询 rayexperiment 的pod
$ kubectl get pods -n rayexperiment
NAME READY STATUS RESTARTS AGE
ray-head-74c449cb64-l9gfc 1/1 Running 0 15s
ray-worker-85789d96d4-zvb6t 1/1 Running 0 15s
五、Load rayexperiment
进入 ray worker pod
$ kubectl -n rayexperiment exec --stdin --tty ray-worker-85789d96d4-zvb6t -- /bin/bash
(base) ray@ray-worker-85789d96d4-zvb6t:/app$
运行我们的程序
(base) ray@ray-worker-85789d96d4-zvb6t:/app$ python3 ray_gpu_load.py
2021-07-30 07:38:45,374 INFO worker.py:655 -- Connecting to existing Ray cluster at address: 10.99.14.184:6379
(pid=91) starting iteration 0
(pid=104, ip=10.244.0.201) starting iteration 1
六、查看自动伸缩
很快您就可以看到我们的自动缩放器在运行中,由于 GPU 使用率高而导致 pod 扩展。
# kubectl get pods -n rayexperiment
NAME READY STATUS RESTARTS AGE
ray-head-74c449cb64-l9gfc 1/1 Running 0 5m12s
ray-worker-85789d96d4-6bjwc 1/1 Running 0 113s
ray-worker-85789d96d4-7tn68 1/1 Running 0 83s
ray-worker-85789d96d4-t8n77 1/1 Running 0 113s
ray-worker-85789d96d4-zvb6t 1/1 Running 0 5m12s
如果您已配置Grafana
,则可以使用标准 NVIDIA DCGM 仪表板可视化指标。(你可以参考我之前关于如何配置它的文章,如果你还没有的话)。

几分钟后,程序完成,您可以看到 pod 数量随着 GPU 的使用而下降。
而已!我们现在可以根据 GPU 利用率指标来扩展我们的光线程序。您可以用实际的深度学习工作负载替换这个玩
参考文献
https://medium.com/@rajupavuluri/how-to-autoscale-your-ray-programs-based-on-gpu-load-on-kubernetes-clusters-9ba75d46b71b
有个疑问, 这里的hpa配置中,数据来源于DCGM的pod,目标pod是另一种deployment; 这种能用Pod类型的metrics吗?
据了解,pods类型应当描述的是当前扩容目标中每个pod的指标
—
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: hpa-gpu
namespace: rayexperiment
spec:
scaleTargetRef:
apiVersion: apps/v1beta1
kind: Deployment
name: ray-worker
minReplicas: 1
maxReplicas: 4
metrics:
– type: Pods
pods:
# use the metric that you used above: pods/http_requests
metricName: DEV_GPU_UTIL_current
targetAverageValue: 40