1. 为什么默认部署的监控栈一重启就“失忆”
如果你用 Helm 或 Operator 部署过 kube-prometheus 这套监控组合,大概率经历过这样的场景:辛辛苦苦调好的 Grafana 面板、配好的告警规则、攒了两周的监控数据,一次 Pod 重启或者节点漂移之后,全部归零。这不是 bug,而是默认配置下这套栈的存储设计就是“用完即弃”的。
kube-prometheus 本质上是把 Prometheus、Alertmanager、Grafana 以及一堆 Exporter 和规则打包在一起的一套编排方案。它默认用 emptyDir 作为存储卷,Pod 一重建,数据就跟着没了。对于只是想跑个 Demo 的人来说这没什么,但只要你想把它用在稍微正式一点的场景里——哪怕只是个人实验室的长期监控——持久化就是绕不过去的第一道坎。
这篇文章要解决的就是两个核心问题:Prometheus 的时序数据怎么持久化,以及Grafana 的配置和面板怎么持久化。前者关系到你的历史监控数据能不能留存,后者关系到你花几个小时调出来的仪表盘会不会白费。我会从存储选型、配置改法、参数计算到踩坑排查,把整套流程拆开讲清楚,让你看完就能直接抄作业。
适合的读者是:已经用 kube-prometheus 跑起来一套监控、但还没做持久化的运维或开发人员;或者正准备部署、想一步到位把存储规划好的朋友。不需要你对 Prometheus 内部原理有多深的理解,但至少要能看懂 Kubernetes 的 YAML 和 PVC 的基本概念。
2. 存储方案选型:别一上来就上分布式存储
在动手改配置之前,先想清楚一件事:你的数据要存到哪里。这个决定会直接影响后面的配置方式和运维复杂度。我见过不少人一上来就想着上 Ceph 或者某分布式存储,结果为了监控持久化搭进去一整套存储集群,本末倒置。
2.1 三种常见存储方案的取舍逻辑
选存储的核心就三个维度:数据重要性、性能要求、运维成本。把这三个维度套进去,方案基本就清晰了。
| 方案类型 | 典型实现 | 适用场景 | 优点 | 代价 |
|---|---|---|---|---|
| 本地盘 | hostPath、local PV | 单节点、个人实验室 | 性能最好、零额外依赖 | 节点故障数据丢失、无法漂移 |
| 网络块存储 | 各类云盘、iSCSI | 中小规模生产 | 可漂移、性能尚可 | 依赖存储后端、有网络延迟 |
| 分布式文件/对象 | 各类分布式存储 | 大规模、多副本要求 | 高可用、可扩展 | 运维复杂、成本高 |
我的建议很直接:个人或小团队场景,优先用网络块存储;实在没有,本地盘也能用,但要接受数据可能丢的风险。Prometheus 的时序数据写入是顺序追加为主,对随机 IO 要求不高,但对磁盘的持续写入能力有要求,所以选存储时别只看容量,要看 IOPS 和吞吐。
2.2 容量到底要留多少:一个能算的公式
很多人配 PVC 的时候是拍脑袋填个 50Gi 或者 100Gi,结果要么不够用要么浪费。Prometheus 的存储占用其实是可以估算的,核心公式是:
所需容量 = 每秒采集样本数 × 每个样本字节数 × 保留时间(秒) × 副本系数每个样本在 Prometheus 的 TSDB 里压缩后大约占 1~2 字节,但考虑到索引、WAL 和压缩过程中的临时空间,工程上一般按每个样本 2 字节来估。举个例子:假设你有 1000 个活跃时间序列,采集间隔 15 秒,那么每秒样本数约为 1000/15 ≈ 67 个。保留 15 天:
67 × 2 字节 × 15 × 86400 秒 ≈ 173 MB看起来很小对吧?但实际场景里时间序列数量往往远不止 1000,加上各种 Exporter 的指标、标签组合爆炸,轻松上到几万甚至几十万序列。所以更实用的做法是:先跑一周,看 Prometheus 实际占用了多少,再按比例推算保留期所需的容量,最后留 30% 余量。
提示:Prometheus 的
--storage.tsdb.retention.time和--storage.tsdb.retention.size可以同时设置,谁先触发谁生效。生产环境建议两个都设,避免磁盘被写满导致进程崩溃。
2.3 Grafana 的持久化为什么和 Prometheus 不一样
这里有个容易混淆的点:Prometheus 持久化的是时序数据,而 Grafana 持久化的是配置数据——包括数据源、面板、用户、告警配置等。Grafana 默认用 SQLite 数据库存在容器里,Pod 一重建就没了。
Grafana 的持久化有两种思路:一是把 SQLite 文件挂到 PVC 上,简单直接;二是换成外部数据库(如 MySQL、PostgreSQL),适合多副本部署。对于绝大多数场景,挂 PVC 就够了,没必要为了一个 Grafana 上外部数据库。但要注意,如果你打算跑多个 Grafana 副本,SQLite 会有并发写入问题,这时候才需要考虑外部数据库。
3. Prometheus 数据持久化实操:从 emptyDir 到 PVC
搞清楚了选型和容量,接下来动手改。kube-prometheus 的 Prometheus 实例是通过一个名为Prometheus的自定义资源(CRD)来定义的,持久化配置就写在这个资源里。
3.1 定位并修改 Prometheus 自定义资源
先找到当前的 Prometheus 资源:
kubectl get prometheus -n monitoring假设输出里有个叫k8s的实例,用下面的命令看它的完整配置:
kubectl get prometheus k8s -n monitoring -o yaml你会看到spec.storage字段,默认可能是空的或者写着emptyDir: {}。要改成 PVC,需要这样写:
apiVersion: monitoring.coreos.com/v1 kind: Prometheus metadata: name: k8s namespace: monitoring spec: retention: 15d storage: volumeClaimTemplate: spec: storageClassName: your-storage-class accessModes: - ReadWriteOnce resources: requests: storage: 50Gi这里有几个关键点要解释清楚。volumeClaimTemplate是一个模板,Operator 会用它为每个 Prometheus Pod 生成一个独立的 PVC。storageClassName必须填你集群里实际存在的存储类,用kubectl get storageclass可以查。accessModes一般用ReadWriteOnce,因为 Prometheus 是单副本写入,不需要多节点同时挂载。
改完之后直接kubectl apply应用,Operator 会滚动重建 Prometheus Pod,并自动创建 PVC。你可以用下面的命令确认 PVC 是否绑定成功:
kubectl get pvc -n monitoring3.2 保留时间与容量的联动配置
retention字段控制数据保留多久,默认是 24 小时,这个值对于正经监控来说太短了。常见的设置是15d到30d,具体取决于你的容量和查询需求。
这里有个实操经验:保留时间不要设得太长,历史数据的查询频率其实很低。如果你需要长期留存,更好的做法是用远程写入(remote write)把数据转发到专门的长存储系统,而不是让 Prometheus 本地扛着。本地保留 15 天,足够覆盖绝大多数排障和趋势分析场景。
另外,retention和 PVC 容量要匹配。如果你设了 30 天保留但只给了 20Gi,Prometheus 会在写满后开始删除旧数据,甚至可能因为磁盘压力触发告警。所以改保留时间的时候,一定回头检查容量够不够。
3.3 验证持久化是否真正生效
改完配置不代表就成功了,得验证。最直接的方法是这样:
- 记录当前 Prometheus Pod 的名字和它挂载的 PVC。
- 手动删除这个 Pod,等它重建。
- 重建后进入 Pod,检查历史数据是否还在。
具体命令:
# 查看 Pod 和 PVC 的绑定关系 kubectl get pod -n monitoring -l app.kubernetes.io/name=prometheus -o wide kubectl get pvc -n monitoring # 删除 Pod 触发重建 kubectl delete pod <prometheus-pod-name> -n monitoring # 等新 Pod 起来后,查询一个历史时间点的数据 kubectl exec -it <new-prometheus-pod> -n monitoring -- \ promtool query instant http://localhost:9090 'up' --time=<某个过去的时间戳>如果新 Pod 能查到删除前的数据,说明持久化生效了。如果查不到,八成是 PVC 没绑上,或者挂载路径不对。
注意:Prometheus Operator 在重建 Pod 时,如果发现原来的 PVC 还在,会复用;如果 PVC 被删了,就会新建一个空的。所以千万别手贱去删 PVC。
4. Grafana 配置持久化:别让调好的面板白费
Grafana 的持久化比 Prometheus 简单,但有个坑很多人会踩:只挂了数据卷,却没处理配置的初始化逻辑,导致重启后虽然数据在,但配置被覆盖了。
4.1 通过 Deployment 挂载 PVC 的标准做法
kube-prometheus 默认用 Deployment 部署 Grafana。要持久化,需要做两件事:创建一个 PVC,然后在 Deployment 里挂载它。
先创建 PVC:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: grafana-storage namespace: monitoring spec: storageClassName: your-storage-class accessModes: - ReadWriteOnce resources: requests: storage: 5GiGrafana 的配置数据量很小,5Gi 绰绰有余,主要是 SQLite 数据库和插件。然后在 Grafana 的 Deployment 里找到volumes和volumeMounts,加上挂载:
volumeMounts: - name: grafana-storage mountPath: /var/lib/grafana volumes: - name: grafana-storage persistentVolumeClaim: claimName: grafana-storage/var/lib/grafana是 Grafana 默认存放数据库和插件的地方,挂上这个路径,配置就持久了。
4.2 用 Helm 部署时的 values 覆盖技巧
如果你是用 Helm 部署的 kube-prometheus-stack,直接改 Deployment 会被下次helm upgrade覆盖掉。正确做法是在 values 文件里配置:
grafana: persistence: enabled: true storageClassName: your-storage-class size: 5Gi accessModes: - ReadWriteOnce这样 Helm 渲染出来的 Deployment 就会自动带上 PVC 挂载。改完执行helm upgrade即可。这里的关键是不要手动去改 Operator 或 Helm 管理的资源,否则下次同步就丢了,所有改动都要通过 values 或 CRD 来做。
4.3 数据源和面板的“配置即代码”思路
挂 PVC 解决了持久化,但还有个更深层的问题:如果 PVC 也丢了怎么办。这时候就需要“配置即代码”的思路,把 Grafana 的配置用声明式的方式管理起来。
Grafana 支持通过 ConfigMap 挂载数据源和面板的 provisioning 配置。你可以在 values 里这样写:
grafana: additionalDataSources: - name: Prometheus type: prometheus url: http://prometheus-operated:9090 access: proxy isDefault: true面板也可以通过dashboardProviders和 ConfigMap 来预置。这样一来,即使 PVC 全丢了,重新部署时配置会自动恢复。PVC 负责运行时数据(用户、临时改动),provisioning 负责基线配置,两者配合才是完整的持久化方案。
提示:provisioning 的配置默认是只读的,在 Grafana 界面上改不了。如果你需要界面编辑,就别把面板放进 provisioning,或者接受“界面改动重启后丢失”的设定。
5. 常见问题与排查技巧实录
持久化这件事,配起来不难,难的是出问题的时候怎么快速定位。下面这些是我在实际操作中踩过的坑和对应的排查思路。
5.1 PVC 一直 Pending 怎么办
这是最常见的问题,PVC 创建后卡在 Pending 状态,Pod 也就起不来。排查顺序是这样的:
| 排查项 | 检查命令 | 常见原因 |
|---|---|---|
| StorageClass 是否存在 | kubectl get sc | 名字写错或没装存储插件 |
| 是否有可用 PV | kubectl get pv | 静态供应模式下没有匹配的 PV |
| 容量是否满足 | 看 PVC 的 events | 请求容量超过后端限制 |
| 访问模式是否支持 | 看 StorageClass 定义 | 后端不支持 ReadWriteOnce |
用kubectl describe pvc <name> -n monitoring看 Events 部分,通常会有明确的原因提示。如果是动态供应,重点看 StorageClass 的 provisioner 是否正常工作。
5.2 Pod 起来了但数据没持久化
这种情况通常是挂载路径不对,或者挂载被覆盖了。比如 Prometheus 的数据目录默认是/prometheus,如果你挂到了别的路径,数据还是写在容器层。
验证方法:进入 Pod,用df -h看挂载点,确认目标路径确实挂的是 PVC 而不是 overlay。另外检查一下securityContext里的fsGroup,如果权限不对,Prometheus 可能写不进去,日志里会有 permission denied。
5.3 Grafana 重启后配置回滚
这个问题的根源往往是 provisioning 和数据库的冲突。如果你既挂了 PVC,又配了 provisioning,重启后 provisioning 的配置会覆盖数据库里的同名配置。解决办法是:要么统一用 provisioning 管理,要么统一用界面管理,别混着来。
还有一种可能是 Helm 的helm upgrade把 Deployment 的挂载改回去了。检查一下 values 文件里的 persistence 配置是否真的生效,用helm get values确认。
5.4 存储写满后的应急处理
Prometheus 磁盘写满会导致进程崩溃,Pod 反复重启。应急处理是先进 Pod 删掉部分旧数据,或者临时调小 retention。但更根本的做法是提前配好告警,监控 PVC 的使用率。
- alert: PrometheusStorageAlmostFull expr: | (kubelet_volume_stats_used_bytes{persistentvolumeclaim=~"prometheus.*"} / kubelet_volume_stats_capacity_bytes{persistentvolumeclaim=~"prometheus.*"}) > 0.85 for: 10m labels: severity: warning annotations: summary: "Prometheus 存储使用率超过 85%"这条告警规则建议直接加上,能帮你避免半夜被磁盘写满叫醒。
6. 几个我踩过之后才明白的细节
最后分享几个文档里不太会写、但实际很关键的经验点。
第一,PVC 的回收策略要留意。动态供应的 PV 默认回收策略是 Delete,意味着你删了 PVC,底层存储也会被删。如果你只是想重建 Pod 而不是丢数据,千万别删 PVC。可以在 StorageClass 里把reclaimPolicy设成 Retain,给自己留条后路。
第二,Prometheus 的 WAL 目录也要持久化。有些人只挂了数据目录,忽略了 WAL。WAL 是预写日志,用于崩溃恢复,如果它在容器层,Pod 异常重启后可能丢失最近几分钟的数据。确保整个/prometheus目录都挂上。
第三,Grafana 的插件目录值得单独考虑。如果你装了很多插件,插件文件也占空间,而且重装麻烦。可以把/var/lib/grafana/plugins单独挂一个卷,或者用 initContainer 在启动时自动安装。
第四,别忽略备份。持久化不等于备份。PVC 可能因为误删、存储故障而丢失,重要的 Grafana 面板建议定期导出 JSON 存档,Prometheus 的关键数据可以考虑远程写入到另一个存储。持久化解决的是“重启不丢”,备份解决的是“彻底丢了能恢复”,两码事。
这套配置我在几个不同规模的集群上都跑过,从单节点实验室到几十个节点的小集群,核心逻辑是一样的:选对存储、配对容量、验证生效、做好监控。真正花时间的往往不是配置本身,而是排查那些“看起来配了但没生效”的细节。把上面这些点都过一遍,基本就能睡个安稳觉了。