ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Prometheus+Grafana监控实战:从部署、PromQL到告警治理

Prometheus+Grafana监控实战:从部署、PromQL到告警治理 1. 从把三件套跑起来说起这套组合到底解决什么问题第一次装 Prometheus Grafana 的人十个里有八个会在同一个地方卡住——服务都起来了Grafana 数据源也加了面板上却是一条直线或者干脆 No data。这不是配置写错了而是这套监控体系和 Zabbix 那种装上就能看的思路完全不同。Prometheus 不会主动去猜你要看什么它只负责按固定周期把指标抓回来、存成时序数据剩下怎么看、看什么、什么时候报警全部交给 Grafana 和 Alertmanager。理解这个分工后面的搭建和使用就顺了。这套组合的定位很明确面向云原生、容器化、微服务场景的指标监控。它天生适合动态环境——服务副本随时扩缩、IP 随时变靠配置文件静态登记被监控目标的传统方式会很痛苦而 Prometheus 的服务发现机制正好吃这一类场景。如果你手里的是一批固定 IP 的物理机、只关心 CPU 内存磁盘和进程存活Zabbix 那套模板加自动发现其实更省事。我见过不少团队不管三七二十一全上 Prometheus结果维护成本比原来还高。适合读这篇内容的人大致有三类一是刚接手监控建设、需要从零把链路跑通的运维同学二是想在测试环境自建一套指标观测、方便排查性能问题的后端开发三是已经在用但只停留在能看面板层面想搞清楚告警规则、PromQL 和存储这些底层逻辑的人。三类人的关注点不同但都绕不开同一个路径先把数据采上来再谈查询和告警。下面我按这条主线展开中间穿插我自己踩过的坑。1.1 拉模型和推模型差别不只是方向Prometheus 最有辨识度的设计是pull拉取模型它主动按配置的间隔去 HTTP 接口抓指标而不是等被监控方把数据推过来。这个选择带来的连锁反应比想象中大。被监控的目标只需要暴露一个/metrics接口不需要知道 Prometheus 在哪、也不需要维护上报连接服务本身零侵入业务代码里只要引一个客户端库。目标挂了抓取直接失败Prometheus 通过up这个内置指标立刻知道这个实例没了不需要额外的心跳超时判断。代价是Prometheus 必须能网络直达目标。跨网段、跨隔离区、目标在 NAT 后面的场景直连就成问题这时候才需要引入 pushgateway 这类中间层把短生命周期任务的数据先缓存下来等 Prometheus 来拉。但要注意pushgateway 是个只进不出的缓存数据推到它那儿之后不会自动清理任务跑完还得自己删否则会一直残留旧值。我早期就把一个定时任务的数据推上去忘了清理结果面板上那条线永远停在最后一次的值排查了半天才想起来是 pushgateway 的锅。拉模型还有个隐性好处抓取间隔统一由 Prometheus 控制所有目标的采样节奏一致做聚合和对比时不用考虑各来源时间戳错乱的问题。推模型下每个客户端各推各的时间对齐会变成一件麻烦事。1.2 时序数据模型带来的查询自由度Prometheus 存的是时序数据每条数据由三部分组成指标名metric name、一组标签label键值对、以及带毫秒时间戳的浮点值。这种指标名 标签的结构让同一个指标可以按任意维度切片。比如http_requests_total这个计数器可以带上method、path、status、instance等标签查询时你想按哪个维度聚合就by哪个维度不需要提前建好表结构或者预设聚合粒度。这一点跟传统的关系型监控库完全不同。关系库里你看到的是一张张预先定义好列的表想加一个维度就得改 schemaPrometheus 里加维度只是多打一个标签查询时自然就能用。灵活性的代价是**基数cardinality**问题——标签取值组合太多会把内存吃光这个后面第 8 章会专门讲。还有一个容易忽略的点Prometheus 的查询语言 PromQL 是面向时间序列的不是面向行的。rate()、increase()、avg_over_time()这类函数直接作用在时间窗口上写起来比 SQL 里手动做时间分组要自然得多。但它的思维方式和 SQL 差异挺大刚上手时容易写出看着没错、结果全空的表达式第 4 章会集中讲这类问题。1.3 什么情况下这套组合并不划算说句实在话Prometheus 不是万能的。它只做指标metrics不做日志、不做链路追踪。你要看单条请求的调用链得上 SkyWalking 或 Jaeger要看错误日志上下文还是得靠日志系统。三者是互补关系不是替代关系。很多人一上来指望 Prometheus 解决所有观测问题结果发现它连这条报错的具体内容都给不了于是失望。另外几个不太适合的场景超大规模单实例千万级活跃序列以上如果不做分片和远程存储本地 TSDB 会扛不住需要长期留存原始明细数据比如审计、计费原始记录的场景Prometheus 的降采样和过期删除机制不合适对数据一致性要求极高的场景也不合适因为它是最终一致、允许少量丢点。认清边界比盲目堆工具重要得多。2. 绕不开的核心概念指标、标签与四种指标类型概念不清楚就直接抄配置文件遇到问题基本只能靠猜。这一章把 Prometheus 数据模型的几个关键点拆开讲这些是后面写配置、写查询、写告警规则的地基。我建议哪怕你现在只想赶紧跑起来也把这部分过一遍后面能省掉大量返工。2.1 一条样本到底长什么样一个完整的样本展开来是这样的http_requests_total{methodGET, path/api/user, status200, instance10.0.1.5:8080, jobuser-api} 12345 1714000000000拆开看http_requests_total是指标名花括号里全是标签12345是值最后是毫秒时间戳。指标名和标签共同构成这条序列的唯一标识不含时间戳。这里有个关键规则标签一旦写进代码就成了序列身份的一部分。同一个指标名标签组合不同就是不同的序列Prometheus 会分别存储。所以标签不是随便加点描述信息加错了会直接影响存储和查询。有两个特殊标签要记住job通常表示一组同类目标比如所有 node-exporter 归为一个 jobinstance是具体目标的地址。查询时这俩用得最多。另外标签值里出现正则特殊字符时要小心PromQL 里用~做正则匹配时没转义会导致匹配失败。提示给业务指标打标签时优先选取值有限的、你确认会用到的维度。路径里带用户 ID、订单号这种高基数的字段千万别当标签用会直接把 Prometheus 打爆。2.2 四种指标类型的选型依据Prometheus 客户端库提供四种基础类型选错类型会让后续查询变得别扭类型语义典型用法查询注意事项Counter只增不减的累计值请求总数、错误总数、字节数不能直接看绝对值要用rate()/increase()Gauge可增可减的瞬时值内存占用、温度、队列长度、并发数可直接看也可用avg_over_time()平滑Histogram分桶统计分布请求耗时、响应大小配合histogram_quantile()算分位数Summary客户端算好的分位请求耗时客户端计算分位不可聚合多个实例不能合并这里最常见的误区是把 Gauge 当 Counter 用或者反过来。比如把当前并发连接数写成 Counter服务重启后从 0 开始rate()算出来就是一个巨大的尖峰因为计数器回退了面板上会出现莫名其妙的毛刺。判断方法很简单这个数值会不会下降会下降就一定是 Gauge。Histogram 和 Summary 的选择也常让人纠结。Histogram 的分桶是在服务端聚合的可以跨实例合并算全局分位Summary 的分位是在客户端算好的多个实例的数据没法合并。所以除非你确定永远只看单实例分位否则用 Histogram。Histogram 的坑在于桶的边界bucket设计如果桶都设成了 0.1、0.5、1、5 秒而实际请求大多在 20ms 以内那么几乎所有样本都落在第一个桶里histogram_quantile()算出来的 P99 精度会非常差。桶的粒度要贴着你的实际延迟分布来设。2.3 Exporter 与 OTel Collector把别人的数据翻译过来Prometheus 自己不会知道 MySQL 慢查询有多少、Nginx 连接数是多少、主机的 CPU 用了多少。这些数据靠exporter转换exporter 是一个独立进程连上被监控对象采集原始数据再以 Prometheus 能读的/metrics格式暴露出来。主机指标用 node-exporterMySQL 用 mysqld_exporterRedis 用 redis_exporter这些都是社区维护得很成熟的组件部署方式和配置都很成熟。近几年有个新趋势越来越多的环境里数据不再由 exporter 直接暴露给 Prometheus而是先由OTel CollectorOpenTelemetry Collector统一接收、转换再由 Prometheus 从 Collector 的 exporter 端点拉取。这样做的原因是多套观测数据的采集入口可以统一不用为每种数据类型单独维护一个组件。Prometheus 从 Collector 收数据的原理还是老一套Collector 配置一个prometheusexporter把你的指标暴露在一个 HTTP 端点上Prometheus 的 scrape 配置指向这个端点即可。理解这一点配置就不会写错——不管数据从哪来最后都得变成一个能被 HTTP 抓取的 /metrics 端点。3. 单机部署实操用 Docker Compose 把三件套跑起来概念说完了动手部分来了。这一章我用 Docker Compose 的方式部署理由很直接配置文件全部落盘到宿主机目录改配置不用进容器重建服务不丢数据比二进制部署干净也比手动docker run好维护。如果你是 k8s 环境思路类似但用 Operator 更合适第 8 章会提。3.1 目录规划与文件落盘先把目录结构定下来别一会儿容器里改配置一会儿宿主机改配置最后自己都不知道哪个是生效的prom-stack/ ├── docker-compose.yml ├── prometheus/ │ ├── prometheus.yml │ └── rules/ │ └── node.rules.yml ├── alertmanager/ │ └── alertmanager.yml └── grafana/ └── provisioning/ └── datasources/ └── prometheus.yml核心原则配置通过 volume 挂载进容器数据通过具名 volume 持久化。Prometheus 的数据默认存在/prometheusGrafana 的数据在/var/lib/grafana这两个一定要挂 volume否则容器一删历史数据全没。3.2 docker-compose.yml 的完整写法version: 3.8 services: prometheus: image: prom/prometheus:v2.51.2 container_name: prometheus volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus/rules:/etc/prometheus/rules:ro - prom_data:/prometheus command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus - --storage.tsdb.retention.time15d - --storage.tsdb.retention.size50GB - --web.enable-lifecycle ports: - 9090:9090 restart: unless-stopped node-exporter: image: prom/node-exporter:v1.7.0 container_name: node-exporter pid: host volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - --path.procfs/host/proc - --path.sysfs/host/sys - --path.rootfs/rootfs - --collector.filesystem.mount-points-exclude^/(sys|proc|dev|host|etc)($$|/) ports: - 9100:9100 restart: unless-stopped alertmanager: image: prom/alertmanager:v0.27.0 container_name: alertmanager volumes: - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - am_data:/alertmanager command: - --config.file/etc/alertmanager/alertmanager.yml - --storage.path/alertmanager ports: - 9093:9093 restart: unless-stopped grafana: image: grafana/grafana:10.4.2 container_name: grafana volumes: - grafana_data:/var/lib/grafana - ./grafana/provisioning:/etc/grafana/provisioning:ro environment: - GF_SECURITY_ADMIN_PASSWORDChangeMe_2024 - GF_USERS_ALLOW_SIGN_UPfalse ports: - 3000:3000 depends_on: - prometheus restart: unless-stopped volumes: prom_data: grafana_data: am_data:几个地方值得说清楚为什么这么写。node-exporter用了pid: host和--path.rootfs/rootfs是因为容器里的/proc、/sys是容器自己的命名空间不加这些拿到的全是容器的假数据主机真实指标看不到。--collector.filesystem.mount-points-exclude这行是过滤掉一堆无意义的挂载点比如/proc、/sys不加的话文件系统指标会多出几十条噪声序列。--web.enable-lifecycle是开启配置热重载的前提改完配置后curl -X POST localhost:9090/-/reload就能生效不用重启容器。3.3 prometheus.yml 的最小可用配置global: scrape_interval: 15s evaluation_interval: 15s external_labels: cluster: demo env: test rule_files: - /etc/prometheus/rules/*.yml alerting: alertmanagers: - static_configs: - targets: [alertmanager:9093] scrape_configs: - job_name: prometheus static_configs: - targets: [localhost:9090] - job_name: node static_configs: - targets: [node-exporter:9100]scrape_interval是抓取间隔evaluation_interval是告警规则的评估间隔两者分开配置。规则评估频率不需要和抓取一样高评估太频繁只是浪费 CPU因为数据更新没那么快。external_labels在单机环境看起来没用但一旦以后做联邦或者远程写入这些标签就是区分数据来源的关键提前加上没坏处。配置写完后启动docker compose up -d docker compose ps3.4 启动后怎么确认数据真的采上来了别急着开 Grafana先在 Prometheus 自己的 Web UIhttp://IP:9090里验证。打开Status - Targets看每个 target 的 state 是不是UPLast Scrape的时间是不是在十几秒内。如果是DOWN把鼠标移到错误信息上看具体原因常见的就是connection refused目标端口不对或者服务没起、context deadline exceeded抓取超时见第 7 章。确认 UP 之后在查询框里敲up应该能看到up{instancenode-exporter:9100, jobnode}值为 1 的序列。再敲一个真实指标比如node_memory_MemAvailable_bytes应该有数据。到这一步采集链路就通了。我见过有人跳过这步直接配 Grafana面板不出图时根本分不清是采集问题还是展示问题白白多排查半小时。4. PromQL从能查出来到查得准PromQL 是这套体系里最有学习价值也最容易出问题的一环。它不难但和 SQL 的思维方式差别大几个函数的意义一旦混淆结果可能是完全错的而且还不会报错——这就是它最阴险的地方。这一章挑最常用的几个函数和最容易写错的地方讲。4.1 rate、irate、increase三个函数别用混rate()是使用频率最高的函数它计算某个 Counter 在时间窗口内的每秒平均增长率。写法是rate(http_requests_total[5m])方括号里的5m是窗口意思是取最近 5 分钟的数据来算速率。返回单位是每秒多少次跟窗口长度无关。它内部做了两件事处理计数器重置服务重启导致归零和外推把窗口边界不完整的部分按比例补全。这个外推是他一大特色也是初学者最容易困惑的地方——为什么我用 5 分钟算出来的速率和用 1 分钟算出来的不一样就是因为外推和窗口的选取不同。irate()只看窗口内最后两个采样点算瞬时速率。它对突变的反应更灵敏适合做实时抖动类的图表但毛刺多、不适合做告警因为一两个点的波动就能让它飙起来。increase()和rate()底层是同一套逻辑返回的是窗口内的总增量而不是每秒速率数值上约等于rate() × 窗口秒数。看过去 1 小时总共发生了多少次用increase()更直观看现在的速率是多少用rate()。有个约定俗成的经验值rate()的窗口至少要是抓取间隔的4 倍。15 秒抓一次窗口至少用 1 分钟想更平滑就用 5 分钟。窗口太短样本点不够外推会放大误差图像上会出现锯齿甚至尖刺。4.2 聚合、by 与 without 的用法原始指标往往是按实例、按路径拆开的要看整体就得聚合。sum、avg、max、min、count是五个最常用聚合算子配合by或without使用# 按实例汇总 CPU 使用率 100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100) # 所有实例的请求总速率 sum(rate(http_requests_total[5m])) # 按状态码分组只看非 2xx sum by(status) (rate(http_requests_total{status!~2..}[5m]))by(instance)是只保留 instance 这个标签其余丢掉再聚合without(instance)是丢掉 instance其余保留。两者结果常常一样但语义相反。我的习惯是明确知道要按哪些维度看时用by想保留大部分标签、只去掉一两个时用without。用错方向不会报错但结果会少掉或多出一堆标签做面板变量时特别容易踩。4.3 那些写错了却不报错的表达式PromQL 有一个很坑的性质查询没有匹配的数据时返回空不返回错误。所以一个表达式语法完全正确、逻辑却错了你看到的就是一片空白没有任何提示。常见的几种静默失败标签匹配写错。指标上的标签实际是status200你写成code200结果就是空。先把指标名单独查出来看看它到底有哪些标签再往上加过滤条件。这一步千万别省。时间窗口内没有样本。目标刚启动、或者抓取间隔大于窗口rate(x[1m])会因为窗口里不足两个点而无结果。把窗口放大试试。聚合方向搞反。sum by(instance)之后还想按path分组但path标签已经被by丢掉了结果只剩 instance。记住聚合会把 by 之外的标签全部丢弃后续不能再按被丢掉的标签分组。正则没锚定。{path~/api}会匹配到/api/v1、/api/user等等如果你只想要/api本身得写{path~/api$}或者直接用。还有一个排查技巧把复杂表达式一层层拆开查。比如histogram_quantile(0.99, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))返回空就先查http_request_duration_seconds_bucket有没有数据再加rate看有没有值最后套histogram_quantile。这种逐层剥离的方法比盯着完整表达式干想要快得多。5. Grafana 面板变量、面板类型与看板的组织数据在 Prometheus 里能查了接下来是把它们变成人能看懂、能快速定位问题的面板。Grafana 本身不难难的是把看板组织得清晰——很多团队的看板越做越大、越做越乱最后没人愿意看。5.1 数据源接入与变量的串联数据源配置只填一个 URLhttp://prometheus:9090容器内用服务名互通不要写 localhost那是指向 Grafana 容器自己。Access 选 Server 模式其他保持默认即可。真正让看板好用的是模板变量Variables。举个最常见的定义一个叫instance的变量类型选 Query查询语句写label_values(node_memory_MemAvailable_bytes, instance)它就会把所有上报这个指标的实例列出来作为下拉选项。然后面板里的查询用$instance引用100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle, instance$instance}[5m])) * 100)这样一来一个面板就能通过下拉切换看不同实例不用为每台机器建一个面板。变量还支持级联先定义job变量再定义instance变量时用label_values({job$job}, instance)切换 job 时 instance 选项自动跟着变。这个用法在做多环境、多集群看板时几乎是标配。5.2 面板类型与它们适合展示什么Grafana 面板类型很多但日常真正高频的就那么几种面板类型适合展示使用要点Time series随时间变化的趋势绝大多数指标用它默认首选Stat单个当前值适合放当前在线数健康状态这类概览Gauge带区间的百分比适合 CPU、内存、磁盘使用率配阈值变色Table多维度明细适合各实例各指标的横向对比Bar chart分类对比适合各接口请求量排名Heatmap分布密度配合 Histogram 看延迟分布很直观选型逻辑其实就一句话看趋势用 Time series看当前值用 Stat/Gauge看多维对比用 Table。新手最容易犯的错是拿 Time series 硬塞各实例对比——一堆线叠在一起谁也看不清这种情况换成 Table 或者配个横向 Bar chart 清楚得多。延迟分布这块特别提一下如果你用 Histogram 采集了请求耗时可以直接用 Heatmap 面板查询写sum by(le) (rate(http_request_duration_seconds_bucket[5m]))就能看到耗时在各个桶区间的密度分布哪一档变厚了一目了然比只看一个 P99 数字信息量大得多。5.3 看板怎么组织才不至于越做越乱几个我实践下来比较有效的规则第一按从全局到局部分层。第一屏放集群总览总 QPS、总错误率、总延迟、整体资源水位点进去看某个服务或某个实例的详情。别把所有东西堆在一个看板上页面越长越没人往下拉。第二同一类指标固定颜色和单位。错误率永远红色、成功率永远绿色延迟单位永远毫秒。看板多了之后统一的视觉语言能让你扫一眼就知道哪里异常不用每次去读图例。第三在面板标题里写清楚这个面板回答什么问题。叫Node CPU 使用率不如叫节点 CPU 使用率5 分钟平均超过 85% 需关注把阈值和判断标准直接写进去接手的人不用问。第四用 Row 分组。Grafana 的行Row可以折叠把基础设施、中间件、业务指标分成几个可折叠行需要看哪块展开哪块页面清爽很多。看板一旦超过 20 个面板不做分组基本没法用。6. 告警链路从规则文件到通知真正落地监控的最终目的是让人在出问题时知道。Prometheus 负责判断是否异常Alertmanager 负责决定发给谁、发几次、怎么合并。这两者的职责分清楚配置就不会乱。6.1 告警规则里的 for 到底怎么用告警规则写在规则文件里通过rule_files加载groups: - name: node.rules rules: - alert: NodeHighCPU expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100) 85 for: 10m labels: severity: warning annotations: summary: 实例 {{ $labels.instance }} CPU 使用率过高 description: 当前 CPU 使用率为 {{ $value | printf \%.2f\ }}%已持续超过 10 分钟 - alert: InstanceDown expr: up 0 for: 2m labels: severity: critical annotations: summary: 实例 {{ $labels.instance }} 无法抓取for: 10m是关键。它的意思是表达式持续为真达到 10 分钟才真正触发告警。如果不加for只要表达式在一次评估时为真就立刻报警稍微一个瞬时抖动就会把你吵醒。经验值资源类告警CPU、内存、磁盘用 10 到 15 分钟服务存活类告警用 2 到 5 分钟业务错误率类告警用 5 分钟左右。太短会误报太长会漏掉真实故障的黄金处理时间。annotations里的模板变量很有用{{ $labels.xxx }}取标签值{{ $value }}取当前值{{ $labels.instance }}是最常用的能直接告诉收到告警的人是哪台机器出问题。描述里带上具体数值和持续时间接手的人不用再自己去查。6.2 Alertmanager 的路由树与分组抑制告警触发后推给 Alertmanager由它决定通知策略global: resolve_timeout: 5m route: group_by: [alertname, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h receiver: default routes: - match: severity: critical receiver: critical continue: true inhibit_rules: - source_match: severity: critical target_match: severity: warning equal: [instance, alertname] receivers: - name: default webhook_configs: - url: http://your-webhook:8080/warning - name: critical webhook_configs: - url: http://your-webhook:8080/critical几个参数值得拆开说。group_by决定哪些告警合并成一条通知按alertname和instance分组意味着同一实例的同一类告警合成一条而不是每台机器每条规则单独发。group_wait是首次通知的等待时间给一点缓冲让同一批告警聚齐再发。group_interval是同一组新告警出现时的发送间隔。repeat_interval是持续未恢复的告警重复提醒的间隔4 小时是比较合适的值太短会被反复打扰太长又怕漏。inhibit_rules是告警风暴的第一道防线。上面这条规则的意思是当某个实例已经触发了critical级别的告警时抑制掉同一实例同一告警名的warning级别告警。这样机器直接宕机时不会同时收到CPU 高内存高服务不可达一堆告警只收最关键的那条。没有抑制规则一次批量故障能瞬间刷出几百条通知把值班的人直接淹没。6.3 通知渠道与告警治理通知渠道通过 receiver 配置可以是 webhook、邮件、即时通讯工具等。配 webhook 的好处是灵活后端自己决定怎么分发、怎么格式化。配完告警一定要做两件事验证一是手动触发一次真实告警确认从规则触发到收到通知整条链是通的别等真出事才发现某个环节配错了二是定期回看被抑制和分组的告警确认抑制规则没有把该发的告警也压掉了。我遇到过抑制规则里equal写得范围太宽把所有 warning 都压了结果一个真正需要处理的磁盘告警被吞了。告警治理还有个原则每一条告警都必须可执行。也就是说收到告警的人应该有明确的下一步动作。纯提示性的、不需要人处理的告警不该发出来。看板可以多看告警要少而准。一个团队如果每天收到几十条告警、绝大部分是看一眼就忽略的那这套告警体系其实已经失效了真正重要的那条也会被淹没。7. 踩过的坑几个反复出现且排查耗时的故障这一章全是实战。配置层面能抄的东西网上很多但真正卡人的往往是一些看起来正常、实则不对的状态。我把遇到过的、以及帮别人排查过的几个典型问题整理出来附上定位顺序。7.1 Target 显示 UP 但查询没数据这是新手最常见的困惑。Prometheus 的 Targets 页面显示 UP说明抓取成功、HTTP 请求返回了 200但查询却查不到指标。可能的原因按概率排序一是指标名或标签写错。UP 只代表这次抓取的响应状态码正常不代表返回的内容里有你想要的指标。先在 Prometheus 输入框敲{job你的job}的空指标名查询只带标签看看这个目标到底上报了哪些指标从结果里找真实名字而不是凭记忆写。二是目标刚启动不久。很多 exporter 在启动后的头几秒不会立即暴露数据或者某些指标要等第一次采集周期完成才有值。等一分钟再查。三是被 relabel 规则改掉了。如果你配了metric_relabel_configs某些标签或指标可能被丢弃或重命名了。检查配置里有没有 drop 规则误伤。四是服务返回的格式不符合规范。某些情况下 exporter 返回的文本格式有问题比如指标名里有非法字符Prometheus 会西里地把这条序列丢掉但抓取状态仍然是 UP。去 Prometheus 的日志里搜error或者invalid能找到线索。排查顺序建议先看 Targets 的报错详情再查空标签看实际有哪些指标最后看配置里的 relabel 规则。按这个顺序大部分问题三步内能定位。7.2 抓取超时与采集间隔的连锁反应context deadline exceeded这个错误几乎每个用过的人都会遇到。它的意思是 Prometheus 在scrape_timeout时间内没等到目标的响应。默认抓取超时是抓取间隔的 1/10 左右15 秒间隔对应 1.5 秒超时实际默认值是 10 秒需要显式设置但如果 exporter 本身响应就慢——比如 mysqld_exporter 在大表上做统计、或者网络延迟高——就容易超时。调大scrape_timeout能缓解但别调得超过scrape_interval否则上一次抓取还没结束下一次就开始了会出现抓取重叠。更根本的做法是让 exporter 变快比如减少它的采集频率、关掉不必要的高开销 collector或者把目标拆成多个 job 分开抓。还有一类隐蔽问题被监控服务在高负载时负责处理/metrics请求的线程被业务请求占满导致指标接口响应变慢——越忙越抓不到越抓不到越看不到问题。这种情况要么给指标接口单独留资源要么把指标采集做成非阻塞的。7.3 时间戳、时区与 Grafana 显示偏差监控数据和实际发生时间对不上是很让人头疼的一类问题尤其是跨时区团队。Prometheus 内部统一用 UTC 存时间戳Grafana 展示时会按浏览器的时区设置转换。如果 Grafana 里看到的曲线整体平移了几个小时先检查 Grafana 右上角的时区设置看是不是设成了 UTC 而不是本地时区。还有一种更隐蔽的容器内的时间没同步。生产环境里 Prometheus 所在主机、目标主机如果时间不同步NTP 没配好采集回来的数据时间戳会有偏差做跨实例聚合时会出现某台机器的数据看起来晚了几十秒的诡异现象。部署前确认所有机器的时间同步是必须的一步比事后排查省事得多。7.4 Grafana 升级后面板报 datasource was not found这个坑我在升级 Grafana 大版本时踩过。表现是所有老面板打开都报failed to upgrade legacy queries, datasource xxx was not found数据源明明还在、名字也没改。原因是 Grafana 8 以后引入了数据源的UID机制老版本的看板里存储的数据源引用是按名字匹配的新版本改成按 UID 匹配升级过程中旧引用对不上新数据源的 UID于是报错。解决办法有两个一是进数据源设置里把数据源的 UID 手动改成旧看板里引用的那个值可以在看板 JSON 里搜datasource字段看到旧 UID二是写一个导入脚本批量把看板 JSON 里的数据源引用替换成新的 UID。第二种适合看板数量多的场景。预防办法是升级前先导出所有看板的 JSON 备份升级后逐个验证别一次性全量升级生产环境的 Grafana。8. 长期运行视角存储、性能与规模扩展跑起来容易长期稳定跑下去才是考验。这一章讲三个绕不开的话题数据存多久、什么会拖垮性能、量大了怎么扩。8.1 本地 TSDB 保留策略与磁盘估算Prometheus 的数据存在本地 TSDB默认保留 15 天。保留时长通过--storage.tsdb.retention.time控制也可以用--storage.tsdb.retention.size按磁盘容量控制两者都设的话谁先触发谁生效。磁盘用量可以粗算公式是每秒新增样本数 × 保留秒数 × 每样本字节数Prometheus 压缩后的每样本大约占用 1 到 2 字节取 1.3 做估算比较稳妥。举个例子活跃序列有 200 万条抓取间隔 15 秒保留 15 天。每秒样本数 2000000 ÷ 15 ≈ 133333保留秒数 15 × 86400 1296000总样本数 ≈ 133333 × 1296000 ≈ 1.73 × 10¹¹磁盘占用 ≈ 1.73 × 10¹¹ × 1.3 字节 ≈ 225 GB这个数字只是估算实际还受标签长度、chunk 压缩率、WAL 和 compaction 临时空间影响通常要在此基础上留 30% 到 50% 的余量。别把磁盘用到 90% 再想扩容TSDB 满了会拒绝写入数据就断了。提示把 Prometheus 的数据目录和日志目录放在同一块盘上、而且盘还很满是很多小规模部署翻车的直接原因。监控盘最好独立。8.2 高基数标签为什么是性能杀手Prometheus 的内存开销和活跃序列数成正比。所谓基数就是一个指标下所有标签取值组合的数量。前面提过路径里带用户 ID 这种标签会爆炸这里说说它到底怎么炸的。http_requests_total{path/user/12345}和http_requests_total{path/user/67890}在 Prometheus 眼里是两条完全不同的序列各自占用独立的内存和索引。如果每天有十万个用户访问光这一个指标就能产生十万条序列再加上 method、status 的笛卡尔积轻松上百万。内存被吃掉之后查询变慢、重启后 WAL 回放变久、甚至 OOM。识别高基数的方法在 Prometheus 查询框里敲topk(10, count by (__name__)({__name__~.}))看哪个指标名下的序列最多再对可疑指标敲count by (某个标签) (指标名)看是不是某个标签取值特别多。发现高基数标签后要么在业务侧改掉不要用 ID 做标签要么在采集侧用metric_relabel_configs的drop或labeldrop把它去掉。后者是应急手段前者才是根治。8.3 联邦与远程写入的适用边界单实例 Prometheus 撑不住的时候两条主流扩展路径一是联邦federation。分层部署每个子集群跑一个 Prometheus 采集本地数据上层 Prometheus 从下层 Prometheus 的/federate端点定期拉取聚合后的指标。适合多集群汇总到统一看板的场景但要注意联邦拉取的是聚合后的结果明细数据在上层是没有的别指望在上层查具体实例的原始指标。二是远程写入remote write。把数据实时写到远端存储比如各类兼容 Prometheus 的时序数据库本地只保留短期数据。适合数据量很大、需要长期留存或者需要多个 Prometheus 汇总查询的场景。远程写入对网络和远端存储的稳定性要求高写失败时本地会有积压配置里要设好重试和队列参数。选择逻辑只想统一看板、不需要长期明细用联邦简单要长期存储、要全局查询、要跨集群聚合用远程写入。两者也可以组合使用。8.4 Kubernetes 环境下的部署差异如果在 k8s 集群里部署直接照搬 docker-compose 那一套会很别扭因为 Pod 的 IP 随重建而变静态配置文件根本维护不过来。k8s 环境的正确姿势是用Prometheus Operator通过 CRD比如ServiceMonitor、PodMonitor、PrometheusRule来声明监控哪些服务、用什么规则Operator 自己负责生成配置、更新 Prometheus、重载规则。Pod 的自动发现由 Operator 处理服务扩缩容时监控目标会自动跟着变。几个 Operator 环境的注意点一是ServiceMonitor的selector要能匹配到目标 Service 的标签标签对不上是Pod 明明在跑但没被监控的最常见原因二是命名空间选择器namespaceSelector默认只监控自己所在命名空间想监控全集群得显式配置三是资源配额resources quota要给够Prometheus 的内存和 CPU 都吃得不轻配额太低会被 OOMKill。这套东西比手写配置文件抽象了一层配置写错时的报错也不那么直观建议先用一个测试命名空间跑通再铺开。我个人在这套体系上最大的体会是搭建只占两成工作量剩下的八成在数据治理和告警打磨。指标怎么命名、标签怎么打、告警阈值定多少、哪些该抑制——这些没有标准答案只能在自己的业务里一点点调。一开始别追求大而全的看板和规则先把核心几个指标采准、把最容易出问题的两三条告警配好、确保它能真正叫醒人剩下的按需慢慢加。这套东西的好处是随时可以拆开重配不喜欢的规则删了就是容错空间比想象中大。
返回列表