news 2026/10/15 1:43:39

K8s部署Prometheus+Grafana:核心链路、配置逻辑与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s部署Prometheus+Grafana:核心链路、配置逻辑与避坑指南

前阵子同事接了一个新项目集群的交接,第一件事就是问我:k8s里监控怎么搭?他说以前在物理机上用的那套Agent采集方案,搬到Kubernetes之后完全失灵了——Pod一重启IP就变,告警目标列表根本维护不过来。

这几乎是每个刚从传统架构转过来的运维都会撞上的墙。K8s里的监控不是把原来的Agent塞进YAML里就行,它需要一套能自动发现目标、能随Pod生命周期动态变化的采集体系。而在社区里沉淀最多、踩坑最少、资料最全的方案,就是Prometheus + Grafana这套组合。

我想借这篇内容把k8s部署prometheus + grafana这件事完整拆开来讲:不是给你一份照抄就结束的安装命令,而是把这个方案在K8s里的核心链路、配置逻辑、以及我实际部署过程中替大家踩过的坑一次讲清楚。不管你是在测试环境先验证,还是准备推到生产,照着这个思路走,能省下不少冤枉时间。

1. 先想清楚:K8s里的监控到底难在哪

1.1 Pod的“短命”让老一套监控直接失效

传统监控的思路是先把机器列表捋清楚,然后在每台机器上装Agent,Agent把CPU、内存、磁盘这些指标持续上报到中心端。这个模式在机器数量稳定、IP固定的环境里非常成熟,维护成本也低。但K8s里的工作负载完全不是这个逻辑:一个Deployment的副本数可以从3个伸缩到30个,Pod随时会被重新调度到另一个节点,Service背后的Endpoints列表每秒钟都可能变化。

更麻烦的是,很多监控指标藏在Pod内部。比如某个业务容器的请求数、某个中间件的连接数,这类指标靠外部Agent很难采集,必须在业务进程内部把数据暴露出来。给一个会随时销毁重建的Pod装Agent,本身就是一个维护噩梦:你没法预先把Agent装到还没创建的Pod里,所以只能在镜像里预置,但又不是所有镜像都适合往里面塞东西。

这里有个很形象的类比:如果把传统配置Agent比作按通讯录打电话,那么K8s里的监控对象更像一个不断换号的群体——你得有办法动态获知“现在该联系谁”,而不是死守一张固定的名单。这也是为什么很多人把Zabbix、Netdata或者其他老方案硬搬到K8s之后,会觉得处处别扭:它们解决问题的前提假设“监控对象相对稳定”在K8s里不成立了。

1.2 Pull模型 + 服务发现:Prometheus解决问题的姿势

Prometheus的处理方式非常朴素:我不注册、不登记,定时过去拉。Prometheus服务器按固定周期(默认30秒)访问一批目标端点,每个端点返回符合OpenMetrics格式的指标文本,这就是Pull模型。相比Agent主动上报的Push模型,Pull模型有几个很实用的好处:被监控端不需要额外安装什么通信组件,只要暴露一个HTTP端口就行;排查问题时可以直接拿curl去看这个端点返回什么;Prometheus作为抓取方,对抓取频率、超时、重试有完全的控制权。

配合Pull模型的是服务发现机制。Prometheus可以对接Kubernetes API,动态获取集群里所有Service、Pod、Node等对象的列表,再根据配置的label selector筛选出要抓取的目标。新扩容一个Pod,只要它暴露了正确的metrics端口,无需人工注册,下一轮抓取就能自动出现在Targets列表里。这种随集群状态自适应的能力,才是K8s监控真正需要的。

K8s生态选择Prometheus作为监控标准不是偶然。正因为这套“自动发现+主动拉取”的架构天然适应Pod的动态调度,再加上CNCF背景下的社区推动,它已经成为K8s监控事实上的默认选项。

2. 选型这步走错了后面全白搭:Helm一键部署与手写清单的取舍

2.1 我为什么最终选了Helm Chart

部署Prometheus到K8s有很多种方式,最省力的是直接用Helm官方Chart。社区维护的kube-prometheus-stack把Prometheus、Grafana、Alertmanager、NodeExporter、kube-state-metrics以及一大堆默认告警规则全部打包成了一张编排清单。装一次就能把能想到的监控组件都带齐,不用自己一个个抠YAML。

