Kubernetes集群监控,聊起来大家第一反应都是“装个Prometheus,搞个Grafana大盘”。但真正在企业里把“Kubernetes Cluster Overview (Complete Edition) ”这套企业级集群监控仪表板做成“能看、能查、能告警、能复盘”的完整方案,远比想象中琐碎。去年我主导搭建了这套监控体系,核心诉求就三个:快速判断集群是否健康、故障时能顺着仪表板找到根因、为容量规划提供数据支撑。这篇文章把我做的整体设计、指标规划、PromQL实战、告警配置、常见坑全部整理出来,适合正在负责集群监控的同学把文章当成一份可落地的对照清单来使用。
1. 监控仪表板的整体设计思路
1.1 动手前先回答三个问题
很多人拿到Kubernetes集群第一件事就是去grafana.com翻Dashboard模板,结果装完发现要么指标为空,要么数据断断续续,要么就是一屏花花绿绿的折线图却完全不知道看哪里。原因很简单:模板只是展示层,真正的难点在采集链路和指标设计。我建议动手前先回答三个问题:这台仪表板给谁看?看什么?看完之后要做什么决策?
如果是给SRE看,那直接上技术指标,节点CPU、内存、Pod重启次数都无所谓难不难看;如果是给技术负责人看,就需要把集群健康和业务表现绑定在一起,比如“支付服务P99延迟”和“所在Pod的CPU饱和度”放在同一个视图里,否则对方看到的只是一群没有业务含义的数字,下次就不会再打开这个页面了。在企业里,仪表板最怕的不是数据不够,而是数据太多。我见过不少团队把几十个exporter全塞进Grafana,一屏同时展示几十个指标面板,最后变成“监控墙”,故障时反而找不到重点。好的做法是分层:集群层、节点层、工作负载层、业务层,每层都有明确负责的指标和入口。
1.2 为什么选Prometheus + Grafana这套组合
选型那段时间我做过对比。Zabbix在传统IDC监控里非常成熟,但Kubernetes的Pod生命周期太动态,指标采集高度依赖服务发现,Zabbix的静态主机模板模式用起来非常别扭,每次扩容还要手动去关联主机组,根本跟不上Pod的弹性伸缩。商业APM方案体验好、告警能力强,但按节点和指标数量收费,上一套生产集群的价格很快就让人肉疼。Prometheus + Grafana + Kubernetes天然属于同一生态,Kubernetes自带/metrics接口和服务发现机制,Prometheus的kubernetes_sd_config可以直接识别Pod、Service、Endpoint,这是其他方案很难替代的。
另外,社区积累是我最看重的部分。node_exporter、kube-state-metrics、cadvisor这些采集器,围绕K8s沉淀了非常完整的指标字典。大家常说的USE方法论(利用率、饱和度、错误)和RED方法(速率、错误、持续时间),社区已经把对应的指标全部梳理过一遍。我们在这套仪表板里做的事情,本质上是把这些指标按场景重新组装,而不是从零发明一套监控理论。基于这些原因,Prometheus + Grafana成为企业级Kubernetes集群监控的事实标准,完全是有道理的。
1.3 监控链路的四层拆解
采集层、存储层、展示层、告警层,这是我对监控链路的固定拆法。采集层负责从Kubelet、节点、各类Exporter和业务应用拉取或接收指标,核心是确认“所有目标都在被采集”;存储层用Prometheus本地时序库,默认保留15天,更长的周期需求后来才引入Thanos做联邦和对象存储;展示层由Grafana承载,按主题分组、模板变量做过滤;告警层由Alertmanager统一收敛和发送,避免单个Panel出现异常时每个面板都给运维发一遍消息。
这里想多说一句,不要一开始就追求全链路。很多团队一上来就上Thanos、Loki、Tempo,结果监控系统的运维成本比Kubernetes集群本身还高。我们的启动阶段只用社区标准组件,先把一套最小闭环跑起来,等监控数据真正被业务依赖了,再逐步扩展长周期存储和日志链路,这样每一步都有明确收益,而不是为了“技术先进”给自己挖坑。
2. 搭建一套最小可用采集栈
2.1 手工拼装还是直接上kube-prometheus-stack
手工拼装Prometheus、node-exporter、kube-state-metrics、Grafana这套组合,好处是每个组件都能按自己的习惯定制,适合已经有Prometheus成熟运维经验的团队。但坏处是坑非常多:需要自己写RBAC权限,访问APIServer的ServiceAccount要配置正确;需要理解ServiceMonitor和PodMonitor这些CRD的selector机制;还要单独处理持久化存储、节点选择、资源配额。整套搞下来没有一整天搞不定,而且后续升级维护非常痛苦。
kube-prometheus-stack是一个完整的打包方案,用Helm Chart一键部署,默认就带Prometheus、Grafana、Alertmanager、node-exporter、kube-state-metrics和一堆ServiceMonitor,采集目标、告警规则、仪表板模板全部预制好。对绝大多数企业场景,我建议直接用它起步。有人担心它“重”、默认规则太多,实际上它确实会占用一定资源,但换来的是统一的升级路径和持续维护的社区保障,性价比非常高。
如果问我什么时候才适合手工拼装,我的判断标准是这样:集群规模很小、只有一两个测试环境,或者公司已经有统一的监控平台、只需要K8s把数据吐出来,这时候轻量部署几个exporter就够了。生产环境建议直接进入kube-prometheus-stack,省下的是后面大量排障时间。
2.2 node-exporter、kube-state-metrics、metrics-server到底谁管什么
好多新手会把metrics-server、node-exporter、kube-state-metrics三者的功能搞混,这里单独说清楚。metrics-server是给kubectl top和HPA提供临时指标用的,只把数据保留在内存里,不服务Prometheus做长期监控。node-exporter暴露节点操作系统指标,比如节点内存总量、CPU各mode时间、磁盘读写、网络收发,端口默认9100。kube-state-metrics负责把Kubernetes对象的状态转换成指标,比如Pod重启次数、Deployment副本数、Node Ready条件,它本质上是K8s API对象到监控指标的翻译器。
容器级指标则来自Kubelet内置的cadvisor,比如container_cpu_usage_seconds_total、container_memory_working_set_bytes。这三类数据加起来,才是完整覆盖“节点-容器-Pod-对象状态”四层视角的数据来源。实际排障时你会发现,只看node-exporter会漏掉Pod级别的问题,只看cadvisor又不知道对象状态,四者必须配合。
2.3 部署步骤与第一次探活
部署kube-prometheus-stack非常简单,前提是环境中已经有Helm。命令如下:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring --create-namespace \ --set grafana.adminPassword='admin123'部署完成之后,最直观的确认方式是看Pod状态:
kubectl get pods -n monitoring kubectl get svc -n monitoring正常情况下会看到alertmanager、grafana、prometheus、kube-state-metrics、node-exporter这几组Pod陆续变成Running。如果是手工方案,还需要自己写scrape_configs并确认RBAC权限。我一般会等Pod全部Running后,用port-forward打开Grafana确认监控数据:
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80浏览器访问localhost:3000,账号admin、密码就是刚才设置的admin123。进去以后先看Prometheus数据源状态,再到Explore里执行一条最简单的PromQL:
node_load1如果能看到多个节点返回数据,说明采集链路已经通了,后面一切面板工作才有意义。一个很关键的注意点是,默认情况下Prometheus通过Kubernetes服务发现自动抓取目标,所以ServiceMonitor一旦创建,采集目标会自动加入,不需要人工维护target列表,这点和传统静态配置非常不一样。
3. 指标规划与PromQL实战
3.1 节点层:从资源水位看到节点异常
节点层是整个仪表板的地基,节点挂了,上面所有Pod都受影响。节点CPU使用率我通常这么写:
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)这里要注意,node_cpu_seconds_total是计数器,必须用irate或rate转换,直接用原始值会看到一条无限增长的折线,没有使用率含义。计算内存使用率时,Prometheus的node_memory_MemAvailable_bytes已经帮我们处理了页面缓存和buffer,比“总内存减free内存”的旧思路准确得多:
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100磁盘使用率要特别注意排除临时文件系统,否则tmpfs和overlay会把面板曲线撑得很奇怪:
100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay.*"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay.*"} * 100)节点层的面板至少要包含:CPU使用率按节点排列、内存可用趋势、磁盘使用率、网络流入流出速率、节点在线状态。实际上节点在线状态我一般用kube_node_status_condition直接看Ready条件是否为true,比单纯看up指标更贴近Kubernetes语义。
3.2 工作负载层:Pod状态、重启与OOMKill
到了工作负载层,核心已经不是“资源用了多少”,而是“调度和运行状态是否正常”。Pod状态分布直接看phase:
sum by (phase) (kube_pod_status_phase)容器重启次数是排障的利器,Prometheus里会有kube_pod_container_status_restarts_total,但直接看总量容易被长期累计值误导,我通常用15分钟增量:
sum by (namespace, pod) (increase(kube_pod_container_status_restarts_total[15m]))这个表达式在后续写告警时也可以直接用。容器OOMKill是另一个高频问题,Kubernetes不会每次都把Pod状态置为CrashLoopBackOff,但指标里有明确记录:
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}Pod维度的资源使用,我会用cadvisor的container_cpu_usage_seconds_total配合rate:
sum by (namespace, pod) (rate(container_cpu_usage_seconds_total[5m]))注意一个老生常谈的点:容器CPU指标也是计数器,必须加rate,之前见过好几个面板直接展示原始值,曲线一路向上,完全没法用于容量判断。
3.3 控制面层:etcd和API Server不能等挂了再查
很多团队只盯业务Pod,控制面组件长期无人关注,直到API Server响应变慢才想起来查etcd。问题在于,Kubernetes的控制面是一个整体,etcd的磁盘延迟、API Server的请求延迟、调度器的Pending Pod数量,任何一环出问题都会表现为“集群整体变卡”。
控制面的核心指标,API Server 99分位请求延迟可以这样看:
histogram_quantile(0.99, sum by (le, verb) (rate(apiserver_request_duration_seconds_bucket[5m])))etcd是否有Leader,这个指标在任何时候都值得放在Dashboard显眼位置:
etcd_server_has_leader一旦它变成0,意味着集群的存储层已经失去主节点,这是最高优先级故障,没有之一。etcd写延迟也是常见隐患:
histogram_quantile(0.99, sum by (le) (rate(etcd_request_duration_seconds_bucket{operation="write"}[5m])))由于etcd对磁盘性能极其敏感,当它的写延迟开始飙升时,通常意味着磁盘IOPS已经接近瓶颈。我见过不少集群,业务看起来一切正常,但etcd延迟已经持续高位,直到某个节点重启才彻底爆发。仪表板上把这些指标放出来,至少能让你在故障发生前看到征兆。
3.4 用nginx Deployment把最小闭环验证一遍
指标设计得再好,也要用真实业务验证一遍链路。我习惯用nginx做验证,因为它轻量、镜像体积小、配置简单,能很快产生CPU和网络流量。先准备一个deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: default spec: replicas: 3 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.27 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi执行:
kubectl apply -f nginx-demo.yaml kubectl get pods -l app=nginx-demo然后暴露服务并做压测:
kubectl expose deployment nginx-demo --port=80 --target-port=80 --name=nginx-demo-svc kubectl port-forward svc/nginx-demo-svc 8080:80压测工具用ab就够了:
ab -n 10000 -c 100 http://127.0.0.1:8080/压完之后回到Grafana,看nginx所在Pod的CPU曲线、内存曲线,再看容器重启次数有没有变化。如果kube-state-metrics采集正常,Dashboard上的副本数、Pod状态分布也会有对应反馈。这样一个最小闭环就算完整跑通了,之后替换成任何业务Deployment都只是改模板变量的事。
4. 面板排布、告警规则与通知渠道
4.1 Dashboard分组和模板变量
仪表板不能只有一个总览页面。我们的分组方式是按故障排查路径组织的:Cluster Overview放集群整体容量、节点在线数、Pod状态分布;Nodes放节点资源TopN,点进去能看到每个节点的CPU内存带宽详情;Workloads按命名空间过滤Pod CPU、内存、重启数;Control Plane单独一个Dashboard放etcd和API Server指标。
排布面板时,我强烈建议用Grafana模板变量做联动过滤。例如在Dashboard设置里加一个namespace变量,查询语句写成:
label_values(kube_pod_info, namespace)用户打开仪表板默认看到所有命名空间,下拉选择某个namespace后,所有面板会一起过滤到该命名空间。这样一来,一套面板可以服务多个业务线,不用为每个业务单独创建Dashboard。同理,也可以加node变量,节点维度的过滤让排障场景变得更顺手。
4.2 告警规则设计心得
告警规则最忌讳“拍脑袋阈值+错误表达式”。常见基础告警规则我建议至少覆盖这几个场景:节点CPU使用率大于85%持续5分钟、节点内存使用率大于90%持续5分钟、节点Ready状态异常、Pod在15分钟内反复重启超过5次、etcd失去Leader、API Server 5xx错误率超过5%。
以API Server错误率为例,表达式可以这样写:
sum(rate(apiserver_request_total{code=~"5.."}[5m])) / sum(rate(apiserver_request_total[5m])) > 0.05告警阈值不要照抄任何模板,一定要结合集群实际水位调整。比如我遇到过一套集群CPU长期在70%左右运行,如果按默认85%阈值,确实不会频繁触发,但一旦流量抖动就直接冲到95%,等告警发出来时故障已经发生了。后来我把阈值调到80%并搭配5分钟的for持续时间,既避免了抖动误报,又保留了足够的处理时间。
4.3 Alertmanager收敛与通知对接
Alertmanager的核心价值不是“转发消息”,而是“收敛噪声”。默认建议把group_by设置为namespace和job维度,这样同一个namespace里同时挂掉多个Pod时,只会收到一条聚合后的告警,而不是每个Pod单独轰炸一遍。
group_wait一般设30秒,等待同一组告警一起发出;group_interval设5分钟,用于处理新告警合入已通知组;repeat_interval建议拉长到4小时甚至更长,避免同一个问题每小时骚扰一次。通知渠道方面,Alertmanager原生支持邮件和Webhook,如果你想发到钉钉、企业微信或者飞书,通常的做法是部署一个webhook转换插件,或者把Webhook地址指向公司内部已有的通知网关。我实际运维中邮件反而用得最少,大部分线上问题需要即时触达,Webhook到IM工具才是真正有效的渠道。
4.4 多集群视角怎么扩展
当公司不止一套Kubernetes集群时,最简单的方法是给每个集群部署一套kube-prometheus-stack,然后在同一个Grafana里配置多个Prometheus数据源。为了让多集群切换更方便,我给每个数据源加上cluster标签,例如cluster=prod、cluster=staging,面板上的图表里可以按cluster维度聚合对比。
如果要管理大量集群,并且希望在一处查询任意集群数据,就需要引入Thanos或VictoriaMetrics做全局指标汇总,把每个集群的Prometheus作为“远端数据源”接入。长期存储的成本和收益要提前想清楚,我建议先用默认15天本地存储跑一段时间,确认数据规模后再决定是否需要对象存储归档,这一步能少走很多弯路。
5. 实战避坑记录
5.1 仪表板常见的“为什么没数据”排查
以下是我在实际运维中遇到的高频问题,整理成速查表:
| 现象 | 大概率原因 | 排查方向 |
|---|---|---|
| 整个Dashboard都没有数据 | Prometheus数据源配置错误或targets全挂 | 先看Prometheus Targets状态,再查ServiceMonitor selector |
| 节点维度数据缺失 | node-exporter Pod没调度到某些节点 | kubectl get pods -n monitoring |
| Pod状态指标为空 | kube-state-metrics权限不足 | 检查ClusterRole与ServiceAccount绑定 |
| CPU使用率曲线无限增长 | 对计数器忘了用rate/irate | 检查PromQL是否对累计值做了速率转换 |
| Grafana模板变量下拉列表为空 | label_values表达式里的指标不存在 | 到Explore确认指标是否有数据返回 |
| 磁盘使用率全绿但节点被驱逐 | 表达式没排除tmpfs/overlay | 检查fstype正则是否生效 |
| 告警一直不发 | for持续时间过长或表达式阈值太高 | 用实际数据在Explore里手动验证触发条件 |
我模拟最常犯的错误就是直接套模板变量表达式,结果集群里根本没有label_values查询的那个指标名,整个Dashboard的下拉框空白。遇到这类问题,第一件事永远是打开Explore手动跑一遍最基础的PromQL,确认数据存在,再去怀疑Dashboard配置。
5.2 资源开销怎么控制
监控系统本身也是跑在集群里的,资源开销不能忽略。node-exporter每个节点占用几十MB内存,可以接受;kube-state-metrics的消耗和集群对象数量相关,几百MB属于正常范围。真正的大头是Prometheus的内存,尤其是容器指标带来的高基数问题,很容易把Prometheus内存顶到几个G甚至OOM。
我常用的优化手段:把global.scrape_interval从默认15s调整为30s;对不需要长期关注的指标做relabel drop;给Prometheus设置明确的资源requests和limits。有一段时间我为了看“每个容器的网络收发速率”,把container_network_*全量采集,结果一天之后Prometheus内存从1.5G涨到6G,后来改成只对核心命名空间采集,问题立刻消失。监控设计也要讲究投入产出比,不能无限放量,一定要按业务需求裁剪指标。
5.3 让监控大盘真正被用起来
仪表板做出来没人看,是监控项目最常见的失败模式。我的体会是,不要只扔一个“总览”页面给团队,要让每条业务线都能看到自己关心的视图。模板变量解决的是“能看到什么”,而告警联动解决的是“看完怎么办”。告警触发时,通知消息里带上Grafana面板链接,点击直接进入对应的Dashboard,并且保证面板过滤维度和告警里的namespace、pod保持一致,排障效率会明显提升。
另外一个小技巧:团队内部要约定“告警响应流程里的第一个操作就是打开监控大盘”。很多人收到告警下意识先去看日志、看发布记录,其实从监控大盘看起更快,因为指标能告诉你影响面有多大、从什么时候开始、是否还在持续恶化。这个习惯一旦养成,集群出问题时的MTTR会有非常直观的下降。
这次整套仪表板交付之后,我们又陆陆续续做了几件事:把Dashboard导出成Provisioning配置文件放进Git,让每次面板调整都走代码评审;给中间件组件单独建立指标面板,比如Redis、Kafka的客户端连接数和消费延迟;还在压测环境里反复调优了etcd相关告警。我个人最大的感悟是,监控仪表板永远不是一次交付,而是持续迭代的工程。今天加一个新的中间件,明天调整一次告警阈值,后天可能要对接成本分摊,底层的指标字典和标签规范一旦定下来,后续调整的成本就非常低。如果只让我给读者留一句话,我会说:先把采集链路和指标字典打扎实,比花时间调任何一个漂亮的Grafana面板都重要。