
你如果亲手管过一套k8s集群大概率经历过这种瞬间业务方说“接口超时了”你登上节点一看CPU、内存、磁盘全都正常但Pod状态就是不对劲。或者半夜被拉起来说某个服务挂了你ssh上去——等等Pod在哪个节点它重启过几次镜像是什么时候变的没有监控系统这些问题你只能靠猜。今天这篇就围绕k8s部署Prometheus监控这件事把整个链路讲透从方案选型、yaml手写、服务发现原理到告警规则配置和排障心得全部基于我实际折腾过的经验来写。这篇文章适合两类人一类是刚接手k8s集群、急需给业务补上监控视角的运维或开发另一类是已经在用Grafana和Zabbix、但对Prometheus在容器环境下的特殊玩法还不够熟悉的同学。看完之后你能照着里面的配置把一套可用的Prometheus监控搭起来也能在出问题时知道往哪个方向排查而不是干瞪眼。1. 先搞明白k8s里做监控到底难在哪为什么偏偏是Prometheus1.1 容器和Pod的动态性让传统监控手段直接失效传统的服务器监控思路很简单给每台物理机或虚拟机装个AgentAgent定期把数据推给中心端你再给IP一个“自定义名字”做展示。这套模式在虚拟机时代没什么问题因为机器是相对稳定的IP是固定的服务是常驻的。但到了k8s环境游戏规则彻底变了。Pod是最小的调度单元它的数量和位置随时在变。一次发布、一次滚动更新、一次节点故障都可能导致一堆Pod被销毁、被重建、被调度到别的节点。如果你还用“给每台机器配监控”的思路会发现两个问题第一监控目标本身是漂移的今天监控的是A节点上的容器明天它可能跑到B节点去了第二k8s里的资源层级很复杂你要看的不只是节点还要看Deployment、StatefulSet、Pod、Service、容器甚至还要看集群层面的调度情况。传统Agent对着固定IP采集数据的方式在这里根本玩不转。还有一个棘手的点k8s内部组件的指标也不是一个格式。kubelet暴露的metrics是一套文本格式Kubernetes API Server暴露的指标是另一套cAdvisor给的容器指标又是第三套。如果你的监控系统只能理解一种数据格式那你在k8s里就得同时维护好几个人插件想想都头疼。这里顺带多说一句很多人会把“容器监控”和“k8s监控”混为一谈其实差得远。容器监控只要看CPU、内存这些资源数据就够了k8s监控必须把视角拉到集群编排层Pod为什么被驱逐、节点是否Ready、副本数有没有达标、PVC有没有Pending、Service的Endpoint是否正常。这些维度传统监控根本没做过。1.2 Pull模型与标签体系恰好长在k8s的审美上Prometheus能在k8s里成为事实标准核心原因就两个拉取模型和数据模型。先看拉取模型。Prometheus是“主动去拉”的也就是Pull模式。每个被监控的目标只需要暴露一个HTTP接口Prometheus定期去这个接口把指标抓回来。在k8s里这个设计有个天然优势kubelet、API Server这些组件本来就自带/metrics接口Prometheus可以直接去抓不需要你在节点上额外装什么插件。而像容器指标k8s里的cAdvisor也已经给你暴露好了Prometheus配置一个抓取任务就能拿到。更妙的是Prometheus在k8s里可以做服务发现。它直接调用Kubernetes API动态获取节点、Pod、Service、Endpoint的最新列表。新起的Pod自动进入监控范围删掉的Pod自动从监控目标里消失。这就像你不需要手动更新通讯录Prometheus自己会去派出所Kubernetes API调最新户籍。这套机制和k8s的声明式理念天然契合。再看数据模型。Prometheus把每条时间序列组织成“指标名 标签集合”的形式。比如http_requests_total{methodGET, endpoint/api, status200}这条序列里标签method、endpoint、status就是你多维分析的切入口。在容器环境里你可以给每个采集目标自动打上namespace、pod_name、service这样的标签然后想怎么切就怎么切按命名空间汇总CPU、按服务看错误率、按Pod看内存趋势。这种灵活性是Zabbix那类以“主机”为模型的老牌监控给不了的。对比维度传统Push模式Agent主动推Prometheus Pull模式监控目标变动需要中心端手动增删或靠Agent定期上报通过服务发现动态更新目标漂移无感对Agent的依赖Agent挂了数据就断中心端被动等数据被监控方只要暴露接口Prometheus主动拉取数据格式各家Agent格式不一定统一统一为Prometheus文本格式生态通用容器环境适配性要额外做服务注册复杂度高原生支持Kubernetes API服务发现收缩到一句话k8s里的监控对象是活蹦乱跳的Prometheus这套“主动拉取 自动发现 标签索引”的组合拳就是为这种动态场景的量身定做。这也是为什么k8s Prometheus这对组合能成为云原生时代的默认监控方案。2. 部署前的方案选型别急着抄yaml先想清楚这几件事2.1 Helm全家桶与手动yaml两条路线怎么选去网上一搜“k8s部署Prometheus”大概率会搜到两种截然不同的教程一种是让你helm install一个全家桶比如kube-prometheus-stack一条命令把Prometheus、Alertmanager、Grafana、node-exporter全部装好另一种是手动创建各种Deployment、Service、ConfigMap、RBAC每一步都写得明明白白。这两条路线选错了后面会很难受。如果你在测试环境、想快速看到效果或者你的k8s知识储备还没到“能徒手排错”的程度我强烈建议直接用Helm方案。kube-prometheus-stack这个Chart非常成熟装完之后自带一整套Prometheus Operator、默认的抓取配置、告警规则、Grafana面板你不需要理解底层原理就能看到一堆漂亮的监控图表。生产环境里绝大多数团队用的也是这套方案只是会把配置改成自己定制的版本。但我今天这篇文章核心还是讲“手动部署”路线也就是把每个组件一个个拆开用原生yaml往下撸。为什么因为我发现很多用Helm一条命令装好Prometheus的人出了问题根本不知道去哪排查——他连Prometheus的配置放在哪个ConfigMap里、RBAC权限是干什么用的都不清楚。手动部署一遍看起来费时间实际上等于把Prometheus在k8s里的每一个齿轮都摸了一遍RBAC怎么建、ServiceAccount怎么挂、ConfigMap怎么挂载、服务发现为什么需要这些API权限。这些才是真正的能力。你手动部署过一次之后再回去用Helm会清楚得多。2.2 版本选择、资源估算与存储规划版本选择这块我用一句话总结不要追新用你所在k8s版本经过验证的组合。我自己在k8s 1.28、1.29上测试时Prometheus选用2.x系列最新稳定版比如v2.53.x没问题Grafana用11.x或10.x都可以node-exporter用v1.8.xAlertmanager用v0.27.x。如果你不想一个个去试直接用kube-prometheus-stack打包验证过的镜像版本组合是最省心的这些镜像都在Docker Hub或Quay上拉取很方便。资源估算上我要提醒一句Prometheus不是个轻量级组件但也不是吃内存的怪物。它的内存占用主要取决于三个因素采集目标的个数、指标的时间序列总量、数据保留时长。一个只有几台节点的测试集群2核4G的Prometheus能跑得很轻松但一个生产集群如果采集密度拉满几十万甚至上百万条时间序列内存可能轻松吃掉8G甚至16G。我给一个初步参考你实际按集群规模动态调集群规模Prometheus资源建议数据保留建议存储建议测试环境≤5节点CPU 1核内存2G7天快照或emptyDir即可小规模生产≤20节点CPU 2核内存4G15天PVC 100GB起步中大规模生产50节点CPU 4核内存8G以上30天PVC 500GB必要时考虑分片或远程存储存储是个大坑。默认情况下Prometheus把数据写在容器本地Pod一删数据全没。生产环境必须给它挂一个PersistentVolumeClaim而且这个PVC要绑定到一个支持动态供给的StorageClass上。我的建议是即使你用local-path这类本地存储也比裸跑在Pod里强有条件上Rook-Ceph或云厂商的云盘更稳。后面我会把PV/PVC的配置一起放进部署步骤里。3. 实操一步步把Prometheus请进k8s集群3.1 第一步创建命名空间与RBAC权限Prometheus要调用Kubernetes API做服务发现比如拉取节点列表、Pod列表、Endpoint列表这需要给它足够的权限。你用一个普通ServiceAccount去跑Prometheus会发现服务发现配置已经写了但抓不到任何目标这就是RBAC权限没跟上。我先创建一个独立的命名空间叫monitoring所有监控组件都放这里和业务隔离方便管理。apiVersion: v1 kind: Namespace metadata: name: monitoring接下来创建ServiceAccount和RBAC授权。这里要用ClusterRole因为Prometheus要发现的是整个集群的资源不管是哪个命名空间的Pod、Service还是要看的。apiVersion: v1 kind: ServiceAccount metadata: name: prometheus namespace: monitoring --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: [] resources: - nodes - nodes/metrics - services - endpoints - pods verbs: [get, list, watch] - apiGroups: [networking.k8s.io] resources: - ingresses verbs: [get, list, watch] - apiGroups: [discovery.k8s.io] resources: - endpointslices verbs: [get, list, watch] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: monitoring注意看resources里列出的nodes、services、endpoints、pods。这四项是服务发现最底层的权限缺一个都可能导致对应类型的发现目标失败。比如只给pods权限不给services权限那么Service粒度的监控目标就永远发现不了。endpointslices这个资源是较新版本才需要的如果k8s版本老一些可以不加但新版集群建议加上。3.2 第二步配置服务发现与抓取任务这是整个部署过程中最核心的一步。Prometheus通过一个ConfigMap来保存抓取配置里面写清楚“从哪里发现目标”以及“抓哪些指标”。先看一个基础配置抓k8s节点、Pod和容器apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config namespace: monitoring data: prometheus.yml: | global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: # 1. 抓取节点指标 - job_name: kubernetes-nodes kubernetes_sd_configs: - role: node relabel_configs: - source_labels: [__address__] regex: (.*):10250 target_label: __address__ replacement: ${1}:9100 scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token # 2. 抓取容器指标cAdvisor由kubelet暴露 - job_name: kubernetes-cadvisor kubernetes_sd_configs: - role: node relabel_configs: - source_labels: [__address__] regex: (.*):10250 target_label: __address__ replacement: ${1}:10250 scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token metrics_path: /metrics/cadvisor这里有几个关键点我展开说一下新手最容易在这里卡住。第一kubernetes_sd_configs的role决定从哪里发现目标。role: node是发现集群所有节点role: pod是发现所有Podrole: service是发现所有Servicerole: endpoints是发现所有Endpoint。你监控什么类型的目标就用对应的role。第二抓节点指标时为什么Prometheus拉的是节点的9100端口node_exporter而服务发现出来的是10250端口因为k8s服务发现看到的是节点上kubelet的地址默认就是10250但你真正想抓的是node_exporter暴露的9100。所以我用relabel_configs把__address__从10250改写成了9100。这就是个“偷天换日”的小技巧很多教程不讲透。第三请求kubelet要以HTTPS协议访问并且要身份认证。Prometheus容器里会自动挂载ServiceAccount的token和CA证书所以配置里直接引用/var/run/secrets/kubernetes.io/serviceaccount/下面的文件就行。前面我们创建了ClusterRoleBinding就是给这里用的。3.3 第三步用Deployment把Prometheus跑起来配置写好了接下来就是把Prometheus跑起来。这里我用Deployment来管理挂载前面创建的ConfigMap同时给它准备一个PVC作为数据存储。apiVersion: v1 kind: PersistentVolumeClaim metadata: name: prometheus-data namespace: monitoring spec: accessModes: - ReadWriteOnce resources: requests: storage: 50Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: prometheus namespace: monitoring labels: app: prometheus spec: replicas: 1 selector: matchLabels: app: prometheus template: metadata: labels: app: prometheus spec: serviceAccountName: prometheus containers: - name: prometheus image: prom/prometheus:v2.53.1 args: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus - --storage.tsdb.retention.size40GB - --storage.tsdb.retention.time15d ports: - containerPort: 9090 name: http volumeMounts: - name: config mountPath: /etc/prometheus - name: data mountPath: /prometheus resources: requests: memory: 2Gi cpu: 500m limits: memory: 4Gi cpu: 2 volumes: - name: config configMap: name: prometheus-config - name: data persistentVolumeClaim: claimName: prometheus-data这里我要特别解释两个参数--storage.tsdb.retention.size和--storage.tsdb.retention.time。前者表示数据最多占多少磁盘容量后者表示数据最多保留多长时间。两个可以同时生效哪个先到就按哪个来。我以前图省事只设retention.time结果有一天发现PVC被打满了Prometheus直接没法写入。后来学乖了两个参数都设size留出Pvc的80%~90%防止磁盘撑爆。Prometheus的--storage.tsdb.path数据目录我挂到了PVC上。注意Prometheus的TSDB引擎写入的文件包括block、WAL、chunks这些删Pod再重建后能从磁盘上恢复历史数据不会丢。这是k8s部署Prometheus和直接用emptyDir的本质区别。依赖CA证书Prometheus容器默认不会自动挂载ServiceAccount的CA和token吗会挂载但路径是/var/run/secrets/kubernetes.io/serviceaccount/所以上面的配置中使用这个路径是没问题的。然后是暴露ServiceapiVersion: v1 kind: Service metadata: name: prometheus namespace: monitoring spec: selector: app: prometheus ports: - port: 9090 targetPort: 9090访问方式看你的集群环境。测试环境我喜欢用kubectl port-forward -n monitoring svc/prometheus 9090:9090直接本地浏览器访问。生产环境就配Ingress或者用LoadBalancer类型的Service但注意Prometheus的UI一般不透出给公网内部使用就够了。3.4 第四步接入Grafana把数据变成看得见的图Prometheus自带的UI能查指标但画图能力和Grafana没法比。生产环境几乎都是Prometheus做数据存储Grafana做可视化。Grafana部署起来非常简单我这边直接写个最简配置apiVersion: apps/v1 kind: Deployment metadata: name: grafana namespace: monitoring spec: replicas: 1 selector: matchLabels: app: grafana template: metadata: labels: app: grafana spec: containers: - name: grafana image: grafana/grafana:11.1.0 ports: - containerPort: 3000 env: - name: GF_SECURITY_ADMIN_USER value: admin - name: GF_SECURITY_ADMIN_PASSWORD value: admin123 volumeMounts: - name: data mountPath: /var/lib/grafana volumes: - name: data emptyDir: {}Grafana的数据目录我也建议用PVC否则面板配置一重启就没了。不过这里为了避免文章过长你先用emptyDir跑通后面记得改。启动之后浏览器访问http://localhost:3000用kubectl port-forward -n monitoring svc/grafana 3000:3000默认账号是admin/admin123。进去之后左侧齿轮进入Data Sources添加Prometheus数据源URL填http://prometheus:9090——注意这里要用Service的DNS名字因为Grafana和Prometheus在同一个命名空间可以直接用服务名访问。数据源加好之后你可以在Import面板的ID库里搜索315或1860等经典面板编号一键导入。不过从Grafana 8.0以后面板ID直接导入的方式还可用但不一定能兼容所有版本有些面板需要你手动调整数据源变量名。反正只要能查到CPU、内存这些基础指标这套链路就通了。4. 告警没有告警的监控就是“事后诸葛亮”4.1 告警规则怎么写怎么配监控不接告警等于是把数据记录了但没人看业务挂了还是没人知道。Prometheus的告警分成两步先用告警规则生成告警事件再把告警事件交给Alertmanager去通知人。告警规则写在Prometheus的配置里通常单独拆成一个rules.yml文件。给它也做成ConfigMap然后挂载到Prometheus容器中。Prometheus配置文件里要加上这样一段rule_files: - /etc/prometheus/rules.yml告警规则文件的示例groups: - name: k8s-alerts rules: - alert: PodCrashed expr: kube_pod_container_status_restarts_total 5 for: 5m labels: severity: critical annotations: summary: Pod {{ $labels.pod }} 持续重启 description: Pod {{ $labels.namespace }}/{{ $labels.pod }} 在5分钟内重启超过5次请检查。 - alert: NodeDown expr: up 0 for: 2m labels: severity: critical annotations: summary: 节点 {{ $labels.instance }} 失联 description: 节点或目标 {{ $labels.instance }} 已无法访问超过2分钟。 - alert: MemoryUsageHigh expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 90 for: 10m labels: severity: warning annotations: summary: 节点 {{ $labels.instance }} 内存使用率超过90% description: 当前内存使用率已超过90%持续10分钟。写规则有几个坑。第一个是expr里PromQL的写法你要对指标非常熟悉。比如up 0表示Prometheus抓目标失败这个规则能抓到节点、Service所有失联目标是个很通用的保底规则。第二个是for参数它表示条件持续多长时间才触发告警这能有效过滤瞬时抖动避免误报。第三个是labels和annotationslabels会附加在告警事件上后面Alertmanager可以根据severity做分级通知annotations则是给人看的描述信息。4.2 Alertmanager配置与通知渠道Alertmanager负责接收Prometheus推过来的告警去重、分组、抑制后通过邮件、钉钉、企业微信、Webhook等方式通知。部署Alertmanager和部署Prometheus大同小异这里我只给出最关键的部分。首先准备Alertmanager的配置apiVersion: v1 kind: ConfigMap metadata: name: alertmanager-config namespace: monitoring data: alertmanager.yml: | route: group_by: [alertname, cluster] group_wait: 10s group_interval: 10s repeat_interval: 1h receiver: webhook receivers: - name: webhook webhook_configs: - url: http://your-webhook-service:8080/alert send_resolved: true然后创建Alertmanager的Deployment和Service。由于和Prometheus基本一样这里不重复贴完整yaml只提几个必须注意的点Alertmanager的默认端口是9093它的配置文件路径是/etc/alertmanager/alertmanager.yml同样要用ConfigMap挂载。Prometheus侧要告诉它“告警发给谁”在prometheus.yml里加alerting: alertmanagers: - static_configs: - targets: [alertmanager:9093]改完之后你需要重启Prometheus让配置生效。怎么安全地改配置我又要啰嗦一遍把新ConfigMap apply上去然后kubectl rollout restart deployment/prometheus但注意Prometheus是单副本会有几秒的不可用窗口。如果有条件多副本挂载PVC会让这个操作更平滑但Prometheus本身不支持多实例共享相同TSDB一般仍然用单副本重启几下无妨。接邮件通知的话Alertmanager配置里可以加smtp配置receivers: - name: email email_configs: - to: opsexample.com from: alertexample.com smarthost: smtp.example.com:465 auth_username: alertexample.com auth_password: your-password send_resolved: true邮件是目前最普及的通知方式不过现在很多团队都用钉钉或企微机器人Webhook原理都一样Alertmanager把告警JSONPOST给你的Webhook服务你的服务再转成对应平台的消息格式。这个后面可以单独做一套这里先不展开。5. 问题排查与实用避坑清单5.1 常见故障速查表我在不同集群里部署Prometheus踩过的坑多了这里整理成一张表你对照着排查会快很多。症状可能原因解决办法Prometheus Pod一直ContainerCreatingPVC没有绑定StorageClass或绑定失败检查PVC事件确认StorageClass存在且可用Prometheus Pod CrashLoopBackOffprometheus.yml配置语法错误先kubectl logs看日志本地用promtool check config检查配置Targets页面没有任何节点/PodRBAC权限缺失服务发现拿不到列表检查ClusterRoleBinding是否创建ServiceAccount是否正确挂载抓取报错context deadline exceeded网络不通或抓取超时检查Pod网络策略、Service域名解析适当增加scrape_timeout抓取报错401 Unauthorized认证token失效或未挂载确认Prometheus容器里能读到/var/run/secrets/kubernetes.io/serviceaccount/tokenPrometheus内存持续上涨时间序列基数过大或采集目标过多检查高基数标签适当降低采集频率限制存储保留时间服务发现到目标但指标全是0relabel配置和实际端口不匹配到Targets页面查看__address__是否被改成了正确的端口Grafana面板加载数据但图是空的数据源变量或指标名不对切到Explore模式手动跑一次PromQL验证指标是否存在这里我要单独强调promtool这个工具。它是Prometheus官方自带的配置检查工具用它对prometheus.yml做一次校验能省掉你大量试错时间。在Prometheus容器里可以这样执行kubectl exec -it -n monitoring deployment/prometheus -- promtool check config /etc/prometheus/prometheus.yml如果配置有问题它会直接提示你哪一行哪个字段写错了非常直观。我在本地改配置时习惯先用容器里的promtool校验一遍再重启几乎不会把配置错误带到线上。5.2 存储、性能与长期落地建议Prometheus用久了数据量增长是必然的。我见过一台测试集群刚部署两周TSDB数据就吃掉20GB细看发现是某个服务把kube_*指标全量采集了高基数标签爆炸。这种问题的本质是采集范围没管住。控制数据量我给你三个方向。第一按需抓取不是所有Pod都要纳入监控通过relabel配置只抓带有prometheus.io/scrape: true注解的Pod能大幅度减少无效采集。第二降低采样频率如果不是对秒级数据有强需求scrape_interval从15s改成30s数据量直接减半。第三用metric_relabel_configs丢弃不需要的指标比如你根本不关心某个标签的值就把它drop掉别让它撑大基数。关于长期存储Prometheus默认的TSDB只适合在单机上保留一段时间。如果你要跨天、跨月做趋势分析本地磁盘迟早不够。社区的流行方案有Thanos、VictoriaMetrics、Mimir这些它们可以对接远程读写存储让Prometheus只负责采集和告警计算历史数据放到对象存储或其他后端。等到你的数据量真的到了那个级别这些迁移方案再去做也不迟前期先把基础监控链路跑稳更重要。另外我强烈建议如果你用的是Helm部署的kube-prometheus-stack官网它自带的Grafana面板和告警规则是经过大量实践验证的。你不需要自己从零开始写一堆告警规则直接用官方默认的再结合自己业务裁剪就行。这也是我为什么说手动部署一遍是为了学原理生产环境要直接用成熟的Chart两手都要抓。写在最后的实操体会我个人在多次部署过程中感受最深的一点是Prometheus本身并不难难的是想清楚你要监控什么、怎么定义采集边界、告警怎么分级、数据保留多久。如果你一上来就把所有目标全量采集把Grafana面板装了一堆告警规则写了上百条最后只会得到一堆永远没人看的图和一批天天误报的告警。我现在的做法是先从节点、容器、核心中间件这三个维度做起指标越少越好告警越准越好然后随着对业务的深入理解再逐步丰富指标和面板。这套从简到繁的思路比一开始就追求大而全要务实得多。部署过程中还有个细节想提一下每次修改Prometheus的配置我都会先在本地用promtool校验再apply到集群然后观察Targets页面是否全部Up。凡是跳过校验直接apply的十有八九后面都在花时间排错。如果你现在正准备搭一套建议把这一条当成铁律。等这套Prometheus跑稳了后续要加Grafana面板、加告警渠道、接业务自定义指标都会顺手很多。