
【集群】云原生批调度实战:Volcano 指标采集与可视化

本系列《云原生批调度实战:Volcano 监控与性能测试》计划分为以下几篇,点击查看其它内容。
- 云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf
- 云原生批调度实战:调度器测试与监控工具 kube-scheduling-perf 实操注意事项说明
- 云原生批调度实战:调度器测试监控结果
- 云原生批调度实战:本地环境测试结果与视频对比分析
- 监控与测试环境解析:测试流程拆解篇
- 监控与测试环境解析:指标采集与可视化篇
- 监控与测试环境解析:自定义镜像性能回归测试
- 监控与测试环境解析:数据收集方法深度解析与Prometheus Histogram误差问题
- 云原生批调度实战:Volcano调度器enqueue功能禁用与性能测试
- 云原生批调度实战:Volcano Pod创建数量不足问题排查与Webhook超时修复
- 云原生批调度实战:Volcano版本修改与性能测试优化
- 云原生批调度实战:Volcano Webhook禁用与性能瓶颈分析
上一篇我们从 Makefile → Kind → 测试代码 串起了一次最小性能测试的全链路。本篇将回答另一个常见问题:
「
TestBatchJob
跑完后,Grafana 面板上的 CREATED / SCHEDULED / RUNNING 曲线是怎么来的?」
下图给出了核心组件与数据流,阅读完本文,希望能够帮你快速实现:
- 理解 审计日志 → Exporter → Prometheus+Grafana → 截图归档 的端到端链路;
- 自定义审计策略 & 面板查询 & 截图归档。
graph LR; subgraph Control-Plane 审计日志 APIServer["Kube-APIServer(开启审计)"] -->|/var/log/kubernetes/kube-apiserver-audit.log| NodeDisk[(control-plane 节点磁盘)] end NodeDisk --> Exporter["Audit-Exporter(Deployment)"] Exporter -->|/metrics| Prometheus((Prometheus)) Prometheus --> Grafana[(Grafana Dashboard)] Grafana --> Script[save-result-images.sh]
1️⃣ 审计日志:audit-policy.yaml 决定记录什么
对应前文流程图中的 Control-Plane 审计日志
部分,在 Kubernetes 中,每个请求在不同执行阶段都会生成审计事件;这些审计事件会根据特定策略被预处理并写入后端。[3]
在此过程中,Kubernetes 审计子系统需要一份 Policy 文件来声明规则(指明需记录的事件范围)。而本项目根目录的 audit-policy.yaml
中就声明了一套规则,重点拦截了衡量调度器吞吐量的关键对象 Pod / Job 的 CRUD:
1 | apiVersion: audit.k8s.io/v1 |
omitStages
指明每个请求可无须记录相关的阶段(stage)。Kubernetes 中已定义的阶段有:RequestReceived
- 此阶段对应审计处理器接收到请求后, 并且在委托给其余处理器之前生成的事件。ResponseStarted
- 在响应消息的头部发送后,响应消息体发送前生成的事件。 只有长时间运行的请求(例如 watch)才会生成这个阶段。ResponseComplete
- 当响应消息体完成并且没有更多数据需要传输的时候。Panic
- 当 panic 发生时生成。
level: RequestResponse
既保留请求头,也包含响应体,方便后续解析出 Result 及耗时。verbs
指明此规则所适用的操作(verb)列表。将 CREATE / PATCH / UPDATE / DELETE 四类操作一次性覆盖;resources
指明此规则所适用的资源类别列表,包含batch
、batch.volcano.sh/jobs
等 CRD,兼顾不同调度器对象。字段group
给出包含资源的 API 组的名称,空字符串代表 core API 组。
level: Metadata
则仅记录请求的元数据(请求的用户、时间戳、资源、动词等等), 但是不记录请求或者响应的消息体。resources
为空列表意味着适用于 API 组中的所有资源类别。
通常情况下,可以使用 --audit-policy-file
标志将包含策略的文件传递给 kube-apiserver
。
▶️ FAQ:策略细节常见疑问
💡 以下内容专门回应在阅读源码时最常见的 3 个疑惑。
① level: Metadata
与 level: RequestResponse
有何区别?为何都要保留?
- 作用域不同:
RequestResponse
规则只匹配我们关心的调度相关资源(pods
/jobs
/jobs.batch.volcano.sh
等),并且显式列举了verbs
=create|patch|update|delete
。它会把 请求头 + 响应体 全量落盘,方便后续 Exporter 解析出 Result (Success/Failure) 与延迟直方图。Metadata
规则的resources: []
表示「兜底规则」——凡是不在前一条命中列表内的 任何 资源,统一只记录元数据(谁、何时、做了什么),不包含请求/响应体。这样既能保留审计合规性,又避免为海量无关对象写大文件。
- 优先级:Kubernetes 会按照 YAML 中的 先后顺序 匹配规则,一旦命中即停止继续匹配。因此本项目先写精确匹配、再写兜底规则,二者不会冲突。
- 同时编写两条规则的目的:平衡指标精度与日志体积。
RequestResponse
为核心对象提供高粒度延迟直方图与成功率计算;Metadata
兜底满足审计留痕合规,又避免为成百上千个与调度无关的对象写入冗余响应体,从而显著降低磁盘占用与解析成本。
② 为什么 omitStages
要排除 RequestReceived
和 ResponseStarted
?最终会记录哪些 Stage?
- 背景:一次 API 请求最多可生成四个 Stage 事件(
RequestReceived
➡ResponseStarted
➡ResponseComplete
➡Panic
)。其中RequestReceived
与ResponseStarted
体量大且价值有限:RequestReceived
只表明「请求到达了 APIServer」,但拿不到任何时长信息;ResponseStarted
仅对 长连接 watch 场景才会生成,对我们的批量 CRUD 测试用例几乎恒为空;
- 因此在策略里把这两阶段排除,既减少日志体积,也避免 Exporter 做无意义解析。
- 与规则无冲突:
omitStages
作用于 全局,告诉 APIServer 在生成审计事件时忽略指定阶段;后面的rules
只决定「对哪些请求生成事件以及生成到什么 level」。二者工作维度不同,不会互相覆盖。 - 在本项目的批量 Job / Pod 测试中,最终实际落盘的 Stage 主要是:
ResponseComplete
— 绝大多数正常请求;Panic
— 只有当 APIServer panic 才会出现(理论上极少)。
- 如何拿到「创建 / 调度 / 运行」等关键时间点?Exporter 仅需关注
stage="ResponseComplete"
的事件:- 创建时间:匹配
verb=create
且resource=pods|jobs
的完成时间戳; - 调度时间:匹配
resource=pods/binding
的完成时间戳(kube-scheduler 向 APIServer 发起 bind 请求); - 运行时间:匹配
resource=pods/status
、verb=update
且status.phase=Running
的完成时间戳;
Exporter 在内存中以同名 Pod UID 关联多条事件,计算时间差即可,无需RequestReceived/Started
阶段即可还原完整链路。 Panic
含义:当 APIServer 在处理请求过程中发生运行时崩溃并捕获到 panic 时才会生成,用于事后问题排查,正常测试流程极罕见。
- 创建时间:匹配
③ audit-policy.yaml
是如何交给 Kind 中的 kube-apiserver 的?
- 每个调度器对应的 Kind 集群(位于
clusters/<scheduler>/kind.yaml
)都做了如下三种操作:extraMounts
把根目录下的audit-policy.yaml
挂载到控制平面节点的/etc/kubernetes/policies/audit-policy.yaml
;apiServer.extraVolumes
定义名为audit-policies
的 HostPath 卷,并将其挂载到同一路径,确保文件在 Pod 内可读;apiServer.extraArgs
增加这样 APIServer 一启动就按照我们自定义的策略把审计事件写入宿主机1
2
3audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml
audit-log-path: /var/log/kubernetes/kube-apiserver-audit.<scheduler>.log
audit-log-maxsize: "10240"/var/log/kubernetes/
,后续再被 Exporter Tail。
2️⃣ Exporter:kube-apiserver-audit-exporter 把日志变成指标
前文 Policy 决定了「记录什么」,Exporter 则决定了「怎么提炼指标」。
部署清单位于:base/kube-apiserver-audit-exporter/kube-apiserver-audit-exporter/deployment.yaml
1 | ... |
关键参数说明:
字段 | 含义 | 示例值 |
---|---|---|
--audit-log-path | 审计日志所在宿主机路径 | /var/log/kubernetes/kube-apiserver-audit.log |
image | 可独立升级的 Exporter 镜像 | …/kube-apiserver-audit-exporter:v0.0.25 |
VolumeMount | 将宿主机日志目录挂载进 Pod | mountPath: /var/log/kubernetes |
📌 组件何时被部署?—— Makefile 触发点
在本仓库最常用的入口 make default
会连续执行多轮 serial-test。理解一次 serial-test 的执行序列即可明白监控组件的真实部署时机:
步骤 | 触发目标 | 关键动作 |
---|---|---|
1 | prepare-<scheduler> | make up-<scheduler> 创建 单个调度器集群,但此时 没有 监控栈 |
2 | start-<scheduler> | 运行性能测试 (TestBatchJob 等) 并 写入 audit-log |
3 | end-<scheduler> | make down-<scheduler> 销毁该集群,日志仍留在宿主机 /var/log/kubernetes/ |
⬇(循环) | (依次换下一个调度器) | … |
4 | prepare-overview | make up-overview 创建 独立的 overview 集群 |
5 | start-overview | clusters/overview/Makefile:start-export 部署 Exporter + PromStack,并把 所有 kube-apiserver-audit.*.log HostPath 挂载到 Pod |
6 | save-result | 睡 $(RESULT_RECENT_DURATION_SECONDS) 秒等待指标就绪→执行 hack/save-result-images.sh 截图 |
7 | end-overview | 销毁 overview 集群,聚合循环结束 |
也就是说:Exporter 和 Prometheus 直到 所有 调度器测试跑完后才被一次性拉起,随后一次性重放/解析先前留下的多份 audit-log。
Exporter 会 tail 文件并实时解析,输出如下两类 Prometheus 指标(简化):
1 | # HELP kube_audit_event_total Total number of audit events |
其中 status="Success"
字段让我们能够在 Grafana 中分别绘制 CREATED / SCHEDULED / RUNNING 三条曲线。
🔍 内部实现:Exporter 如何 tail + 解析?
该部分比较复杂,涉及另一个项目。简单理解后,将该部分分为以下三步:
- 跟踪文件:Exporter 使用 Go 语言实现,入口位于 <base/kube-apiserver-audit-exporter>,源仓库位于https://github.com/wzshiming/kube-apiserver-audit-exporter。其核心依赖
tail
(或 OSinotify
)持续读取宿主机/var/log/kubernetes/…audit.log
; - JSON 解析:每行审计日志都是合法 JSON,Exporter 利用
encoding/json
反序列化为auditinternal.Event
结构体,随后按verb / resource / stage / status
维度进行map
聚合; - 指标暴露:聚合结果通过
prometheus/client_golang
转为counter
与histogram
两类kube_audit_*
指标;
若要增加更多指标(如自定义 label、增加 summary
等):
- 定位代码:仓库中路径
exporter/metrics.go
下可见:1
2
3
4
5
6
7
8
9var (
apiRequests = prometheus.NewCounterVec(...)
podSchedulingLatency = prometheus.NewHistogramVec(...)
)
func (p *Exporter) updateMetrics(clusterLabel string, event auditv1.Event) {
// ... 根据 verb/resource 填充指标 ...
apiRequests.WithLabelValues(...).Inc()
podSchedulingLatency.WithLabelValues(...).Observe(latency)
} - 扩展步骤:
- 复制:复制上述变量块,替换
Name
为kube_audit_pod_latency_seconds
(示例),同时调整Buckets
、Help
; - 修改:在
updateMetrics
中增加条件:1
2
3if evt.ObjectRef.Resource == "pods" {
podLatency.WithLabelValues(evt.Verb, evt.Stage).Observe(cost)
} - 注册:确保在
init()
或NewCollector()
中registry.MustRegister(podLatency)
; - 换镜像:
docker build -t <registry>/audit-exporter:dev . && docker push …
,然后在base/kube-apiserver-audit-exporter/.../deployment.yaml
更新image
并kubectl apply -k
。
- 复制:复制上述变量块,替换
完整示例可参考项目
exporter/metrics.go
源码。
3️⃣ Prometheus 抓取:Kustomize 一条龙
base/kube-prometheus-stack
目录通过 Kustomize 把 Exporter、Prometheus Operator 与多个 ServiceMonitor 组合在一起,无需额外手动配置抓取目标。
- Prometheus 会自动发现 Exporter 的
metrics
端口; - Grafana 面板 JSON
audit-exporter.json
已预置在同目录,标签切片(Scheduler 类型、Namespace、Verb)均可动态选择。
若要自定义阈值或颜色,只需
kubectl edit cm grafana-dashboards
后刷新浏览器即可即时生效。
其中用到了 Kustomize 工具,较为复杂,在此仅简单介绍。
✨ Kustomize 简介
Kustomize 是 Kubernetes 官方提供的 原生资源定制工具,核心理念是“声明式 Patch 与组合”。相比 helm
,它无需模板语言,也不引入额外 CRD:
- 基础资源(Base):每个目录下的
kustomization.yaml
列出若干resources
,可按文件或目录引用; - 叠加层(Overlay):上层可以通过
patches
,images
,replicas
等声明式字段覆写或追加配置; - 生成器:
configMapGenerator
,secretGenerator
快速为应用生成引用。
在本项目中:
1 | base/kube-prometheus-stack/ # 监控基础组件 Base |
clusters/overview/Makefile
里的 kubectl kustomize <dir> | hack/local-registry-with-load-images.sh
两步做了:
kubectl kustomize
→ 渲染:把以上 Base + Patch 解析成纯 YAML 清单;local-registry-with-load-images.sh
→ 镜像处理:重写鏡像地址到本地 Kind Registry 并预先docker pull
;kubectl create -k
→ 应用:批量创建 Exporter、Prometheus Operator、Alertmanager、ServiceMonitor 等所有资源,一次到位。
因此我们才能“一键 make”拿到完整的监控栈。
4️⃣ 截图归档:save-result-images.sh 归档面板截图
运行 make save-result
后,hack/save-result-images.sh
会在本地循环调用 Grafana render
API,按面板 ID 生成 output/panel-*.png
:
1 | RECENT_DURATION=${RECENT_DURATION:-5min} |
RECENT_DURATION
环境变量控制 截图时间窗口(默认 5 分钟);FROM/TO
时间戳使用 ISO-8601 UTC 毫秒格式,避免时区混淆;panelId
与面板 JSON 中的id
一一对应,可根据需要扩展。
示例输出目录结构:
1 | output/ |
⏱️ 为什么三套调度器曲线对齐到同一起始时间?
在 serial-test 模式下,Exporter 直到第 4 步才启动,它会从头开始顺序扫描所有 audit-log。Prometheus 采集时将「第一次 scrape 该指标的时刻」视为样本时间戳,而不是事件发生时间。因此:
- 当 Exporter 第一次读取 三份 日志文件时(约 T0),所有指标都会带上 T0 的统一时间戳;
- 读取完第一份文件后继续第二、第三份——对于 Prometheus 来说也仍是 “T0~T0+Δ” 的时间窗口;
结果就是:Grafana 图上三条曲线似乎“同一时刻起跑”。它们并非并发,而是 日志回放造成的时间折叠 —— 先跑的调度器其实更早完成,但其事件被延后才被采集。
如果希望曲线按真实事件时间展开,可以:
- 修改 Exporter,让它把
evt.StageTimestamp
用作 Prometheushistogram
的ObserveWithTimestamp
; - 或者在测试流程中提前启动 overview 集群,使 Exporter 按实时模式持续采集。
如果想亲眼查看 audit-log,有两种方法:
- 直接读宿主机文件(Kind 节点实际上是 Docker 容器):三个文件名称分别为
1
2docker exec kueue-control-plane \
cat /var/log/kubernetes/kube-apiserver-audit.kueue.log | headkube-apiserver-audit.{kueue|volcano|yunikorn}.log
。 - 查看 Exporter 容器日志(overview 集群):启动时它会打印
1
kubectl -n monitoring logs deploy/kube-apiserver-audit-exporter | head
starting from offset=0 file=...
,表明正在从头重放。
日志文件很大,可用
grep '"verb":"create"'
等命令过滤感兴趣事件,字段含义详见 Kubernetes 官方审计文档。
5️⃣ 本地验证:三步走
- 最小规模跑一次
1 | make prepare-volcano start-volcano end-volcano \ |
- 打开 Grafana
浏览器访问 http://127.0.0.1:8080/grafana,Dashboards → perf
,即可看到实时曲线。
- 查看截图
测试结束后,output/
将出现自动截好的图片,确认时间轴与曲线一致。
- 希望这篇博客对你有帮助!如果你有任何问题或需要进一步的帮助,请随时提问。
- 如果你喜欢这篇文章,欢迎动动小手给我一个follow或star。
🗺参考文献
- 标题: 【集群】云原生批调度实战:Volcano 指标采集与可视化
- 作者: Fre5h1nd
- 创建于 : 2025-08-07 22:59:25
- 更新于 : 2025-08-21 19:57:58
- 链接: https://freshwlnd.github.io/2025/08/07/k8s/k8s-scheduler-performance-volcano-metrics/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。