深色模式
L7 Prometheus 监控
目标
- 在 K8s 集群上部署 Prometheus 和 Grafana;
- 用 PromQL 查询节点、Pod、GPU 的资源使用情况;
- 用 Python 从 Prometheus 拉取指标,作为调度算法的输入或实验数据。
背景
Prometheus 定期从各个“导出器”(exporter)抓取指标,存成时间序列,用 PromQL 查询。常用的数据来源:
| 导出器 | 提供的指标 |
|---|---|
| node-exporter | 节点的 CPU、内存、磁盘、网络 |
| cAdvisor(集成在 kubelet 中) | 每个容器的 CPU、内存使用 |
| kube-state-metrics | K8s 对象的状态:Pod 创建时间、所处阶段、副本数等 |
| DCGM exporter | GPU 利用率、显存、功耗、温度(GPU Operator 会自动安装) |
| 应用自己的指标 | 例如 vLLM 在 /metrics 暴露排队请求数、KV Cache 使用率、TTFT 分布 |
第 1 步:部署
用 Helm 安装 kube-prometheus-stack,一次装好 Prometheus、Grafana、node-exporter、kube-state-metrics:
bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
kubectl get pods -n monitoring
kubectl port-forward -n monitoring svc/monitoring-kube-prometheus-prometheus 9090:9090浏览器打开 http://localhost:9090 进入 Prometheus 查询页面。Grafana 同样可以用 kubectl port-forward 访问,初始密码按 chart 文档说明获取。
第 2 步:常用 PromQL
节点
text
# CPU 使用率(0~1),按节点
1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))
# 正在使用的 CPU 核数
sum by (instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m]))
# CPU 核数
count by (instance) (node_cpu_seconds_total{mode="idle"})
# 内存使用率(%)
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# 根分区使用率(%)
100 - node_filesystem_avail_bytes{mountpoint="/", fstype=~"ext4|xfs"} * 100
/ node_filesystem_size_bytes{mountpoint="/", fstype=~"ext4|xfs"}
# 网卡接收速率(KB/s),排除回环和容器虚拟网卡
rate(node_network_receive_bytes_total{device!~"lo|veth.*|cni.*|flannel.*"}[5m]) / 1024
# 最近 5 分钟接收的总字节数
increase(node_network_receive_bytes_total{device!~"lo|veth.*"}[5m])
# 每个节点上的 Pod 数
count by (node) (kube_pod_info)内存怎么算
MemFree 是完全没用到的内存,Linux 会把空闲内存拿去做页面缓存,所以 MemFree 通常很小,用它算使用率会严重偏高。MemAvailable 是系统估计的可用内存(包括可回收的缓存),算使用率应该用它。
容器与 Pod
text
# 每个 Pod 的 CPU 使用(核)
sum by (namespace, pod) (rate(container_cpu_usage_seconds_total{container!="", container!="POD"}[5m]))
# 每个 Pod 的内存使用(字节),working_set 是 K8s 判断内存超限的依据
sum by (namespace, pod) (container_memory_working_set_bytes{container!="", container!="POD"})GPU(需要 DCGM exporter)
text
DCGM_FI_DEV_GPU_UTIL # GPU 利用率(%)
DCGM_FI_DEV_FB_USED # 已用显存(MiB)
DCGM_FI_DEV_POWER_USAGE # 功耗(W)Pod 生命周期(kube-state-metrics)
text
kube_pod_created{namespace="default", pod="pod-name"} # 创建时间
kube_pod_status_scheduled_time{namespace="default", pod="pod-name"} # 调度完成的时间
kube_pod_start_time{namespace="default", pod="pod-name"} # kubelet 接收 Pod 的时间(拉镜像之前)
kube_pod_container_state_started{namespace="default", pod="pod-name"} # 容器真正开始运行的时间(每个容器一条)
kube_pod_status_ready_time{namespace="default", pod="pod-name"} # Pod 就绪的时间
kube_pod_completion_time{namespace="default", pod="pod-name"} # 完成时间
kube_pod_status_phase{namespace="default", pod="pod-name"} # 当前阶段容器调度研究最常用的指标是 Pod 的启动耗时,要注意每个时间点代表什么:
| 想测什么 | 怎么算 |
|---|---|
| 调度耗时 | kube_pod_status_scheduled_time − kube_pod_created |
| 启动耗时(含调度和拉取镜像) | kube_pod_container_state_started − kube_pod_created;以服务可用为准时,用 kube_pod_status_ready_time − kube_pod_created |
| 拉取镜像的耗时 | Pod 事件中 Pulling 到 Pulled 的时间(kubectl get events --field-selector involvedObject.name=pod-name),Pulled 事件的消息里也直接写了耗时 |
不要用 kube_pod_start_time 算启动耗时
kube_pod_start_time 取自 Pod 的 status.startTime,Kubernetes 对它的定义是 kubelet 确认这个 Pod 的时刻,在拉取镜像之前。用它减去创建时间,只得到调度和 kubelet 接收的时间,不包括镜像下载。层感知调度研究的恰恰是镜像下载时间,用错这个指标,调度方法的收益会被严重低估,甚至测不出来。
rate 与 irate
rate(x[5m]):5 分钟窗口内的平均每秒增长率,曲线平滑,适合看趋势和告警。irate(x[5m]):只用窗口内最后两个采样点计算的瞬时增长率,反应快但抖动大,适合观察短时突变。
rate 和 irate 只能用于持续递增的计数器(名字通常以 _total 结尾)。
第 3 步:用 Python 采集指标
做实验时,经常需要把指标存下来画图,或者实时读给调度算法:
python
import time
import requests
PROM = "http://localhost:9090"
def query(promql):
"""查询当前值"""
r = requests.get(f"{PROM}/api/v1/query", params={"query": promql}, timeout=10)
r.raise_for_status()
return [(item["metric"], float(item["value"][1])) for item in r.json()["data"]["result"]]
def query_range(promql, minutes=30, step="15s"):
"""查询过去一段时间的序列"""
end = time.time()
r = requests.get(
f"{PROM}/api/v1/query_range",
params={"query": promql, "start": end - minutes * 60, "end": end, "step": step},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]["result"]
cpu = query('1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[1m]))')
for metric, value in cpu:
print(f"{metric['instance']}: CPU {value:.1%}")命令行也可以直接查:
bash
curl -G http://localhost:9090/api/v1/query --data-urlencode 'query=up'
curl -G http://localhost:9090/api/v1/targets/metadata --data-urlencode 'metric=container_cpu_cfs_periods_total'第 4 步:监控推理服务
在 Prometheus 中抓取 vLLM 的 /metrics(kube-prometheus-stack 用 ServiceMonitor 配置),在 Grafana 里画出请求排队数、KV Cache 使用率、TTFT 分布,同时观察 GPU 利用率。用 L1 的压测工具加压,看这些指标怎样随负载变化。