NAS 风扇狂转,查出 Thanos Compactor 把 bucket 撑到了 241G


TL;DR:一次例行升级重启了 Thanos Compactor,它开始疯狂追赶积压了一年多的 block,把家里 NAS 的磁盘 I/O 打满、风扇狂转。排查发现 compactor 早已静默停摆、bucket 膨胀到 241G,最后用 thanos tools bucket retention 把过期数据一刀清到 18G。复盘才发现:它压根不是”某天停了”,而是从上线起就因为 PVC 太小反复 halt,retention 一次都没跑完过。补上告警防止再次发生。

风扇为什么在狂转

最近给家里的 Kubernetes 集群做一次例行的 pod 滚动升级,几个 pod 陆续更新。没过多久,就摆在我旁边、客厅里的绿联 NAS 风扇开始呼呼狂转。

进入 NAS 后台网页查看,网络和磁盘 I/O 已经全顶满了。NAS 上跑着一个 MinIO,是集群里 Thanos 的对象存储——磁盘被打满,说明集群里有什么东西正在疯狂读写它。

打开 Lens 按 CPU 排序,占用最高的是 Thanos Compactor。翻它的日志,确认就是它在驱动 NAS 的磁盘读写:

downloading blocks (5.2s)
compacting blocks (3.1s)
uploaded block (4.8s)
downloading blocks (6.9s)
compacting blocks (3.6s)
uploaded block (4.9s)
... 循环 47 次,12 分钟内完成

compactor 的工作是定期把 Prometheus 写入的 2 小时小数据块合并、降采样、清理。正常情况下应该是一个持续的、分散在日常的后台进程,不应该在某个瞬间集中爆发。肯定有东西坏了。

插播:Thanos 是什么

在继续之前,先花一分钟搞清楚 Thanos 的几个组件。如果你已经很熟了,可以跳过。

Thanos 是 Prometheus 的长期存储和高可用扩展层——把指标长期堆到廉价的对象存储上,正是我引入它的原因。几个组件的关系是这样的:

Thanos 组件关系图:Prometheus 采集的 2h block 由 Sidecar 上传到对象存储(S3/MinIO),Compactor 在对象存储上做压缩、降采样和删除过期数据,Store Gateway 从对象存储读历史数据交给 Querier,Querier 再把它和 Sidecar 的最近 2h 实时数据聚合起来,供 Grafana 查询

主要组件:

  • Sidecar:挂在 Prometheus 旁边,把 TSDB block 上传到对象存储(S3/MinIO)
  • Store Gateway:从对象存储读历史数据,暴露给 Querier 查询
  • Compactor:把对象存储里的 2h block 压缩合并,同时做降采样(5m / 1h 精度),并按保留策略删除过期数据
  • Querier / Query Frontend:聚合 Sidecar 的实时数据 + Store Gateway 的历史数据,给 Grafana 提供统一查询入口

简单说:Compactor 是管数据整理和清理的。如果它停了,数据只增不减,但查询不受影响(因为 Store Gateway 可以直接读未压缩的 block),这也是为什么它停了好久我都没发现。

第一层:Thanos Compactor 积压了多久?

先看日志时间线。最早被压缩的 block 能追溯到 2025 年 7 月——算下来积压了一年多。

不用翻日志也能验证:直接在 NAS 上打开 MinIO 的 thanos bucket,最早的那批 block 同样停在 2025 年 7 月。两边对得上,说明不是日志滚动造成的错觉——这些数据是真的从那时候起就没被清理过。

compactor 暴露了 Prometheus 指标在 :10902/metrics

thanos_compact_todo_downsample_blocks 179
thanos_compact_halted 0

179 个 block 等待降采样处理。halted 是 0,说明它此刻在正常跑;但结合这么大的积压,只能推断它此前长期没有真正推进过——具体原因留到复盘(这也是后面要补告警的动机)。

compactor 什么时候停的?当时我答不上来。因为没有告警。(后面复盘会发现,答案比“某天停了”更尴尬。)

这个指标一直是暴露的,Prometheus 一直在抓,但没有人配一条告警规则说 “compactor 挂了请通知我”。compactor 停掉不会立刻影响任何业务——Grafana 照常查询,所有应用指标正常采集——它是一个静默失败的服务。

第二层:追赶积压时 compactor 的 PVC 被挤爆

重启后的 compactor 在追赶积压了一年多的 block。它并行下载 7 个旧 block 到本地 PVC,合并之后再把结果传回去——开头那阵风扇狂转,就是这个”下载、压缩、上传”的循环在把 NAS 的网络和磁盘打满

而在本地这边,峰值磁盘占用超过了 PVC 容量:

preallocate: no space left on device
critical error detected; halting

挂了。10Gi 的 PVC 不够用。

两个措施:

  • 扩容 PVC 到 20Gi
  • compact.concurrency 从 4 降到 2,减少同时并行的压缩组,从而压低峰值磁盘占用(它和上面”并行拉取几个 block”不是一个概念)

重启 pod,compactor 重新开始追赶。

第三层:bucket 里积压了多少数据?

