Skip to content

L7 Prometheus 监控 ​

目标 ​

  • 在 K8s 集群上部署 Prometheus 和 Grafana;
  • 用 PromQL 查询节点、Pod、GPU 的资源使用情况;
  • 用 Python 从 Prometheus 拉取指标,作为调度算法的输入或实验数据。

背景 ​

Prometheus 定期从各个“导出器”(exporter)抓取指标,存成时间序列,用 PromQL 查询。常用的数据来源:

导出器提供的指标
node-exporter节点的 CPU、内存、磁盘、网络
cAdvisor(集成在 kubelet 中)每个容器的 CPU、内存使用
kube-state-metricsK8s 对象的状态:Pod 创建时间、所处阶段、副本数等
DCGM exporterGPU 利用率、显存、功耗、温度(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 的压测工具加压,看这些指标怎样随负载变化。

作者:崔涵帅、姚智、王振胜、唐志清 · 边缘智能系统实验室