监控K8s和监控传统虚拟机完全是两码事。你在虚拟机时代写死一组目标IP,配好账号密码定期拉数据就完事了。到了K8s里,Pod频繁漂移、节点自动扩缩容、探针重试、滚动发布,监控对象的生命周期可能只有几分钟。我第一次给K8s集群上监控时,用旧思路手动加了一堆静态采集端点,结果发布一次服务全乱套,Prometheus targets里一片红,排错排到怀疑人生。
后来我彻底换成了 Prometheus + Grafana 这套组合,基于服务发现自动感知Pod和节点变化,配合kube-state-metrics、node_exporter这类辅助组件,把K8s集群从基础设施层、平台层到工作负载层的指标全部收拢起来,再通过Grafana做可视化,Alertmanager做告警通知。这篇文章把整套部署过程、关键原理、PromQL常用查询,以及我在实战中踩过的坑完整记录下来,适合准备给K8s上监控的运维、开发和架构师直接参考。
1. 部署前必须想清楚的:K8s监控到底要解决什么问题
1.1 动态环境让传统监控手段失效
传统监控的思路是静态目标:先列一批机器IP和服务端口,监控系统主动去连或者装个Agent上去采集,目标列表基本固定。K8s打破了这套逻辑,一个Deployment从2个副本扩到20个副本,Pod IP完全随机分配,滚动更新时旧的Pod销毁、新的Pod拉起,你怎么维护监控对接关系?
这也是当初我在规划K8s监控方案时最头疼的点。靠人工维护列表不现实,靠脚本同步节点信息也容易滞后,真正可行的是让监控系统自己去发现目标。Prometheus的K8s服务发现机制就是干这个的,通过访问K8s API实时获取Service、Pod、Endpoint的变化,动态调整采集目标。Pod销毁了,数据点自动消失;新Pod创建了,秒级出现在采集列表里。这个机制才是适配容器环境的核心,而不只是某个采集组件换了个名字。
所以无论用什么监控系统,在K8s里最关键的能力是:能不能跟上资源对象的生命周期变化,而不是靠人肉维护拓扑。
1.2 从三个层次拆解监控目标
在动手部署前,最好把监控目标分成三个层次,每个层次关心的指标完全不同,后续的采集配置、仪表盘设计、告警阈值都会跟着分层走。我在文档里习惯用下面这张表帮团队对齐需求:
| 层次 | 监控对象 | 核心指标示例 | 数据来源 |
|---|---|---|---|
| 基础设施层 | 节点CPU、内存、磁盘、网络 | node_cpu_seconds_total、node_memory_MemAvailable_bytes、node_filesystem_avail_bytes | node_exporter |
| K8s平台层 | API Server、etcd、Scheduler、Controller Manager | apiserver_request_duration_seconds_bucket、etcd_server_leader_steady_state_totals | kube-apiserver、etcd自身的/metrics端点 |
| 工作负载层 | Deployment、Pod、容器、HPA | kube_pod_container_status_restarts_total、kube_deployment_status_replicas_available | kube-state-metrics |
这三层监控缺一不可。只盯节点不盯Pod,容器频繁重启你是看不出来的;只盯工作负载不盯平台层,某次故障很可能根源在API Server响应变慢,而你抓到的只是表象。我当时就把这套分层逻辑画成墙上的图,团队开会讨论时一对照,需求很快就清楚了。
1.3 为什么是Prometheus+Grafana这个组合
市面上的监控方案不少,但K8s生态里绕不开Prometheus,因为它是云原生计算基金会孵化的项目,K8s自身就把/metrics端点作为指标暴露的标准方式,几乎所有云原生组件都自带Prometheus格式的指标端点。它的拉取式采集模型配合服务发现机制,天然适配动态环境;PromQL查询语言支持多维数据模型,按标签聚合、筛选、计算都很方便;再加上Alertmanager的告警路由、分组、静默能力,整条链路是完整的。
Grafana的优势不光是图表好看,它对数据源做了抽象层,一套面板可以对接Prometheus、Loki、Elasticsearch、CloudWatch等多种数据源。这意味着你以后加了日志平台或者链路追踪,可以在同一个看板里把指标、日志、链路关联起来,不用反复切换系统。而且Grafana社区有海量现成Dashboard模板,导进来改改数据源就能用,上手成本很低。
这套组合给我的感觉是:Prometheus负责把数据收得全、算得准,Grafana负责把数据看得懂、看得爽。分工明确,不越界,这也正是它能成为K8s监控事实标准的原因。
2. 组件分工与数据流:先搞清楚谁在干什么再动手
2.1 Prometheus:时间序列数据库加采集调度器
Prometheus不是一个简单的采集器,它是一个带有时间序列数据库(TSDB)的完整监控系统。它通过HTTP轮询方式抓取各目标暴露的指标,并将指标数据按时间维度存储,随后通过PromQL对外提供查询能力。在K8s环境中,它通过内置的Kubernetes服务发现机制,实时Watch API Server中的资源变化,动态生成采集目标列表。
理解Prometheus的关键在于三个词:抓取、存储、查询。抓取是它的采集循环,按照scrape_interval配置的时间间隔去拉取暴露的指标;存储是TSDB在本地落盘,默认保留15天数据;查询是接受PromQL请求,从存储中计算结果。这三个环节只要有一个出问题,整个监控链路就会出状况。
2.2 Grafana只负责展示,不负责采集
很多刚接触的人会搞混Prometheus和Grafana的职责边界,以为Grafana是负责采集的。其实Grafana只是一个可视化平台,它通过配置数据源地址,向Prometheus发起查询请求,然后把结果渲染成图表面板。Prometheus不采集的话,Grafana画不出任何东西。
这意味着排错顺序很重要。我一般碰到大盘没数据,先看Prometheus里能不能查到指标,再回头看Grafana配置。在Prometheus查询页面里输入一条PromQL,如果有数据,问题一定出在Grafana侧的数据源配置、时间范围选择或变量设置上;如果Prometheus里本来就查不到数据,就得从采集链路排查。
2.3 采集链路全览:从kubelet到Grafana
K8s监控数据流可以画成一条清晰链路:节点上的node_exporter暴露节点指标,kubelet内嵌的cAdvisor暴露容器指标,kube-state-metrics从API Server聚合资源对象状态指标,这三类数据源被Prometheus以服务发现方式采集;Prometheus将指标写入内置TSDB后,一方面通过PromQL响应Grafana的查询请求,另一方面通过告警规则评估定时触发Alertmanager。
链路里最容易忽略的是kubelet的指标端点。K8s的每个节点上运行着kubelet,它内置了cAdvisor来采集本节点所有容器的CPU、内存、磁盘、网络指标,通过/metrics/cadvisor端点暴露。这是Pod和容器级指标最主要的来源,不需要额外部署Agent。这也是Prometheus采用拉取模式的好处:K8s已经把这些指标准备好了,监控系统只需要知道去哪个端点拿就行。
2.4 辅助组件的角色定位
除了Prometheus和Grafana,实际部署中还需要三个重要辅助组件,每个组件承担的角色不同,不能混用:
node_exporter负责采集节点级别的硬件指标,包括CPU、内存、磁盘、文件系统、网络流量。它运行在每个节点上,以DaemonSet方式部署,采集到的指标都带instance标签,用于标识节点IP。
kube-state-metrics负责将K8s资源对象的状态转换成指标,例如期望副本数、可用副本数、Pod重启次数、Pod状态、资源请求量等。它不采集日志,不采集节点指标,只关注资源对象的期望状态和实际状态。需要注意的是,如果你关心的是"Deployment有几个副本可用"这类问题,kube-state-metrics是不可替代的数据源。
第三个是metrics-server,它轻量采集节点和Pod的CPU、内存实时用量,为kubectl top和HPA提供数据。它和上面两个组件并不冲突,metrics-server的数据是聚合API方式提供的,Prometheus一般不直接采集它,而是绕过去使用更底层的指标源。搞清楚这三个组件的分工,部署时就不会装重复或者漏装。
3. 从零到一部署Prometheus监控栈
3.1 前置条件与环境规划
部署前先把环境确认一遍,省得中途返工。K8s集群版本建议1.26以上,Helm版本3.x,Prometheus Operator对K8s版本有兼容性要求,版本太老会有各种API不兼容问题。存储方面,Prometheus默认用emptyDir存数据,Pod一重启数据全没,生产环境必须挂持久卷。我一般建议提前确认集群里的StorageClass,使用块存储或者分布式存储都可以,磁盘性能直接决定PromQL查询的响应速度。
名字空间我统一用monitoring,也可以按团队习惯调整,但所有组件放同一个命名空间便于权限管控和资源隔离。另外建议在部署前确认集群是否有足够的资源余量,kube-prometheus-stack默认会部署Prometheus、Alertmanager、Grafana、kube-state-metrics、node_exporter等组件,至少预留2核CPU和4GB内存,否则部署到小规格集群容易把节点压垮。
3.2 Helm安装kube-prometheus-stack操作步骤
这是我在生产环境用得最多的方式。kube-prometheus-stack是专门为K8s监控栈打好的集成包,内置了Prometheus Operator、Prometheus实例、Alertmanager、Grafana、kube-state-metrics、node_exporter,以及一系列预置的告警规则和Dashboard。相比一个个组件手动装,Helm方式能极大减少配置工作量。
先添加Helm仓库并拉取最新版本:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm search repo prometheus-community/kube-prometheus-stack然后创建一个values文件,按需覆盖默认配置。下面是一个我在生产环境常用的最小化配置片段:
grafana: adminPassword: "your-strong-password" persistence: enabled: true storageClassName: "your-storage-class" size: 5Gi service: type: LoadBalancer prometheus: prometheusSpec: retention: 15d storageSpec: volumeClaimTemplate: spec: storageClassName: "your-storage-class" accessModes: ["ReadWriteOnce"] resources: requests: storage: 50Gi resources: requests: cpu: 500m memory: 2Gi limits: cpu: 1 memory: 4Gi alertmanager: alertmanagerSpec: storage: volumeClaimTemplate: spec: storageClassName: "your-storage-class" accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi然后执行安装:
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring -f values.yaml等待Pod全部就绪:
kubectl get pods -n monitoring -w正常情况下会看到prometheus-operator、prometheus、alertmanager、grafana、kube-state-metrics以及每个节点上的node-exporter类似的Pod。这些Pod全部Running之后,基础监控栈就起来了。
3.3 关键参数解析:为什么这样配置
Grafana的adminPassword必须设成强密码,因为默认账号admin权限极高,能直接看到集群所有监控数据和告警配置,暴露在公网容易被爆破。Grafana的persistence我同样建议开启,否则你辛辛苦苦配好的面板、数据源、用户,Pod重启后全丢。storageClassName根据实际环境填,前提是集群里有默认的存储类。
Prometheus的retention表示数据保留天数,15天是一个平衡点,太长对磁盘空间要求高,太短做长时间范围的趋势分析会查不到历史数据。storageSpec配置了持久卷声明模板,这是Prometheus Operator的典型用法,它会为每个Prometheus副本自动创建PVC,比手动绑定PV更灵活。
Alertmanager的存储用于保存告警抑制和静默状态,生产环境建议持久化,否则重启后静默规则丢失,可能引发重复告警。资源requests和limits是为了防止监控组件抢占集群资源,尤其当监控目标数量很大时,Prometheus内存占用会明显上升,设limit可以避免它拖垮节点。
3.4 部署后必须完成的验证动作
组件起来了不代表监控链路是通的。我用三分钟做三件事验证:
第一,检查Prometheus Targets。通过kubectl port-forward把Prometheus服务暴露出来,访问Targets页面,状态应该是全部UP,至少能看到kube-apiserver、kubelet、node-exporter、kube-state-metrics这些采集目标。
kubectl get svc -n monitoring kubectl port-forward -n monitoring svc/kube-prometheus-stack-prometheus 9090:9090浏览器打开http://localhost:9090/targets,这里记录着所有采集目标的状态和最近一次抓取时间。如果某个目标显示Down,点进去看错误信息,一般是证书问题、RBAC权限问题或者网络不通。
第二,验证PromQL基本查询。在Prometheus自带的查询页面执行以下语句,确保有数据返回:
up正常情况下会返回一堆指标数据,后面跟着1,代表各目标在线。再试试:
node_cpu_seconds_total有数据说明node_exporter的采集链路没问题。
第三,验证Grafana登录。同样用port-forward暴露Grafana服务,使用设置的admin密码登录,默认数据源和预置Dashboard应该已经存在。找一个现成的Kubernetes集群视图的Dashboard看是否出图。这一步能确认Prometheus和Grafana之间的数据查询链路是通的。
4. Grafana数据源接入与仪表盘实战
4.1 数据源的两种接入方式
kube-prometheus-stack安装完成后,Grafana会自动配置好Prometheus数据源,因为Operator在初始化时就把数据源地址写进去了。但如果你是基于已有Grafana实例接入新的Prometheus,就需要手动添加数据源。在Grafana左侧菜单进入Configuration下的Data Sources页面,点击Add data source,选择Prometheus,把URL填成Prometheus服务的访问地址,例如http://kube-prometheus-stack-prometheus.monitoring.svc:9090,点击Save & Test,看到Success说明连接成功。
这里有个细节容易踩坑:如果Grafana和Prometheus在同一K8s集群内,URL用Service的ClusterIP或DNS名称是最稳的,走内网不需要经过负载均衡;如果Grafana在集群外,就得把Prometheus通过Ingress或NodePort暴露出来,并配置可访问的地址。
4.2 导入现成Dashboard与手工搭建核心看板
Grafana社区有很多现成的K8s Dashboard模板,我常用的是Kubernetes Cluster维度和Kubernetes Node维度,模板ID在Grafana官网可以搜到,市场上也能直接搜到。导入方式是在Dashboards页面点击Import,输入模板ID,选择之前配置好的Prometheus数据源即可。这类模板的PromQL表达式非常全面,覆盖CPU、内存、网络、文件系统、Pod状态等维度,基本满足日常巡检需求。
不过我的建议是别完全依赖现成模板,至少自己搭一张核心总览看板,这样你能理解每个面板背后的PromQL逻辑,一旦出问题也知道去哪查。核心看板我一般放四五个面板:集群节点数、节点CPU使用率Top、内存使用率Top、Pod状态分布、容器重启次数Top。每个面板对应一个Panel类型和一条PromQL查询。
4.3 日常用得最多的PromQL查询
这里把我在日常运维中高频使用的PromQL整理成表,供参考:
| 场景 | PromQL表达式 |
|---|---|
| 节点CPU使用率 | 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100) |
| 节点内存使用率 | 100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100) |
| 节点磁盘使用率 | (1 - node_filesystem_avail_bytes{fstype!~"tmpfs |
| Pod重启次数变化 | changes(kube_pod_container_status_restarts_total[5m]) |
| Deployment可用副本数 | kube_deployment_status_replicas_available / kube_deployment_spec_replicas |
| API Server平均延迟 | rate(apiserver_request_duration_seconds_sum[5m]) / rate(apiserver_request_duration_seconds_count[5m]) |
这些查询的价值不仅在看板,排查问题时直接在Prometheus里执行,能快速定位问题。比如某个服务发布后大量报错,先查kube_pod_container_status_restarts_total,确认是不是容器反复重启;再看节点CPU使用率,排除资源竞争。
4.4 变量、权限与看板组织
Grafana面板支持模板变量,可以把PromQL中的标签值动态化。我在做多集群纳管时,经常在Dashboard里定义一个cluster变量,取值范围来自label_values(up, cluster),然后用$cluster做查询过滤,这样一张Dashboard就能在多个集群之间切换。变量是Grafana用起来很顺手的功能,建议熟练掌握。
如果团队内有多人查看监控,建议启用Grafana的组织和权限控制。默认org所有人共享,容易误操作修改面板。可以在Server Admin里创建独立组织,或者给不同用户分配Viewer角色,只给少数管理员Editor权限。监控是给全团队看的,但改动权限应该收敛,否则哪天有人不小心改了个PromQL,整块看板就成了错的。
5. 告警规则与通知链路:让监控从"看板"变成"值班"
5.1 告警规则的本质与工作机制
告警不是Prometheus凭空产生的,它本质上是一条PromQL布尔表达式:当查询结果满足某个条件时触发告警。Prometheus会持续评估规则中定义的表达式,如果条件的持续时长超过了for字段设置的时间窗口,就标记为Firing,并通过Alertmanager发送通知。
这里有一个关键概念叫告警疲劳的消除,for字段的意义是防止瞬时抖动误报。比如节点CPU瞬时冲到90%,可能只是某个任务高峰期,并不是故障,设置for: 10m意思是持续10分钟才触发告警,这能过滤掉大量误报。我在告警配置上花的时间比部署本身还多,因为规则设置得好不好,直接决定值班团队会不会被噪音淹没。
5.2 内置规则与自定义规则写法
kube-prometheus-stack自带了一套齐全的告警规则,部署后Prometheus会加载这些规则,主要包括集群节点异常、K8s资源状态异常、etcd异常等类别。以Pod频繁重启为例,内置规则覆盖了很多常见场景,比如死锁无法启动、异常状态持续过久等。
实际业务场景往往需要自定义规则。下面是一个Deployment可用副本数不足的告警规则示例:
groups: - name: deployment-availability rules: - alert: DeploymentReplicasUnavailable expr: kube_deployment_status_replicas_available / kube_deployment_spec_replicas < 1 for: 5m labels: severity: critical annotations: summary: "工作负载副本不足" description: "Deployment {{ $labels.namespace }}/{{ $labels.deployment }} 可用副本数低于期望值,当前可用副本数为 {{ $value }}"这类规则通过kube-state-metrics提供的指标判断工作负载健康状况。我通常会为无状态服务配置副本数期望值校验,一旦可用副本不等于预期,立刻通知到人,这是防止服务雪崩的第一道防线。
5.3 Alertmanager路由与接收器配置
Alertmanager收到Prometheus推送的告警后,按照路由树决定发送给谁、用什么方式通知。路由配置的核心是分组和匹配。分组逻辑用group_by字段控制,比如按告警名称分组,可以将同一时间段内重复的同类告警合并为一条。这在节点故障时尤其有用,一个节点挂了可能连带很多Pod都有问题,不分组的话通知渠道会被刷爆。
接收器支持邮件、Webhook、钉钉、企业微信等通道。下面是一个把高等级告警发到Webhook、普通告警发到邮件的最小路由配置:
route: group_by: ['alertname', 'severity'] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: 'default' routes: - matchers: - severity = "critical" receiver: 'critical-webhook' receivers: - name: 'default' email_configs: - to: 'ops-team@example.com' send_resolved: true - name: 'critical-webhook' webhook_configs: - url: 'http://notify-service.monitoring.svc:8080/alert'repeat_interval控制相同告警的重复通知间隔,设置4小时是比较稳妥的默认值,既能持续提醒,又不会每隔几分钟刷屏。send_resolved: true表示告警恢复时也要通知,这样团队知道问题已经解除,不用反复确认。
5.4 告警去重、静默与值班运营技巧
告警规则上线后,第一个要紧的事是建一个静默机制。当执行运维变更时,比如滚动升级节点,你明明知道Pod会短暂不可用,如果不去静默,值班人员会收到一堆假告警。Alertmanager的Silence功能可以提前设置静默时间窗口,按标签匹配要屏蔽的告警,到期自动解除。
我在这里的实践是:每次重大变更前,先在Alertmanager里创建静默,匹配本次变更涉及的节点或命名空间,变更结束确认无误后再手动解禁。这个习惯极大减少了告警疲劳。另外,告警恢复通知也非常重要,我见过太多团队只配了触发通知没配恢复通知,导致值班人员看到告警后不知道是否已恢复,只能再去查看板确认,效率很低。建议把send_resolved全程开启。
6. K8s监控实战中的坑与排查思路
6.1 Targets一片红的完整排查链路
部署完监控后最常出现的问题就是Prometheus Targets里大量目标显示Down。我遇到过一次,node-exporter的Targets全红,并且Targets页面上看到错误信息是tls handshake timeout。我先检查了node-exporter Pod的日志,确认服务本身正常,端口也在监听,说明问题出在Prometheus到节点的网络链路或者证书上。
继续排查发现,集群里部分节点是通过一组中转节点接入的,节点之间的网络策略没有放行10250端口,导致Prometheus无法访问kubelet的HTTPS接口。我用kubectl get networkpolicy查看了命名空间策略,发现monitoring命名空间被拒绝访问别的命名空间。解决方案是在NetworkPolicy里放行Prometheus对10250和9100端口的出站访问。这类问题在K8s环境里特别常见,尤其是启用了严格网络策略的集群,排错顺序建议是:先看Targets页面的错误信息,再查目标组件Pod是否正常,接着查网络连通性,最后检查RBAC。
6.2 存储暴涨与高基数指标失控
Prometheus TSDB的存储膨胀通常有两个原因:数据保留时间太长和指标基数太大。前者好解决,调整retention即可;后者是隐蔽的坑。高基数意味着同一指标名下出现了海量标签组合,例如把用户ID放在标签里,每次查询都要扫描大量时间序列,内存占用和磁盘增长速度完全是爆炸式的。
我在某个项目上见过一次内存持续攀升,排查后发现是某个业务指标把请求路径中的参数全部打成了标签,一小时内产生了几百万条时间序列,最终把Prometheus OOM。处理方法是设计promtool检查指标基数,并在采集配置里对高基数指标做drop操作,把URL参数、随机ID这类高离散值的标签去掉。经验法则很简单:标签值的高基数通常是性能灾难的来源。另外有条件的建议配置远程存储,把Prometheus的TSDB数据远程写出一份到长期存储,这样本地保留周期可以缩短,不用在磁盘和查询性能之间找平衡。
6.3 Grafana图表空白的定位思路
Grafana面板加载后一片空白,这是排查链路中最容易绕弯路的问题。我的定位思路是分三段:先在大盘面板的Query编辑器里直接看查询返回结果,如果显示No data,就把同样的PromQL拿到Prometheus里执行一遍。如果Prometheus能查到数据,问题就出在参数上,最常见的是$__rate_interval这种Grafana内置变量,它在5m速率计算里有时会因为时间范围选择太小而返回空数组。
如果是变量导致的空数据,检查Dashboard的变量定义是否正确,尤其是用label_values查询标签值时,数据源选择是否指向了正确的Prometheus。还有一种很低级但很常见的坑:时间范围越界。在Grafana右上角选了最近1小时,而Prometheus数据保留时间较短或集群刚重启过,时间范围内的数据确实不存在。这时候把时间范围拉大到7天,再试一次,基本就能区分是数据不存在还是查询逻辑错误。
6.4 告警延迟与通知丢失
Alertmanager的告警延迟一般出在路由配置上。我记得有一次发现告警要过5分钟才收到通知,后来查配置发现group_wait设置的是5分钟,它的作用是等同一分组的告警聚齐后再统一发送,所以会有明显延迟。高优先级告警我建议单独路由分支,设置较短的group_wait,比如30秒。
通知丢失需要检查三个点:Prometheus到Alertmanager的推送是否成功,在Prometheus的Alerts页面里能看到告警是否被选中;Alertmanager的Receivers页面能看到通知发送结果和错误消息;接收器本身是否配置正确,比如Webhook URL访问不到,或者邮件服务被限流。还有一种很容易忽略的情况:Alertmanager自身的持久化存储如果丢失,静默规则就会消失,已被屏蔽的告警会突然全部发出来,造成通知风暴。这也是前面为什么建议给Alertmanager配置持久卷的原因。运营监控系统,很多时候不是功能缺失,而是细节没配好,告警链路尤其如此。
最后说说我这几轮踩坑下来的实际体会
监控系统部署起来并不难,难的是把它运营好。我在给多个K8s集群部署Prometheus和Grafana后最大的感受是:这套监控栈的稳定性和你的配置习惯强相关。先把分层指标梳理清楚,再动手部署,比装完再补数据源要高效得多;告警规则要克制,宁可少配几条精准的,也不要堆一堆误报率高的规则,值班团队对告警的信任一旦崩塌,再重要的问题都会被忽略;存储和资源配额要提前规划,后期扩容的成本远比前期预估高。
还有一个小技巧:把PromQL查询沉淀成本团队的共享文档,常用查询分类归档,新同事接手监控时直接抄作业,能少走很多弯路。监控是持续迭代的过程,随着集群规模增长和业务形态变化,指标维度和告警策略都需要反复调整。希望这篇文档能帮你把第一套K8s监控栈顺利落地。