1. 规模上来之后,Prometheus 和 Grafana 为什么是必选项
先说个我自己的真实经历。早几年我们内部环境也就几十台虚机,监控用脚本轮询加 Zabbix 勉强能应付。后来容器化铺开,业务实例从几十个涨到几百个,虚机也从两位数跨到了三位数,原来的方案开始频繁出幺蛾子:新增容器要手工往监控配置里加主机、采集任务一多数据库锁表、告警风暴一来运维群直接刷屏。后来在Rocky Linux 8.7上整套迁到Prometheus + Grafana,花了大概一个迭代把几百个容器的 CPU、内存、网络、磁盘、JVM 全部纳管,之后新容器上线基本是零干预自动出现。这篇文章就把这套落地过程里我认为最值得参考的部分写出来,包括部署姿势、服务发现、看板设计和规模上去之后的调优经验。
这套方案解决的核心问题,简单说就是:大规模容器环境下,指标怎么自动采、怎么统一存、怎么可视化、怎么在出问题之前看出征兆。适合正在用 Rocky Linux / CentOS / AlmaLinux 跑容器,并且监控方案开始跟不上规模的运维和开发同学参考。基础的概念比如 Prometheus 是什么、Grafana 是什么我就不铺开讲了,直接聊落地。
1.1 我们之前那套监控,到底哪里撑不住了
先拆一下旧方案的痛点,不然你不好理解后面每个设计决策的动机。
首先是自动发现能力缺失。容器和虚机有个本质区别:虚机的生命周期以月为单位,容器的生命周期以分钟为单位。扩缩容、发布回滚、故障重启,都会导致监控对象的 IP 和实例名频繁变化。传统监控喜欢让运维手工添加主机,这在容器场景里就是灾难——你还没加完,一批容器已经换了两轮。
其次是指标模型太死板。传统监控通常以“主机、服务”为维度存数据,查询的时候只能按固定维度切。而容器问题往往需要多维度交叉分析,比如“某个应用的所有副本里,哪个节点的 CPU 使用率异常”“某个宿主机上的容器内存总和有没有超限”。没有灵活的标签体系,这种查询要么做不了,要么得写一堆脚本硬算。
最后是存储和查询的性能瓶颈。监控数据的写入是持续且高频的,采集频率高、指标维度多之后,传统关系型数据库的写入压力会非常大,查询复杂一点就超时。我们当时的库一天写入几千万条记录后,页面加载都成问题。
如果你现在规模不大,十几台机器,那 Agro监控凑合一下没毛病。但只要你计划容器化铺开,或者已经在铺,监控方案早晚要换,不如一步到位。
1.2 Pull 模型和标签体系:Prometheus 最值钱的两个设计
Prometheus 最核心的设计是Pull 模型:不是被监控对象主动推数据,而是 Prometheus 定期去抓取指标。这个设计一开始看着反直觉,用久了才发现全是好处。
Pull 模型解决了两个实际问题。第一,新增监控对象零配置:只要目标暴露了指标接口,Prometheus 配置好服务发现规则,就能自动拉取。第二,故障判定更可靠:被监控方挂了就是“抓不到数据”,Prometheus 可以基于“目标不可达”直接产生 Up 告警,不需要在被监控端部署 Agent 去判断上报。
再说标签体系。Prometheus 每个指标可以带任意多个 label,比如container_cpu_usage_seconds_total{namespace="prod",pod="api-server-7b8c9d",container="java-app"}。这相当于给每一条时序数据打了多个维度的索引。查询时你可以任意组合标签做聚合、分组、过滤,这是传统监控完全做不到的灵活度。
Grafana 的作用则是把 Prometheus 里这些多维数据变成看得懂的图和看板。它本身不存数据,只负责查询和展示,好处是你可以在一个 Grafana 里同时接 Prometheus、Loki、Elasticsearch、MySQL 等多个数据源,把指标、日志、业务数据放在一个视图里对照排查。
1.3 这套组合在整个监控链路里的位置
为了不让后面内容看得一头雾水,先给一个整体架构图景,不画图,就用文字描述:
- 采集层:在每台宿主机上跑 node_exporter 采集系统指标;用 cAdvisor(容器内或 DaemonSet 方式)采集容器运行指标;Java 应用用 jmx_exporter 暴露 JVM 指标;如果你的容器平台是 Kubernetes,那 kube-state-metrics、metrics-server 这些也是数据来源。
- 聚合层:Prometheus 通过服务发现自动找到上面这些 exporter,定期抓取指标,存在自己的 TSDB 时序数据库里,同时执行告警规则。
- 可视化层:Grafana 从 Prometheus 查询数据,渲染成 Dashboard,配置告警渠道。
- 告警层:Prometheus 内置的 Alertmanager 负责把告警做分组、去重、路由,推到钉钉、企业微信、邮件等渠道。
这个链路里 Prometheus 是绝对核心,Grafana 是好用的脸面,二者结合之后,你在一个界面上就能看到几百个容器的资源水位和异常趋势。
2. Rocky Linux 8.7 上的环境准备:先把容易翻车的三件事做掉
Rocky Linux 8.7 作为 CentOS 的替代品,部署 Prometheus 和 Grafana 本身不难,难的是环境细节。我见过很多人在部署阶段没翻车,跑起来之后莫名其妙丢数据、告警不触发,回头查才发现是最开始的环境没做对。这里把我认为最重要的三件事单独拉出来说。
2.1 静态 IP 与时间同步:监控数据失真的第一来源
监控系统对时间极度敏感。如果被监控机器和 Prometheus 服务器时间不一致,会出现两类问题:一类是数据点时间戳错乱,查询区间根本对不上;另一类是告警规则判断出错,比如基于时间窗口的for子句会失效。
在 Rocky Linux 8.7 上配置静态 IP,我建议直接用nmcli,不要再去改/etc/sysconfig/network-scripts/下面那套旧文件。8.x 系列默认用 NetworkManager 管理网络,直接改配置文件很容易被 NetworkManager 覆盖,甚至重启后丢失。一个典型的静态 IP 配置流程如下:
# 查看当前连接名,一般是 ens160 之类 nmcli connection show # 修改为静态地址 nmcli connection modify ens160 ipv4.method manual \ ipv4.addresses 192.168.10.10/24 \ ipv4.gateway 192.168.10.1 \ ipv4.dns 192.168.10.2 # 重启连接生效 nmcli connection up ens160时间同步方面,Rocky Linux 8.7 默认装的是 chrony,你要确保它真的在跑,并且指向一个可靠的时间源:
systemctl enable --now chronyd chronyc sources -v如果chronyc sources看到的源前面是^*说明已同步,如果是^?说明有问题。监控服务器和所有被监控宿主机都要同步,最好在部署的初始化脚本里统一带上去,不要手工一台台敲。
注意:Prometheus 的
scrape_timeout、告警规则里的for子句、Grafana 图表的时间范围都依赖时钟同步。这个问题排查起来非常隐蔽,一开始就该做掉。
2.2 firewalld 和 SELinux 对 exporter 端口的默认狙击
Rocky Linux 8.7 默认开了 firewalld,SELinux 也是 enforcing 状态。这意味着你部署好 Prometheus、node_exporter、cAdvisor 之后,从别的机器访问端口大概率是通不了的。
防火墙放行端口是刻板印象里的第一步,但很多人会漏掉一件事:Prometheus 抓取目标时,目标机器上的 firewalld 要放行 exporter 端口,而 Prometheus 自己所在机器的 firewalld 只需要放行 9090(Grafana 是 3000)。如果 Prometheus 和 exporter 在同一台机器,那 localhost 访问不受防火墙限制,一般不需要特殊处理。
# 在每一台跑 exporter 的宿主机上执行 firewall-cmd --permanent --add-port=9100/tcp # node_exporter firewall-cmd --permanent --add-port=8080/tcp # cAdvisor 按需 firewall-cmd --reloadSELinux 这块我一般建议保持 enforcing,而不是图省事直接关掉。node_exporter、cAdvisor 这些开源组件通常很小,不需要特殊 SELinux 策略。但如果你用 systemd 托管 exporter 并且发现端口绑定失败,可以用ausearch -m avc查一下是不是 SELinux 拦截,再针对性放行。setenforce 0只适合临时验证,不建议写进生产环境的初始化脚本。
2.3 目录规划与服务账号:逃不掉的“磁盘写满”问题
Prometheus 的数据是持续写入磁盘的,时间序列数据库的膨胀速度比你想的快得多。在单机 Demo 里你可能觉得无所谓,几百 MB 而已,但在大规模环境下,Prometheus 的磁盘空间规划必须一开始就决定好。
我习惯的目录规划是:
/data/prometheus/ # TSDB 数据目录 /data/prometheus/rules/ # 告警规则文件 /data/prometheus/targets/ # 静态目标与服务发现配置 /opt/prometheus/ # 二进制文件 /var/log/prometheus/ # 日志服务账号方面,不要用 root 跑 Prometheus。创建一个专用账号,数据目录属主给它:
useradd --no-create-home --shell /sbin/nologin prometheus mkdir -p /data/prometheus /opt/prometheus chown -R prometheus:prometheus /data/prometheus /opt/prometheus这一步不光是安全考量,也是为后续 systemd 托管做准备。User=prometheus指定后,如果数据目录权限不对,Prometheus 启动会直接报错,早点排查掉比跑起来之后再处理舒服得多。另外,给/data分区单独挂块盘或者至少提前看下df -h,别让它和根分区抢空间。
3. Prometheus 落地部署:从单机抓取到容器自动发现
环境准备好之后,进入正题。这里我选择用二进制包 + systemd的方式部署 Prometheus,而不是 Docker。原因后面详说,先讲部署流程。
3.1 二进制部署 + systemd 托管,为什么我不用容器跑监控本身
在 Rocky Linux 8.7 上部署 Prometheus,官方提供了二进制包,解压即用。很多教程会让你用 Docker 跑 Prometheus,但我个人在监控系统落地时更倾向二进制部署,理由如下:
- 监控系统不应该依赖被监控对象。如果用 Docker 部署 Prometheus,而 Docker 服务挂了,监控也一起挂,那你连“Docker 挂了”这个事实都无法及时发现。
- 升级和回滚更可控。Prometheus 升级就换一个二进制文件,systemd 托管下 restart 一下就完事,比容器镜像管理直接。
- 故障排查路径更短。日志直接在
/var/log,端口直接ss -lntp能看到 PID,不需要进容器、看容器日志、再绕一层。
当然,如果你的监控团队对容器化运维更熟练,用 Docker 也完全可行,只是我这里给的是更保守的选择。
安装步骤分四步走:
# 1. 下载并解压(以 2.53.0 为例) cd /opt wget https://github.com/prometheus/prometheus/releases/download/v2.53.0/prometheus-2.53.0.linux-amd64.tar.gz tar xzf prometheus-2.53.0.linux-amd64.tar.gz mv prometheus-2.53.0.linux-amd64/* /opt/prometheus/ # 2. 修改属主 chown -R prometheus:prometheus /opt/prometheus # 3. 创建 systemd unit cat > /etc/systemd/system/prometheus.service <<'EOF' [Unit] Description=Prometheus Server After=network-online.target Wants=network-online.target [Service] Type=simple User=prometheus Group=prometheus ExecStart=/opt/prometheus/prometheus \ --config.file=/data/prometheus/prometheus.yml \ --storage.tsdb.path=/data/prometheus/tsdb \ --storage.tsdb.retention.time=30d \ --web.listen-address=0.0.0.0:9090 \ --web.enable-lifecycle Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF # 4. 重载并启动 systemctl daemon-reload systemctl enable --now prometheus--web.enable-lifecycle这个参数很重要,它允许你通过curl -X POST http://localhost:9090/-/reload热加载配置,不用重启进程。在大规模环境下,重启 Prometheus 要重新加载几百万条时序数据,耗时很长,热加载能省大量时间。
配置目录我放在/data/prometheus/下面而不是/opt/prometheus/,是为了让“数据”和“程序”分离。程序升级不影响数据,数据备份也更简单——只需要打包/data/prometheus/tsdb目录。
3.2 静态 target 之外:file_sd 动态发现与管理
Prometheus 抓取配置最基础的是static_configs,写死 IP。这在容器环境肯定不行,所以要根据你的容器平台选服务发现方式:
- 如果跑的是裸 Docker:用
file_sd配合脚本生成 targets 文件,或者用 Consul 服务发现。 - 如果跑的是 Kubernetes:用
kubernetes_sd_configs,让 Prometheus 自动监听 Pod、Service、Endpoints 变化。
我这边环境大部分是裸 Docker 宿主机构成的集群,采用了file_sd方案。它的原理很简单:Prometheus 定期扫描指定目录下所有*.yml/*.json文件,只要文件内容发生变化,它就自动更新抓取目标,不需要 reload。
我的prometheus.yml核心内容大概是这样的:
global: scrape_interval: 30s evaluation_interval: 30s scrape_configs: - job_name: 'node' file_sd_configs: - files: - /data/prometheus/targets/nodes/*.yml refresh_interval: 60s relabel_configs: - source_labels: [__meta_file_contenthost] target_label: hostname - source_labels: [__meta_file_contentrole] target_label: role - job_name: 'docker-containers' file_sd_configs: - files: - /data/prometheus/targets/containers/*.yml refresh_interval: 60s对应的 targets 文件(比如/data/prometheus/targets/nodes/node01.yml):
- targets: - '192.168.10.11:9100' - '192.168.10.12:9100' labels: hostname: node01 role: worker那这个 targets 文件谁生成?我的做法是每台宿主机上装一个定时脚本,把本机 IP、主机名、角色写进一个文件。也可以在部署主机上写脚本,统一生成后推到 Prometheus 服务器的目录。这个方案比 Consul 轻量,对 200 台以内规模足够用。规模再大,建议直接上 Consul 或者云原生环境用 Kubernetes 服务发现。
3.3 容器侧的指标入口:cAdvisor、node_exporter 和 JMX 出口
有了服务发现框架,接下来就是每个宿主机上部署哪些 exporter。
node_exporter负责宿主机层面的指标,比如 CPU 总使用率、内存总量、磁盘 IO、网络流量。每个宿主机必须有一个,端口 9100。下载安装我就不贴了,和 Prometheus 一样,二进制 + systemd 跑起来就行。
cAdvisor负责容器层面的指标,它由 Google 开源,能采集每个容器的 CPU、内存、网络、文件系统真实使用量。cAdvisor 以容器方式运行时,需要把宿主机的一些目录挂载进去,典型启动命令:
docker run -d \ --name cadvisor \ --restart=always \ -p 8080:8080 \ --privileged \ --device=/dev/kmsg \ -v /:/rootfs:ro \ -v /var/run:/var/run:ro \ -v /sys:/sys:ro \ -v /var/lib/docker/:/var/lib/docker:ro \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ gcr.io/cadvisor/cadvisor:latest注意--device=/dev/kmsg这个在较新的内核上很有必要,否则 cAdvisor 启动时会因为无法读取内核日志设备而异常。Rocky Linux 8.7 默认内核 4.18,一般没问题,但遇到启动失败先检查这一项。
Java 容器是容器环境里的特殊群体。如果业务是 Java 应用,宿主机层面的 CPU 和内存指标根本看不出 JVM 内部情况,必须让 Java 进程暴露 JMX 指标。比较常规的做法是在 Java 应用的启动参数里挂 jmx_exporter agent:
java -javaagent:/opt/jmx_exporter/jmx_prometheus_javaagent.jar=9091:/opt/jmx_exporter/config.yaml \ -jar app.jar这个config.yaml可以控制暴露哪些 MBean,默认配置会把堆内存、GC、线程等核心指标都暴露出来。普罗米修斯这边加一个 job 指向容器的 9091 端口即可。容器环境下要注意的是:9091 端口必须映射到宿主机,否则从宿主机外面抓不到。
3.4 抓取配置里的三个细节:interval、scrape_timeout 与 relabel
配置 Prometheus 抓取时,很多人直接抄默认的15sinterval,在大规模环境里会比较吃力。默认 15s 意味着一个容器有 100 个目标时,每 15 秒要完成 100 次 HTTP 抓取,加上目标机器上 cAdvisor 的实时计算压力,整体开销非常大。
我建议起步阶段抓取间隔设为 30s,甚至更宽松的 60s。监控数据的实时性,在容器场景下并不是越密越好,30 秒粒度足够覆盖 90% 的告警和性能分析场景。调高抓取间隔会减少磁盘写入量、减少 CPU 开销、减少网络占用,是一个性价比很高的杠杆。
scrape_timeout默认是 10 秒,如果目标机器负载高,抓取可能超时。我建议在 job 级别明确写成scrape_timeout: 15s,并且给 Prometheus 和目标之间的探测接口配置合理的 HTTP 超时防止积累。
relabel_configs是 Prometheus 最强大的特性之一。很多人觉得难懂,我建议从实际诉求出发理解它:你把 target 的元信息(比如__address__、__scheme__、__meta_*)重写成更符合业务语义的标签。比如让所有 node_exporter 目标都带一个role="worker"标签,这样 Grafana 里就能按角色分组对比了。
relabel_configs: - source_labels: [__address__] regex: '(.*):9100' target_label: node_ip replacement: '$1'这段的含义是:把抓取地址里的 IP 部分单独提取出来,存成node_ip标签,方便后续查询时直接按 IP 过滤。标签的设计直接影响查询体验,建议从第一天就做好规划。
4. Grafana 接入与看板定制:从模板到能用、好用
Prometheus 存好数据,接下来的问题是“怎么看”。Grafana 装起来不难,难的是把看板调到“能干活”的状态。这一节讲我实际走过的路径。
4.1 数据源配置与版本匹配
Grafana 安装我建议直接用官方 YUM 仓库:
cat > /etc/yum.repos.d/grafana.repo <<'EOF' [grafana] name=grafana baseurl=https://rpm.grafana.com repo_gpgcheck=1 enabled=1 gpgcheck=1 gpgkey=https://rpm.grafana.com/gpg.key EOF dnf install -y grafana systemctl enable --now grafana-serverGrafana 默认监听 3000 端口,初始账号密码都是 admin,登录后第一件事就是改密码。
数据源配置很简单,左侧菜单进入 Data Sources 添加 Prometheus,填上 HTTP 地址http://localhost:9090,保存即可。版本上注意一点:Grafana 和 Prometheus 的 API 兼容性总体很好,不需要特别焦虑版本匹配。我曾经用 Grafana 10 对接 Prometheus 2.45,没有任何问题。除非你用的是极老的 Prometheus 版本,一般直接连就行。
4.2 直接可用的社区模板:1860 与 cAdvisor 系列
Grafana 社区有大量现成模板,导入之后基本零改动就能看到比较完整的指标看板。
我常用的模板有两个:
- Node Exporter Full(ID 1860):覆盖宿主机视角的 CPU、内存、磁盘、网络、IO,信息密度高,适合作为宿主机总览看板。
- Docker and system monitoring(ID 893 或 10915):综合了 node_exporter 和 cAdvisor 数据,容器视角比较舒服。
导入路径:左侧Dashboards -> New -> Import,输入模板 ID,然后选好数据源,导入完成。
注意:社区模板的局限在于它并不知道你环境里的标签值。比如模板里写了
instance=~"$node",但你实际的 instance 可能不是主机名而是 IP。导入之后需要按你的标签体系调整变量查询,这一步不能跳过。
调整变量的地方在 Dashboard 顶部的 Settings(齿轮图标)-> Variables。以 1860 模板为例,node变量的查询通常是label_values(node_uname, instance),如果你没用 node_uname 这个指标,就要换成你实际采集的指标,比如label_values(node_boot_time_seconds, instance)。
4.3 自建核心 Panel:把 rate/irate 和利用率算对
模板只是起点,真正“对得上号”的看板要自己调。最容易算错的是CPU 利用率和内存使用率。
先说 CPU。cAdvisor 暴露的是container_cpu_usage_seconds_total,这个指标是累计值,会一直增长。直接用来画图,你会得到一条永远向上的线,毫无用处。必须用rate()包一层,取单位时间内每秒钟的增长速率,才是真正的 CPU 使用率:
rate(container_cpu_usage_seconds_total{name="my-app"}[5m])这里[5m]表示取过去 5 分钟的数据做平滑。用rate()还是irate()?我的经验是:图表展示用rate比较平滑,不容易出现毛刺;瞬时波动的排查用irate更好,因为irate取的是最近两个数据点的变化率,对突刺更敏感。面板默认给我推荐的irate在长时间范围图上会看起来非常跳,我通常改成rate。
CPU 使用率的百分比化也是一道经典坑。container_cpu_usage_seconds_total是单核时间,在四核容器里跑满一个核,值增加 1 秒/秒。你要的是百分比,不能直接乘 100,因为容器可能被限制在 2 核。正确写法:
sum(rate(container_cpu_usage_seconds_total{name="my-app"}[5m])) / container_spec_cpu_quota{name="my-app"} / 1e6container_spec_cpu_quota是容器 CPU 限制的配额(单位是微秒),除一下再归一化,才是真实使用的百分比。
内存相对简单,直接看 cAdvisor 的container_memory_usage_bytes就行,但要注意这个值包含了 page cache,实际可回收的缓存也算在里面了。要排除缓存,可以用:
container_memory_working_set_bytes{name="my-app"}working_set是容器实际驻留的内存,更接近你在docker stats里看到的值。我建议 Dashboard 里两个指标都放出来,一个看整体分配,一个看实际占用,才能判断是不是过度申请内存。
4.4 告警怎么从“吵死”变成“叫得准”
告警配置很多人喜欢一步到位做很多规则,结果就是告警风暴,然后就把告警关了。我的经验是起步阶段宁可漏报,不可误报,先把频率高、影响大的几类告警做好,再逐步补充。
Prometheus 的告警规则写在独立的 yml 文件里,然后在prometheus.yml中引用:
rule_files: - /data/prometheus/rules/*.yml一个典型的容器 CPU 告警规则:
groups: - name: container.rules rules: - alert: ContainerCPUUsageHigh expr: sum(rate(container_cpu_usage_seconds_total{name=~".+"}[5m])) by (name) / 1e6 > 80 for: 10m labels: severity: warning annotations: summary: '容器 {{ $labels.name }} CPU 使用率超过 80%'for: 10m的意思是连续 10 分钟超过阈值才告警,这是过滤短时峰值闹腾的第一道滤网。十有八九的“瞎报”都是因为少了这个字段。
告警要发到钉钉/企业微信,需要串 Alertmanager。Alertmanager 单独部署,主要做两件事:分组和路由。分组的意思是同一应用同一时间段的告警合并成一条;路由的意思是按标签把告警发到不同渠道,比如预警发到闲聊群,严重故障发到值班群并 @ 到具体人。
Grafana 也可以直接配置 Alerting,但如果你已经在用 Prometheus 的 Alertmanager,就统一走它,避免重复告警链路。Grafana 侧只需要关注图和趋势。
5. 大规模容器监控的性能分析与调优实战
监控系统本身的性能,很多人关注得不够。我前面说“大规模容器环境”,其实到了这一步才真正拉开差距。Prometheus 默认配置能跑通小环境,但撑不住大场面,下面是我实际压测和踩坑调优的一些结果。
5.1 大规模环境先压垮的不是采集,是 Prometheus 自身
先澄清一个认知:Prometheus 是个单机数据库,它在多大规模下可能会扛不住?和你想的不一样,瓶颈通常不在采集,而在TSDB 的写入和压缩。
当你有 500 个容器、每个容器暴露 2000 条时间序列时,总序列数就是 100 万条。每条序列每 30 秒产生一个新样本点,Prometheus 每秒要写入约 33000 个样本。这还没算上 label 的高基数带来的序列数量膨胀。
实际观察下来,Prometheus 的 CPU 会先成为瓶颈——TSDB 写入路径的压缩和索引更新都要消耗 CPU。其次是内存,内存主要被查询引擎和 TSDB 的 WAL 缓冲占着。
针对这个情况,我的优化顺序是:
- 调大采集间隔。从 15s 改成 30s,样本量直接减一半,这是最暴力的减负手段。
- 限制样本量。在 job 级别加
sample_limit: 5000,防止某个 exporter 因为 label 爆炸导致单次抓取返回超大响应,拖垮抓取循环。 - 减少不必要的时间序列。上
keep和droprelabel,把每个 exporter 里你根本用不上的指标直接丢弃。
一个简单的 drop 示例:
metric_relabel_configs: - source_labels: [__name__] regex: 'container_network_tcp_usage_total' action: drop这几个措施叠加,存储写入压力能降一半以上,Prometheus 的 CPU 也能明显回落。反直觉的是:大多数场景下,你并不需要那么多指标。把我常用的指标从 2000 个精简到 300 个,查询速度快了不止一个量级,看板加载也从卡顿变成了秒开。
5.2 TSDB 存储:块、WAL、retention 与磁盘估算
Prometheus 的存储机制比较特殊,理解它才能做好磁盘规划和排障。它把数据按时间切成 2 小时一组的block 块,新抓取的数据先写进 WAL(预写日志),后台再压缩合并到 block 里。定期做压缩,冷数据块还可以压缩成更紧凑的格式。
常见误区是只看--storage.tsdb.retention.time决定磁盘大小。实际影响磁盘的是:样本量 × 采样间隔 × 保留天数 × 每条样本平均字节数。
用一个更实用的估算方式:先看看 TSDB 目录现在多大,按当前的每日增长量反推。比如du -sh /data/prometheus/tsdb现在是 20GB,保留 30 天,说明跑了几天基本能算出日均增长量。这个方法比任何公式都贴近实际。
磁盘规划上我的建议是:至少留出 30 天数据量 2 倍以上的空闲空间。原因是 TSDB 压缩时需要临时空间,空间不足会直接触发删除旧数据,你回溯一周前的性能问题时,会发现数据只剩一堆空洞。另外,建议在prometheus.yml里把storage.tsdb.retention.size也配一下,比如--storage.tsdb.retention.size=50GB,它会基于总数据量而不是时间做删除,更能防呆。
WAL 目录和 TSDB 数据目录尽量不要放同一个磁盘分区。WAL 是频繁写小文件,TSDB 是批量写大文件,两者混合会互相干扰磁盘 IO。如果条件允许,SSD 给 TSDB,机械盘给 WAL 都是可以玩的花活。
5.3 高基数问题的排查方法
“高基数”是 Prometheus 监控运维里必须掌握的进阶概念。简单说就是某个 label 的取值变化非常频繁,导致同一指标实际包含了大量独立的序列。最经典的元凶是:把 Pod 名、容器 ID、随机字符串当作 label。
高基数会让 Prometheus 内存持续增长,查询变慢,甚至 OOM。排查方法:
# 查看序列数量最多的指标 topk(50, count by (__name__)({__name__=~".+"}))找到之后,去确认这个指标的来源。如果是 cAdvisor 里的container_label_com_amazonaws_*这种和业务无关的标签,直接在抓取配置里 drop 掉:
metric_relabel_configs: - source_labels: [__name__] regex: 'container_label_com_amazonaws_.*' action: drop如果业务侧非要用高基数的 label(比如用户 ID),考虑改用日志系统或 APM,不要强行塞进 Prometheus。Prometheus 适合存高维度、低基数的指标,不适合存低维度、高基数的数据——遇到高基数的需求先怀疑数据建模有问题。
5.4 性能分析的补充手段:从指标反推容器问题
监控不只是看数字,还要用指标做性能归因。这里给一个我常用的分析路径。
当业务反馈某个容器响应变慢时,我的排查顺序是:
- 先看
container_cpu_usage_seconds_total的 rate,判断是不是 CPU 跑满了。 - 再看
container_memory_working_set_bytes,判断是不是内存不够触发了频繁 GC 或 swap。 - 然后看
container_network_receive_bytes_total和container_network_transmit_bytes_total的 rate,排除网络带宽瓶颈。 - Java 容器的话,进一步看
jvm_gc_pause_seconds和jvm_threads_live_threads,定位 JVM 层问题。
这套顺序的底层逻辑是:先看硬件资源水位,再看中间件层指标,最后看业务指标。每一步都在缩小范围,而不是随机点开一堆图表乱看。
还有一个容易被忽略的指标是磁盘 IO。容器环境的磁盘问题非常隐蔽,因为多个容器共享宿主机的磁盘带宽。node_exporter 里有node_disk_read_bytes_total和node_disk_writes_completed_total,先用宿主机视图找到 IO 异常的机器,再定位到具体容器,这个路径比直接看容器指标有效得多。
落盘到 Dashboard 上的话,我建议每个容器项目至少有三块视图:
- 概览视图:CPU、内存、网络、磁盘的当前值,支撑快速巡检。
- 趋势视图:过去 24 小时 / 7 天的趋势,用于发现缓慢恶化。
- 对比视图:同一应用的多个副本放在同一张图里对比,能快速定位实例间不均。
最后再说个实际心得:监控看板务必克制。一屏 20 个图,看的人只会觉得焦虑,不会觉得专业。我最终保留的看板,信息密度很高但一张图能回答一个问题。做监控的目的是在 30 秒内定位问题,不是展示我们监控了多少指标。
这套 Prometheus + Grafana 的框架,在 Rocky Linux 8.7 上跑了大半年,几百个容器的监控一直很稳。新容器上线自动被发现、看板不用改、告警基本能准确定位到是哪个应用哪个节点出了问题。如果你也在折腾从传统监控往容器监控迁移,或者刚从一张白纸开始,照着上面这条路走就可以少踩很多坑。