我一开始也觉得“全家桶”太重,想自己搞一个精简版。后来在某个内部项目里试过手写一整套,发现光是调通RBAC权限、ServiceMonitor、各种Service关联关系就花了大半天。改用Helm Chart之后,这些问题大部分被Chart自身的默认值处理好了,我只需要关心values.yaml里需要覆盖的少数配置。

实际命令也很简单,先添加仓库再安装:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update kubectl create ns monitoring helm install monitor prometheus-community/kube-prometheus-stack \ -n monitoring \ -f values.yaml

注意我创建命名空间用的是kubectl create ns而不是在Helm命令里带--create-namespace,主要是我习惯安装前先确认命名空间存在、并且知道它里面已经有什么东西。这个习惯在维护多套环境时挺重要,避免Helm悄悄建了空间导致后面找资源时漏掉。

2.2 手写YAML适合什么场景

如果环境网络受限、无法访问Helm仓库,或者你只需要Prometheus单组件、不想被Grafana等组件绑定,那手写YAML也是一条路。手写的好处是每个资源对象都看得见摸得着,适合教学、适合离线环境、适合对组件数量有严格控制的项目。

但代价是维护成本高。Prometheus的部署涉及Namespace、ServiceAccount、RBAC、StatefulSet/Deployment、ConfigMap、Service、Ingress等十几种资源,每个组件都是一套。一旦K8s版本升级导致部分API变化,所有清单都要跟着过一遍。我见过有人从网上手工找了一套版本很老的清单直接apply到新版集群,服务起不来才回头排查兼容性问题,那种排查真的很耗时间。

所以我的建议是:除非有明确的隔离或合规要求,否则优先用Chart。手写方案更适合作为学习手段,而不是长期生产维护的方式。

2.3 版本兼容矩阵:这是最容易被忽视的

K8s版本和Chart版本的兼容性是选型时第一个要确认的点。Kubernetes每代版本会调整一部分API,比如某些资源的group从v1beta1升到v1,旧清单在新集群上无法工作。kube-prometheus-stack本身对K8s版本有要求,升级Chart时如果不看兼容区间,很容易出现装了最新Chart、集群却太老的情况。

建议去Chart仓库的README里确认它支持的K8s版本区间,再对照自己的集群版本选择Chart版本。另外要注意,Grafana和Prometheus这两个组件对版本敏感度不太一样,Grafana面板和数据源插件的兼容性通常更宽松,但Prometheus的存储格式和抓取配置在某些大版本升级时会有迁移要求。升级前做好配置备份,比什么都重要。

3. 让Prometheus“看得见”集群:ServiceMonitor与指标抓取链路的拆解

3.1 抓取链路全景:从ServiceMonitor到Exporter

如果你用的是kube-prometheus-stack,会发现Prometheus并不是直接抓某一个Pod的IP,而是通过一个叫ServiceMonitor的自定义资源来声明“我要抓什么”。这个CRD是Prometheus Operator引入的,也是整个K8s监控体系里最核心的一个概念。

完整链路是这样的:业务服务创建一个Service,指向暴露metrics端口的Pod;在同一个命名空间下创建一个ServiceMonitor,通过selector选择刚才那个Service;Prometheus启动时读取自己配置里的serviceMonitorSelector,匹配到这些ServiceMonitor后生成抓取目标列表;Prometheus定期访问Service的地址,由Service转发到后端Pod的metrics端口完成采集。

这个设计把“目标发现”和“目标管理”解耦了。开发团队只需要维护自己的ServiceMonitor,不需要去改Prometheus的全局配置。从平台角度说,你给业务团队开放一个ServiceMonitor的写入权限,他们就能自助接入监控,而不会碰坏全局抓取配置。这也是Operator模式在监控领域最大的价值。

3.2 标签匹配与服务发现的几个勘误

ServiceMonitor的selector是整个环节里最容易出bug的地方。很多人发现Targets页面一直没有新目标出现,问题大多出在标签不一致上。