compactor 处理了一会儿之后我心里冒出一个问题:这一年多的积压,bucket 里到底有多少数据?

去 NAS 上 du -sh 看了一下:

thanos-dev/ 241G

241G。对于一个保留策略是 14d / 30d / 60d 的 Prometheus 集群,正常应该在 20G 以内。

这时候我问了下 AI,得到的建议是”让 compactor 自己处理就行,它会慢慢把积压追上来”。听着挺有道理,但有个显而易见的逻辑问题:我最长的保留期就是 60 天,一年前的数据不管怎么压缩、降采样,追上来之后照样是要删的。

再看一眼 compactor 此刻在干的事:

  1. 从 MinIO 下载 2025 年的旧 block
  2. 在本地做 compaction 合并
  3. 上传合并结果回 MinIO
  4. 然后标记旧 block 删除(因为超过 60d 保留期)

下载、压缩、上传,最后再删掉。 每一步都是 NAS 上的读写 I/O,而最终结果跟直接删除一模一样。compactor 在浪费生命。

一刀切:用 thanos tools bucket retention 清掉过期数据

Thanos 有个内置命令 tools bucket retention,可以直接按保留策略扫描 bucket,标记所有过期 block 为待删除:

Terminal window
# 先停掉 compactor,避免冲突
kubectl scale deploy thanos-compactor -n observability --replicas=0
# 按保留策略标记过期 block,--delete-delay=0s 立即生效
thanos tools bucket retention \
--objstore.config-file=/tmp/objstore.yml \
--retention.resolution-raw=14d \
--retention.resolution-5m=30d \
--retention.resolution-1h=60d \
--delete-delay=0s
# 物理删除
thanos tools bucket cleanup \
--objstore.config-file=/tmp/objstore.yml \
--delete-delay=0s
# 恢复 compactor
kubectl scale deploy thanos-compactor -n observability --replicas=1

objstore.yml 就是 compactor 平时用的那份对象存储配置(指向同一个 bucket),直接复用即可。运行前务必确认 compactor 已经停了,两边同时写会冲突。

几分钟后,再次 du -sh

thanos-dev/ 18G

241G → 18G。 风扇终于安静了。

复盘:根因是 PVC 太小,撑爆后不会重试

10Gi 的 PVC 太小。compactor 一重启就去追赶积压,并发下载直接撑爆本地磁盘,no space left on device 之后它就 halt 在那儿——halt 了不会自己重试,一直等到下次 pod 重启,再重演同一场失败:

pod 重启 → 追赶积压 → 撑爆本地磁盘 → no space left on device → halted
→ 一直 halted,直到下次 pod 重启 → 回到第一步

而这一年里 thanos_compact_halted 一直亮着 1,没人看过它一眼。

让沉默的不再沉默:给 compactor 补上告警

清理数据是治标,加告警才是治本。配了一条 PrometheusRule:

- alert: ThanosCompactorHalted
expr: thanos_compact_halted == 1
for: 10m
labels:
severity: critical
annotations:
summary: "Thanos compactor is halted"
- alert: ThanosCompactorDown
expr: absent(thanos_compact_halted)
for: 15m
labels:
severity: critical
annotations:
summary: "Thanos compactor is down"

两个告警都是 critical 级别,路由到 Pushover,手机收通知。早点配的话,这也就一行配置——指标一直在那,没人看而已。

如果你也在跑 Thanos:三步自查

留一份可以照抄的自查清单,怕你的 compactor 也在悄悄积压:

  1. 看指标:抓 thanos_compact_halted(是否 == 1)和 thanos_compact_todo_compaction_blocks / thanos_compact_todo_downsample_blocks(还有多少 block 待处理),确认 compactor 最近确实在推进、没有卡住。
  2. 看 bucket 大小:在对象存储上 du -sh(或 MinIO 控制台)看实际占用,对照你的保留策略估个正常值——差一个数量级就要警惕。类似的”磁盘莫名被占满”排查思路,我在树莓派磁盘占满那篇也写过。
  3. 加告警:把上面那两条 PrometheusRule 抄进去,让它在 halted 或 down 时主动喊你,而不是等风扇替你报警。

总结:241G → 18G

度量修复前修复后
bucket 大小241G18G
compactor PVC10Gi20Gi
并发数42
告警规则02
NAS 风扇狂转安静

几点教训:

  • compactor 静默失败的杀伤力——不影响查询,所以很难被发现。但数据不断堆积,某天重启后集中爆发。对这类服务要主动监控,不能”等用户报告”。
  • “没炸”不等于”在工作”——复盘下来,它不是某天停的,而是从上线起就没成功清理过一次。pod 一直是 Running,日志里也一直有动静,唯一说真话的是那个没人看的 thanos_compact_halted。给这类后台服务定验收标准时,要盯它推进了多少,而不是它还活着没有
  • Prometheus 指标已经在等你了——thanos_compact_halted 暴露了很久,少一条告警规则的区别就是 241G 的磁盘占用。
  • 过期数据别让 compactor 硬算——如果知道大量数据已经超出保留期,直接用 bucket retention 一刀切,省掉把这些注定要删的数据来回下载、压缩、上传的整套 I/O。