1. 为什么要在K8s里认真规划监控告警Prometheus为什么是首选K8s可视化监控告警这个系列我计划从最底层的Prometheus部署讲起。原因很简单不管你想在集群里监控什么东西最终都绕不开指标数据的采集、存储和查询这一步不扎实后面Grafana画得再漂亮、告警规则写得再花哨都是在沙地上盖楼。先说说K8s的监控到底特殊在哪。传统物理机或虚拟机时代一台服务器的IP是固定的进程怎么启、端口怎么配清清楚楚写进运维手册。但K8s世界里Pod是随时可以漂移的节点会扩缩容服务实例数会从3个变到30个连Pod的IP都是临时分配的。你按传统思路写死一个IP去抓监控用不了几个小时就抓空了。Prometheus恰恰是被设计出来解决这个问题的它不依赖固定的目标地址而是通过服务发现机制动态地找到“现在该采集什么”。Prometheus在云原生领域的地位可以类比成日志领域里的ELK生态。它在2016年加入云原生计算基金会后很快成了Kubernetes监控的事实标准。这不是大家跟风而是它的数据模型和K8s的标签体系配合得太默契了。K8s里一切资源都带标签Prometheus的指标也允许带任意多标签两个系统都以标签为核心组织信息天然对得上。你可以在PromQL查询里像一个维度自由地用pod_name、namespace、instance这些标签去切分、聚合数据这在监控容器化环境时是刚需。写这篇系列文章之前我重新在模拟项目X里完整走了一遍部署流程。模拟项目X是个典型的微服务集群三个节点接近二十个命名空间线上服务的Pod数量在五六十个这个量级。我特意没让团队用云厂商的一体化监控平台目的就是验证自建Prometheus在这类中等等级的集群里到底要投入多少精力又能在告警上做到什么精细度。整个流程走下来我的结论是先部署Prometheus核心再逐步接入告警和可视化这个路线是对的。2. 部署前的组件规划一个完整的监控体系必须有这五类角色2.1 每个组件解决什么问题很多新手拿到Prometheus就直接往集群里压一个Deployment配上Service就开干。这样固然能跑起来但你在K8s环境里监控的不是一台机器而是一个动态系统。一个完整的Prometheus部署至少需要处理五类数据来源各有各的采集方式各有各的坑。第一类是节点层面的指标也就是CPU、内存、磁盘、网络这类宿主机数据。容器再抽象它们最终跑在节点上节点挂了就是真挂了。这部分用Node Exporter采集以DaemonSet方式部署保证每个节点有一个采集实例。第二类是K8s集群自身状态比如Pod数量、Deployment副本数、Node状态、容器重启次数、PVC使用率这些数据需要kube-state-metrics这个组件从Kubernetes API里拉取并转换成指标格式。第三类是容器运行时指标比如每个容器的CPU使用、内存驻留这部分通过kubelet内置的cAdvisor暴露出来Prometheus直接去kubelet的/metrics接口采集就行不需要额外部署。第四类是应用自定义指标比如请求延迟、队列深度、业务处理量这类靠业务方在应用里暴露/metrics端点。第五类是短生命周期任务的指标比如批处理Job跑完就没了这种场景需要Pushgateway暂存指标否则还没等采集任务已经退出数据全丢。我的建议是第一轮部署先把前四类齐活Pushgateway是可选组件没有批处理场景可以先不装。多一个组件多一个维护点Pushgateway本身还被不少人诟病容易掩盖“目标已经下线”的信号拖到后面真有需求再说。2.2 部署方式的取舍Helm Charts和手工YAML怎么选部署Prometheus两条路一条是用Helm Chart一键拉起完整生态另一条是手写几十个YAML文件逐步部署单个组件。我分别试过两种方式说下真实的体感。用Helm的kube-prometheus-stack一条helm install下去Prometheus、Alertmanager、Grafana、各种Exporter全部就位还附带了一整套默认的告警规则和Dashboard。这个Chart是目前社区里最完整的Prometheus部署方案没有之一。但代价是它太“重”了默认配置会拉起一堆你暂时用不到的东西而且它的升级策略比较激进大版本升级时核心CRD也跟着换如果你在上层做了定制升级一次疼一次。手工YAML部署就是老老实实写每个组件的Deployment、Service、ConfigMap、RBAC。第一次搭的时候感觉很繁琐一个接一个地apply还要自己写RBAC授权Prometheus访问K8s API。但好处是你能真正理解每个组件是怎么拼起来的出了问题排查时心里有数。而且组件少能精确控制资源占用后面改动配置也灵活。我的建议分情况如果你追求快速跑通且集群规模不大直接Helm装kube-prometheus-stack先把监控体系用起来后面再逐步切到定制化配置。如果你想长期维护、对配置有强管控欲望或者集群里有严格的安全策略需要逐个审批资源那手工部署更合适。我在模拟项目X里最终选择了Helm部署核心栈但通过values.yaml做了大量裁剪等于走了一条“Helm安装手工深度定制”的中间路线这个我们后面实操部分细说。提示不管选哪种方式RBAC权限都是最容易被忽略的。Prometheus需要读取ServiceMonitor、Pod、Endpoints等资源必须给它创建ClusterRole并绑定合适的ServiceAccount。权限少了服务发现会静默失效报错还不明显。2.3 版本选择和镜像源这一步提前想好省掉后面大量麻烦选Prometheus版本时我一开始直接用了最新的2.47版本结果跟kube-state-metrics的兼容性文档一核对发现有些CRD版本的兼容矩阵很微妙。实际项目里没必要追新建议走这个组合Prometheus写入数据查询走PromQL只要你和K8s API的版本差距在一个大版本内基本没问题。更稳妥的做法是看官方文档里Prometheus Operator对Kubernetes版本的兼容表它写得很清楚照着选不会掉坑。然后是镜像源问题。国内网络环境下直接从Docker Hub拉镜像的速度能不能忍是个实际问题。尤其kube-prometheus-stack装一次要拉十几个镜像每个几十MB到几百MB不等网络抖动的话扯半天。建议提前把镜像地址改到国内可以稳定访问的镜像源。Helm方式的话在values.yaml里把每个组件的repository字段整体替换即可。我实操时是把所有默认的quay.io/prometheus前缀改成国内源整个过程大概五分钟。版本锁定这点也想提醒一句Helm部署时指定好Chart版本不要用默认的latest。锁定版本之后helm list能看清当前部署的是哪个Chart版本后续升级流程可控。我在模拟项目X里锁定的是一套经过验证的组合版本后面复现问题时同事之间沟通也方便。3. Helm部署实操记录从准备到验证的完整链路3.1 前期准备命名空间、存储和配置设计实操前先规划几样东西命名空间、存储类、持久化容量、告警通道占位。命名空间我固定在monitoring单独的命名空间有几个好处一是权限管控上可以针对这个命名空间单独设配额和策略二是卸载重装时整个命名空间清掉不会对外面的业务系统产生干扰。存储这块是容易出问题的点。Prometheus需要持久化存储历史数据但默认的storageClass如果没配好PVC调度会一直Pending起个Pod卡半天都不知道原因。我先用kubectl get storageclass确认集群里有没有可用的存储类。如果集群是云厂商托管的一般自带个standard或者managed-premium直接用就行。如果是自建集群建议提前准备好一个基于NFS或者本地盘动态供给的StorageClass这一点务必在部署前验一遍。我在模拟项目X里最初没验证结果装Prometheus时两个PVC一直Pending排查半天发现是存储类不存在白白耽误了一个小时。容量尺寸也提前估好。测试环境给20G就够模拟项目X这种几十个Pod的中等集群我给的是100G保留15天。粗算方式每个Pod平均暴露几百条指标每条指标带几个标签大约每小时产生几百MB到1GB左右的TSDB数据。等正式环境跑两周再根据实际磁盘增长速率调整保留周期和容量这样比拍脑袋准得多。3.2 values.yaml定制把默认的厚重栈裁剪成精简监控核心helm show values prometheus-community/kube-prometheus-stack values.yaml拿到默认配置之后我做的第一件事不是在原始文件上改而是复制一份自定义values然后把不用的组件关掉。为什么要裁剪默认栈会安装Grafana、Prometheus、Alertmanager、各种Exporter、还有一套默认Dashboard和告警规则。我第一期只想部署Prometheus监控核心Grafana可视化计划在系列第二篇展开Alertmanager告警再往后排。全都装上是能用但对刚入门的同学组件一多就分不清谁跟谁协作出了问题也不知道从哪排查。所以我在values里把grafana.enabled设成falsealertmanager.enabled设成false保留Prometheus Server、Node Exporter、kube-state-metrics这三样。然后改镜像仓库和存储配置。镜像仓库前缀统一替换prometheus.prometheusSpec.storageSpec里配置PVC的容量和存储类prometheus.prometheusSpec.retention设成15d。还有一点容易漏默认配置里Prometheus的resources.requests比较保守但在小集群里两个Prometheus副本外加各种Exporter加起来CPU和内存请求总和也不低。我根据节点规格把requests调小了避免高估导致调度失败。3.3 安装命令和初始化验证裁剪完成后执行安装helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install kps prometheus-community/kube-prometheus-stack -n monitoring -f my-values.yaml等大概两三分钟kubectl get pods -n monitoring看到以下状态就算成了prometheus-kps-prometheus-0 2/2 Running prometheus-kps-kube-state-metrics-xxx 1/1 Running node-exporter-abc12 1/1 Running node-exporter-def34 1/1 Running我最初留下的一个错误是安装完后不检查ServiceMonitor就被Grafana带跑了。部署完先确认CRD资源有没有被正确创建kubectl get servicemonitors -n monitoring。没有这个的话即使Prometheus跑着也什么数据都抓不到。这个问题后面专门开一节讲核心机制不搞清楚排坑会花很长时间。4. ServiceMonitor和CRDPrometheus在K8s里自动发现监控目标的秘密4.1 为什么Prometheus能“知道”要采什么Prometheus原生配置采集目标用的是scrape_configs一个列表写清楚目标地址、端口和采集路径。在K8s环境里这么写会累死人因为Pod和Service是随时变化的。K8s生态里引入了Prometheus Operator这个控制器它看管三类CRD资源Prometheus、ServiceMonitor、Alertmanager。你声明一个ServiceMonitor说“我要监控这个Service后面所有Pod”Prometheus Operator会监控这些CRD自动把采集配置翻译成Prometheus的scrape_configs写入运行中的Prometheus配置里。机制本身不复杂但理解这个机制对排错特别重要。很多人在Prometheus监控里配了ServiceMonitor但Targets页面一直显示0第一反应是ServiceMonitor写错了。其实更常见的原因是RBAC没给够Prometheus Operator没权限读取新的ServiceMonitor。所以在排Targets消失的问题时第一个动作是kubectl logs看Operator日志十个里有八个能在这步找到线索。4.2 写一个最小可用的ServiceMonitor示例模拟项目X里给某业务服务加监控时我写的第一版ServiceMonitor长这样apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: biz-app-monitor namespace: monitoring spec: selector: matchLabels: app: biz-app endpoints: - port: metrics interval: 30s path: /metrics namespaceSelector: matchNames: - biz三个关键点解释一下。第一selector.matchLabels匹配的是Service的标签不是Pod的标签。我当时以为selector直接选Pod结果Prometheus把Targets认出来但端口对不上排查半天才发现是Service里没写对应的标签。第二endpoints里的port填的是Service端口的名字不是数字端口。Service YAML里ports.name定义了它两边必须一致。第三namespaceSelector.matchNames用来指定Service所在的命名空间如果你的ServiceMonitor和Service在同一个命名空间这个可以不写但写上更保险尤其是跨命名空间采集的场景。4.3 Targets页面排查流程ServiceMonitor生效之后验证途径是Prometheus UI的Status-Targets页面看新加的Target是否处于UP状态。这一步是每次加监控的必做动作不需要依赖GrafanaPrometheus自己的界面就能确认链路通不通。如果Targets里加载了配置但状态是DOWN或者0挨个排查顺序如下看Service的selector是否匹配到PodPod是否健康端口名是否对得上Prometheus Operator日志有没有报权限错误最后才是检查网络策略和防火墙。按照这个顺序我基本能在五分钟内定位问题。5. 存储和高可用Prometheus服务自身也要靠谱5.1 单点不是终点PVC是底线Prometheus默认的--storage.tsdb.path目录写入本地磁盘Pod重建后数据就没了。在K8s里以为数据没事其实丢了个干净是非常让人头疼的。所以不管集群多小给Prometheus挂一块PVC是底线这属于基础建设项目没有讨价还价的余地。我这里统计过处理一次Prometheus Pod因为数据目录被冲掉而需要恢复的时长远大于配置PVC的时间。成功创建一个PVC之后用kubectl get pvc -n monitoring验证确认状态是Bound。如果在等待通常就是StorageClass的问题。这类问题在部署文档里很少被人专门讲但实际遇到概率极高。5.2 副本数和WAL目录要不要单独挂Prometheus官方支持以多副本方式实现基本高可用但要注意它默认的模式是每个副本各抓各的数据并不共享。如果你要求严格的写入一致性得用Thanos或者VictoriaMetrics这类方案。模拟项目X的第一期阶段我只部署了单副本加PVC数据安全先有了高可用延后到后续章节再说。WAL目录的挂载问题是进阶项。Prometheus写入的WAL和TSDB块其实都写在同一个数据目录下硬要把它们拆开挂载收益很小还增加复杂度。我看到有些团队在PV设计里给WAL单独划一块高性能盘对写入量特别大的场景可以但小集群没必要。保持简单这是第一期部署的关键心态。6. 告警规则初体验不配告警的监控等于白干6.1 从PrometheusRule开始理解告警监控部署好了不看告警这套体系的价值打了对折。Prometheus的告警规则通过PrometheusRule CRD声明规则文件本质上是PromQL表达式加一个阈值表达式一旦持续满足设定时长就触发告警。我理解PromQL规则的感觉就像学SQL刚开始记几个函数用得多了自然熟练。写一个节点磁盘告警规则示例如下apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: node-rules namespace: monitoring spec: groups: - name: node.rules rules: - alert: NodeDiskUsageHigh expr: 100 - (node_filesystem_free_bytes{fstype!~tmpfs|overlay} / node_filesystem_size_bytes{fstype!~tmpfs|overlay} * 100) 85 for: 5m labels: severity: warning annotations: summary: {{ $labels.instance }} 磁盘使用率超过85%这里for: 5m的意思是持续五分钟才触发防止瞬时的磁盘波动造成告警风暴。配合一批告警规则时我建议用一个PrometheusRule文件先封两三条最小规则确认告警链路通了再往上加不然一堆规则里分不清是规则写错还是通知没配。6.2 Alertmanager配置先留好占位告警要发出去必须经过Alertmanager。第一期我们不打算接入外部通知渠道但Alertmanager的告警路由配置可以先规划起来。简单理解告警从Prometheus产生后会推给Alertmanager由它负责分组、抑制、静默并且走各种receiver发送。留一个最小receiver配置先用webhook指向一个占位地址验证规则触发时事件能推出去。模拟项目X里最早踩过的一个坑告警规则触发了以为Prometheus UI里能看到红点就完事了实际Alertmanager没配好告警压根没发出去。所以判断告警链路通不通标准动作是Prometheus UI的Alerts页面看到规则状态为Firing再看Alertmanager UI看到同一告警最后看接收端有没有消息。三步走全才是真的通了。7. 常见问题与排查实录部署和接入阶段的高频坑从部署到接入第一套业务监控前前后后我积累了一批高频问题整理成一张速查表给各位参考现象可能原因排查路径PVC一直PendingStorageClass不存在或未指定kubectl get storageclass确认名称后改valuesPrometheus Targets空ServiceMonitor未匹配到Service核对selector和Service标签、namespaceSelectorTarget状态DOWNService端口名对不上检查Service的ports.name与endpoints.port是否一致RBAC权限不足Operator没权限读CRD查Operator日志检查ClusterRole绑定告警触发但没收到通知Alertmanager未配置或receiver为空Prometheus Alerts→Alertmanager→接收端逐段看镜像拉不动默认仓库访问慢values里改镜像仓库为可访问的国内源另外两个独家经验一是不要在命名空间里混放业务监控和Prometheus组件。ServiceMonitor放在monitoring命名空间内如果链路出问题清理时整个命名空间删掉就恢复原状不影响业务。二是要养成改完values后先生成渲染模板再apply的习惯。helm template可以把最终渲染出来的YAML落盘看一眼尤其是ServiceMonitor的selector部分经常有误写。先看再apply十条命令比反复调试省时很多。8. 实操心得监控体系建设的节奏感是慢慢磨出来的这个系列第一期的核心目标不是把整套K8s可视化监控告警体系一口气装完而是先把Prometheus这个数据底座立住。我反复在模拟项目X里验证过先把采集、存储、查询跑通再看可视化、告警通知、告警治理节奏是最顺的。反过来一上来就追求花哨的大屏和复杂的告警路由大概率会因为底层采集链路不稳排查问题时所有环节都像黑盒。最后分享一个实际小技巧。Prometheus部署完成后别急着配一堆规则先花半天时间把集群基础指标看一遍节点CPU、内存、磁盘、容器重启次数、Pod状态分布。用Prometheus UI的Graph页面跑几个最简单的PromQL比如sum(rate(container_cpu_usage_seconds_total[5m])) by (node)看到曲线有数据了等于确认从采集到查询的整条链路是通的。后面加再多的监控项都只是在这条验证过的路上修修补补风险小得多。下一期我计划把Grafana接进来把这些指标变成一张张直观的Dashboard到时候再跟大家聊Panel设计的经验和那些容易忽略的细节。