我举一个实际的例子:Service的标签是app: my-app,而ServiceMonitor的selector写成release: monitor,两者永远匹配不上。正确做法是让ServiceMonitor的selector去匹配Service的标签,而不是自己想当然的标签。写错之后最典型的现象是:Pod已经起来了,metrics端口也通了,但Prometheus就是抓不到。

是命名空间的问题也经常遇到:ServiceMonitor默认只能发现同命名空间内的Service,如果目标服务在别的命名空间,要么在该命名空间也创建一个ServiceMonitor,要么用namespaceSelector的any: true做跨命名空间选择。后者会有一定的权限和资源过滤上的副作用,实际生产里我更喜欢在每个业务命名空间各放一个ServiceMonitor,保持职责清晰。

排查时最有用的三条命令是:

kubectl get servicemonitor -n <namespace> kubectl get svc -n <namespace> --show-labels kubectl describe servicemonitor <name> -n <namespace>

然后到Prometheus的Targets页面看每个目标的状态和Discovery标签,基本就能定位是selector不匹配、端口不对还是路径不对。

3.3 NodeExporter与kube-state-metrics各司其职

组件多了以后容易混淆这几个采集器的分工。NodeExporter负责采集节点操作系统层面的指标,比如CPU、内存、磁盘IO、网络;kube-state-metrics则从Kubernetes API角度生成资源对象的状态指标,比如Deployment期望副本数、Pod当前状态、Service的selector情况。

采集器数据来源典型指标适合回答的问题
NodeExporter宿主机/Nodenode_cpu_seconds_total、node_memory_MemTotal_bytes节点资源是否够用
kube-state-metricsKubernetes APIkube_deployment_spec_replicas、kube_pod_container_status_restarts_total应用是否按预期运行
cAdvisor(kubelet内置)容器运行时container_cpu_usage_seconds_total、container_memory_usage_bytes单个容器到底吃了多少

这三个一起配合,才能回答“节点没爆但应用挂了”这类常见问题。比如一个Pod反复重启,你在NodeExporter看到的CPU和内存都很正常,但看kube-state-metrics的Pod重启次数指标就会发现异常;再进一步用cAdvisor的容器指标看单容器资源用量,就能定位是进程内存泄漏还是被OOMKilled。离开任何一层,排查都不完整。

4. Grafana接入:把监控数据变成能看懂的图表

4.1 数据源配置的几个细节

在kube-prometheus-stack里,Grafana的数据源通常已经预置好了,它会自动往Prometheus的Service地址注册一个数据源。但如果你是分开部署的,手动配置时最容易犯的错就是把数据源URL填成localhost。

在K8s集群里,Grafana Pod和Prometheus Pod不在同一进程空间,“localhost”指的是Grafana Pod自己,不是Prometheus。正确写法取决于Service的命名和命名空间,一般是类似这样的集群内地址:

http://<prometheus-service-name>.<namespace>:9090

如果Prometheus以StatefulSet方式部署,Service名通常叫prometheus-operated或类似格式。配置数据源还有两个容易被忽略的选项:一个是Access模式要选“Server”,让Grafana服务器去访问Prometheus,而不是浏览器直接访问;另一个是如果Prometheus前面挂了认证或TLS,需要在数据源里对应配置。

4.2 仪表盘导入:别急着找最新版

Grafana生态最大的好处是现成的Dashboard可以导入。社区里常用的几个面板我用下来最顺手:Node Exporter Full这类节点资源看板很直观,K8s集群视图类面板能把集群大致状态一眼扫清楚。导入方式很简单,在Dashboards页面点Import,输入一个数字ID或者上传JSON文件就行。

这里有个经验:不要看到新面板就导入,先看它的数据源是否匹配、依赖的标签名称和你的环境是否一致。很多面板在别人环境里显示正常,导过来一片空,绝大多数是因为标签名不匹配。比如面板里写的是instance,而你的环境实际输出的是host标签,图表自然就是空的。

另一个经验是版本问题。有些Dashboard的JSON是为特定Grafana版本写的,老版本Grafana可能不支持新面板类型,导入时报错。所以如果团队对Grafana版本有强管控,导入面板前最好先确认面板要求的版本范围,不然就是白白花时间调半天。

4.3 自定义一个简单面板的流程

不管现成面板多丰富,总有某些指标需要自己画。以最常见的CPU使用率为例,PromQL可以这样写:

100 - (avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100)

这个表达式的意思:先求每台节点5分钟内空闲CPU秒数的变化率,按节点聚合后整体里减掉,得到的是各节点的CPU使用率。新建面板时选时间序列图,查询选择刚写的PromQL,单位设置为percent(0-100),图例里显示instance标签,一张节点CPU使用率面板就出来了。

自定义面板这件事做几次之后,你对PromQL的理解会比看十篇教程都深。我个人的做法是先找现成面板里的查询语句,读懂它,再改标签或者时间范围,慢慢就掌握了一套属于自己的查询习惯。

5. 忘掉告警等于白监控:Alertmanager与告警规则落地

5.1 PrometheusRule的语义与热加载

指标有了、图表有了,还差最后一道闭环:告警。Prometheus自己负责计算和触发告警,但真正把消息发出去的是Alertmanager这个组件。

在kube-prometheus-stack里,告警规则同样通过自定义资源来管理,也就是PrometheusRule。一个PrometheusRule里可以声明若干组规则,每组规则的核心字段包括:

  • expr:PromQL表达式,满足条件时触发告警;
  • for:条件持续多久才触发,避免瞬时抖动造成误报;
  • labels:给告警附加标签,后续路由会用到;
  • annotations:告警内容文本,包括标题、描述、处理建议。

Prometheus会周期性重新加载规则文件,改完规则不需要重启服务,过几十秒就会自动生效。这在实际值班时很实用,临时调整告警阈值不用走发布流程。

一条规则示例:

apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: example-rule namespace: monitoring spec: groups: - name: app.rules rules: - alert: TargetDown expr: up == 0 for: 1m labels: severity: critical annotations: summary: "{{ $labels.job }} 的抓取目标已下线" description: "实例 {{ $labels.instance }} 已超过1分钟无法抓取"

5.2 告警路由:让通知找到对的人

Alertmanager的route配置是整个告警体系的调度中枢。它按照标签匹配,把不同告警分发给不同接收者。例如按severity区分:critical的告警走紧急通道,warning的走普通通道;按alertname分组,避免同一类告警刷屏。

route是一个树形结构,顶级路由下面可以有子路由,每级路由都可以定义continue、receiver、group_by等参数。实际场景举例:把基础设施相关告警发给运维值班渠道,把业务应用告警发给业务值班渠道。这个用父路由匹配团队标签、子路由匹配告警级别就能完成。

receiver这块,最常用的是email、webhook两种。Webhook的用途很广,可以接到办公软件的群机器人里,也可以接到内部的工单系统。实际配置时需要注意:如果webhook接口响应慢或者返回错误,Alertmanager会按重试策略不断重发,接收方需要做好幂等处理。

5.3 告警从触发到恢复的完整生命周期

一条告警从产生到恢复,通常会经历Pending、Firing和Resolved三个阶段。Pending表示PromQL条件已经满足,正在等待for的持续时间验证;一旦超过for指定的时间条件仍然成立,告警进入Firing并送入Alertmanager;当表达式不再成立时,Prometheus自动发送Resolved通知表示恢复。

了解这个生命周期有助于设置合理的for值。for设得太短,一个突发的CPU尖峰就会被当成告警轰炸;设得太长,小故障会拖很久才被发现。对大多数业务指标,1到3分钟的for是一个比较合理的起点。告警规则数量多起来之后,还要注意抑制规则(inhibit_rules)的合理配置。比如某个节点宕机,会连带触发一堆Pod级别的告警,这时如果有抑制规则自动屏蔽衍生告警,值班同学的手机就能清净很多。

6. 这些坑我替你们踩过了:存储、资源配额与版本兼容

6.1 默认存储是emptyDir:重启即失忆

kube-prometheus-stack安装完成后,如果没做任何持久化配置,Prometheus和Alertmanager的数据都放在emptyDir里。emptyDir的生命周期跟随Pod,Pod一旦重启或迁移到其他节点,历史数据全部清空。这在测试环境无所谓,生产环境是不能接受的。

生产环境必须配置持久化存储。以StorageClass为例,在values.yaml里指定Prometheus使用PVC:

prometheus: prometheusSpec: storageSpec: volumeClaimTemplate: spec: storageClassName: "local-path" accessModes: ["ReadWriteOnce"] resources: requests: storage: 50Gi

这里踩过的坑是:如果集群里的StorageClass没有配置动态供给,PVC会一直停在Pending状态,Prometheus的Pod也起不来。所以配置前先确认集群里有哪些可用的StorageClass,以及它们的供给方式。另外Prometheus官方建议保留15天左右的数据,具体存储容量可以根据你的抓取指标数量和保留周期粗略估算。

6.2 资源配额调不好,Pod起不来

默认的Chart会为Prometheus和Grafana设置requests,但如果你在自定义values.yaml时把limits设置得比实际用量小,最常见的问题就是Pod一直重启,看日志才发现是OOM。

各个组件的资源需求差异很大:Prometheus随着监控目标增加,内存消耗会明显上涨;Grafana在加载复杂面板时CPU波动明显。建议初期不要把limits卡得太死,先让Prometheus和Grafana跑起来,观察一到两周的实际用量,再根据Grafana里的资源监控回头调整。

我之前就踩过一次:给Prometheus的容器设了2Gi内存limits,实际因为采集了太多业务指标,半小时就崩了,排了半天才发现是内存被打满。后来把limits调到合理值并加了PVC,才稳定下来。合理的做法是先用宽松limits跑,再结合容器的实际用量曲线去收紧,而不是上来就拍脑袋定一个数。

6.3 kubelet指标抓取失败的常见原因

集群本身的很多指标依赖kubelet暴露的metrics接口。但在部分托管集群或自建集群里,kubelet的指标端口可能没有对外开放,导致Prometheus的target列表里出现很多down状态。

常见原因有两个。一个是指标地址绑定在回环地址上,只能本机访问,需要通过修改kubelet启动参数或者使用特殊抓取方式解决;另一个是kubelet的安全端口需要认证,而Prometheus没有配置对应的token或者证书。这部分和集群初始化方式强相关,不同发行版的kubelet参数默认值都有差异。

kube-prometheus-stack自带的规则和抓取配置已经处理了大多数场景,但仍建议安装后先打开Targets页面过一遍,确认所有目标都在up状态再继续加业务采集。一个很实用的排障路径:在目标列表里点开一个down状态的目标,查看它返回的error信息,大多数时候错误信息已经把原因说得很直白了。

现象可能原因排查/解决方案
Prometheus Pod一直重启内存limits过小或PVC未绑定查看Prometheus日志,调整资源或存储
Grafana数据源连不上URL填了localhost改为Prometheus Service的集群内地址
Target列表有大片downkubelet端口未开放或证书异常逐项看Down的原因,重点看错误信息和认证配置
新添加的Service采集不到ServiceMonitor标签不匹配核对selector标签与Service标签一致

最后说一点个人体会:Prometheus+Grafana这套方案在K8s里的成熟度确实高,但难度从来不在安装,而在把链路跑通之后怎么持续维护。我自己的习惯是每接一个新集群,先让Targets页面保持全绿,再谈后面的事情。监控系统的价值不在于面板多华丽、告警多密集,而在于当你真正需要它的时候,它足够可靠、足够准确。希望这篇文章能帮你少走点弯路,把基础打扎实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:43:10

GD32远程升级IAP全解析:双工程、Flash分区与跳转避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:43:00

国内大学生最爱的AI论文写作工具是哪款?

一、本土全流程论文 AI 工具&#xff08;中文适配首选&#xff09; 1. 千笔AI&#xff08;中文论文全能王者&#xff09; 核心定位&#xff1a;专为中国学术场景研发&#xff0c;全流程覆盖&#xff0c;适配高校审核标准&#xff0c;学生首选。核心功能&#xff1a; 智能选题&a…

作者头像 李华
网站建设 2026/10/15 1:42:55

2S磷酸铁锂主动均衡设计:基于MP2642GR-Z与MK20DN128的压差治理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:42:52

C# WinForm 使用 iText 导出 PDF 的简单 demo 与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:42:11

TMS320C6678 DSPLIB FFT实战:从调用到多核并行优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:41:50

ARM嵌入式Linux下libpcap抓包库移植实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华