在运维工作中,如何搭建 多套 K8s 集群统一监控平台
多套 K8s 集群统一监控平台
适用场景:有 N 套 Kubernetes 集群,需要把每套的节点 CPU / 内存 / 磁盘(LVM 逻辑卷)使用率,聚合到一套中心监控平台统一查看、统一告警。
本文是可复用 SOP:换环境时,只需替换文中的<占位符>变量即可照做。文中附"踩坑规避"提示,均来自真实落地经验。
三个组件的完整 YAML 已内嵌在文中,也可在deploy-manifests/目录下直接取用。

0. 阅读约定
- 文中
<...>是需要你按环境替换的占位符,例如<中心集群IP>、<harbor地址>、<你的StorageClass>。 - 每个"踩坑规避"框,都是实际部署中真实踩过、已确认根因和解决办法的问题,务必先读再动手。
- 目标架构统一为 Thanos Receive(remote_write)模式,理由见 1.2。
1. 架构与前提
1.1 目标架构
每套业务集群(N 套,改动最小)
node_exporter (DaemonSet) ──抓取──> Prometheus
├─ externalLabels: {cluster: "<集群唯一编号>"}
└─ remote_write ──┐
▼
中心监控集群(1 套)
Thanos Receive ──> MinIO(对象存储,1 周留存)
│
▼
Thanos Query ──> Grafana(统一大盘 + 告警)
1.2 为什么用 Receive 模式(而非 sidecar)
| 对比 | Receive(remote_write) | Sidecar |
|---|---|---|
| 业务集群改动 | 只加 remoteWrite + externalLabels 两段配置 |
需给每个 Prometheus 旁挂 sidecar 容器 |
| 适合场景 | 短期留存(如 1 周)、集群已有完整 Prometheus | 长期留存、需要历史全量数据 |
| 抓取链路 | 不变 | 不变 |
决策规则:数据保留 ≤ 数周、业务集群已各自有 Prometheus → 用 Receive;需要按月/年长期归档 → 用 sidecar + 对象存储降采样。
1.3 前置条件(务必逐条确认)
2. 指标与 PromQL 规范(先对齐口径)
2.1 验证 LVM 指标已采集
# 在任意业务集群节点上:
curl -s localhost:9100/metrics | grep node_filesystem_size_bytes | grep mapper
# 预期能看到 device="/dev/mapper/vg名-lv名" 的输出
2.2 三个核心指标 + 标准 PromQL
统一口径(本手册所有大盘/告警都基于这些公式):
# ① CPU 使用率(按节点,百分比)
100 - (avg by (cluster, instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# ② 内存使用率(按节点,百分比)
100 * (1 - avg by (cluster, instance) (node_memory_MemAvailable_bytes)
/ avg by (cluster, instance) (node_memory_MemTotal_bytes))
# ③ 磁盘使用率(按 LVM 逻辑卷,百分比)
100 * (1 - avg by (cluster, instance, device) (node_filesystem_avail_bytes{device=~"/dev/mapper/.*"})
/ avg by (cluster, instance, device) (node_filesystem_size_bytes{device=~"/dev/mapper/.*"}))
踩坑规避 —— LVM 过滤必须用
device,不要用mountpoint:
部分逻辑卷的mountpoint标签值可能带前导空格(如" /log"),导致按 mountpoint 过滤会漏卷。统一用device=~"/dev/mapper/.*"精确匹配 LVM 逻辑卷,同时天然排除 tmpfs/overlay 等虚拟文件系统。
2.3 集群唯一编号规范
externalLabels 里的 cluster 标签必须全局唯一且命名规范(如 cluster-01 ~ cluster-20),否则聚合时数据会混。建议用配置文件或变量统一管理,避免手误。
3. 中心集群部署(Thanos 侧)
在中心集群创建命名空间:
kubectl create namespace thanos
依次部署 MinIO → Receive → Query,三个 YAML 完整内容如下。
3.1 MinIO(对象存储)
kubectl apply -f 01-minio.yaml -n thanos
完整 YAML(01-minio.yaml):
apiVersion: v1
kind: Secret
metadata:
name: thanos-objstore
namespace: thanos
type: Opaque
stringData:
accessKey: "<minio-accessKey>"
secretKey: "<minio-secretKey>"
---
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: thanos
spec:
ports:
- name: api
port: 9000
targetPort: 9000
- name: console
port: 9001
targetPort: 9001
selector:
app: minio
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
namespace: thanos
spec:
serviceName: minio
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: <harbor地址>/bitnami/minio:<版本>
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: thanos-objstore
key: accessKey
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: thanos-objstore
key: secretKey
ports:
- name: api
containerPort: 9000
- name: console
containerPort: 9001
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: <你的StorageClass>
resources:
requests:
storage: 50Gi
踩坑规避 ① —— PVC Pending:
很多集群没有默认 StorageClass,MinIO 的 PVC 会一直Pending(FailedScheduling)。必须显式写storageClassName。查询可用 SC:kubectl get storageclass。
踩坑规避 ② —— Bitnami 镜像不能带官方 args:
若用 Bitnami 版镜像(bitnami/minio),它有自己的 entrypoint,不要加官方镜像的args: [server, /data, --console-address, :9001],否则报exec: server: not found直接 CrashLoopBackOff。上面 YAML 已省略args。
部署后创建 bucket(Receive 的 objstore 需要):
# 方式一:进 MinIO pod 用 mc 客户端创建
kubectl exec -n thanos minio-0 -- mc alias set local http://localhost:9000 <minio-accessKey> <minio-secretKey>
kubectl exec -n thanos minio-0 -- mc mb local/thanos-receive
# 方式二:或用你环境的任意 mc 客户端连 MinIO Service 创建
# bucket 名固定为 thanos-receive(或与 3.2 节 objstore 配置保持一致)
3.2 Thanos Receive
kubectl apply -f 02-thanos-receive.yaml -n thanos
完整 YAML(02-thanos-receive.yaml):
apiVersion: v1
kind: ConfigMap
metadata:
name: thanos-receive-objstore
namespace: thanos
data:
objstore.yaml: |
type: S3
config:
bucket: thanos-receive
endpoint: minio.thanos.svc:9000
access_key: <minio-accessKey>
secret_key: <minio-secretKey>
insecure: true
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: thanos-receive
namespace: thanos
spec:
serviceName: thanos-receive
replicas: 1
selector:
matchLabels:
app: thanos-receive
template:
metadata:
labels:
app: thanos-receive
spec:
containers:
- name: thanos-receive
image: quay.io/thanos/thanos:v0.34.1
args:
- receive
- --tsdb.path=/var/thanos/receive
- --tsdb.retention=7d
- --objstore.config-file=/etc/thanos/objstore.yaml
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:10902
- --remote-write.address=0.0.0.0:19291
- --label=receive_replica="0"
ports:
- name: grpc
containerPort: 10901
- name: http
containerPort: 10902
- name: remote-write
containerPort: 19291
volumeMounts:
- name: data
mountPath: /var/thanos/receive
- name: objstore-config
mountPath: /etc/thanos
volumes:
- name: objstore-config
configMap:
name: thanos-receive-objstore
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: <你的StorageClass>
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
name: thanos-receive
namespace: thanos
spec:
type: NodePort
selector:
app: thanos-receive
ports:
- name: grpc
port: 10901
targetPort: 10901
- name: http
port: 10902
targetPort: 10902
- name: remote-write
port: 19291
targetPort: 19291
nodePort: <你的NodePort>
关键配置说明:
| 项 | 值 | 说明 |
|---|---|---|
--tsdb.retention |
7d |
数据保留期(按需求调整) |
--remote-write.address |
:19291 |
remote_write 接收端口 |
| Service 类型 | NodePort | nodePort: <你的NodePort>(如 30921),是业务集群 remote_write 统一入口 |
--label |
receive_replica="0" |
多副本时用于去重的副本标签 |
3.3 Thanos Query
kubectl apply -f 03-thanos-query.yaml -n thanos
完整 YAML(03-thanos-query.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-query
namespace: thanos
spec:
replicas: 1
selector:
matchLabels:
app: thanos-query
template:
metadata:
labels:
app: thanos-query
spec:
containers:
- name: thanos-query
image: <harbor地址>/thanos/thanos:<版本>
args:
- query
- --http-address=0.0.0.0:10902
- --grpc-address=0.0.0.0:10901
- --store=thanos-receive.thanos.svc:10901
- --query.replica-label=receive_replica
ports:
- name: http
containerPort: 10902
- name: grpc
containerPort: 10901
---
apiVersion: v1
kind: Service
metadata:
name: thanos-query
namespace: thanos
spec:
selector:
app: thanos-query
ports:
- name: http
port: 10902
targetPort: 10902
- name: grpc
port: 10901
targetPort: 10901
- Query 通过
--store=thanos-receive.thanos.svc:10901连 Receive 的 gRPC 地址。 - Query HTTP 端口 10902,Grafana 数据源指向它。
3.4 验证中心侧
kubectl get po -n thanos # 三个组件都应 Running
# 验证 remote_write 入口(空体 POST 返回 "snappy decode error" 即正常)
curl -X POST http://<中心集群IP>:<NodePort>/api/v1/receive
踩坑规避 ③ —— Receive 无健康检查路径:
Thanos Receive 没有/-/ready这类探活端点,不要用它探活。用上面的空体 POST,能返回 "snappy decode error" 就说明端点在正常监听、等待合法的 snappy 压缩数据。
4. 接入业务集群(每套一条命令)
对每套业务集群执行,只改 <集群编号>:
kubectl patch prometheus k8s -n monitoring --type merge -p '
{"spec":{
"externalLabels":{
"cluster":"cluster-86.170",
"env":"研发测试环境",
"arch":"非国产化",
"ip":"86.170"
},
"remoteWrite":[{"url":"http://10.43.86.247:39291/api/v1/receive"}]
}}'
说明:
kube-prometheus-stack里 Prometheus 资源名默认是k8s,命名空间默认monitoring,按实际调整。
4.1 验证接入成功
# 在中心 Thanos Query 上执行,能看到对应集群:
count by (cluster) (up)
4.2 批量接入建议
- 先接 2~3 套试点,观察指标量、网络、存储正常后,再批量接入剩余集群。
- 剩余集群用脚本循环 patch,集群编号从统一配置读取,避免手误。
5. 镜像管理(内网环境必备)
踩坑规避 ④ —— quay.io / docker.io 内网拉不通:
Thanos、Grafana 等官方镜像常托管在quay.io,内网通常拉不动。统一走"外网机器拉取 → 打 tag → 推内网 Harbor"的流程:
# 在能上外网的机器上执行(以 thanos 为例):
docker pull quay.io/thanos/thanos:<版本>
docker tag quay.io/thanos/thanos:<版本> <harbor地址>/thanos/thanos:<版本>
docker push <harbor地址>/thanos/thanos:<版本>
所有 YAML 里的镜像地址,统一替换成内网 Harbor 地址。
6. Grafana 统一大盘
6.1 数据源
Name: Thanos-Query
URL: http://<thanos-query的Service地址>:10902 #http://thanos-query.thanos.svc:10902
踩坑规避 ⑤ —— kube-prometheus-stack 装的 Grafana 常无 provisioning sidecar:
若 Grafana Pod 里只有grafana一个容器(没有grafana-sc-datasources等 sidecar),则数据源不能靠 ConfigMap 自动注入,需在网页 UI 手工添加,或用 Grafana API 创建。
6.2 大盘结构(告警优先,推荐)
三层结构,默认只暴露超阈值项,正常项收起,避免大盘信息过载:
| 区块 | 内容 | 默认状态 |
|---|---|---|
| ① 超阈值告警表 | CPU/内存 >80%、磁盘 >85%,标注 集群/主机/磁盘(LV) | 展开置顶 |
| ② 全部集群总览 | 各集群 CPU/内存/磁盘均值,按磁盘降序 | 收起 |
| ③ LVM 磁盘卷明细 | 每个逻辑卷使用率,按使用率降序 | 收起 |
- 顶部下拉变量
cluster支持筛选单套/多套集群。 - 自动刷新 10s。
- 阈值配色:>85% 黄、>90% 红(磁盘);>80% 黄、>90% 红(CPU/内存)。
6.3 大盘 JSON 模板
项目内提供可直接导入的 JSON 模板(含完整 PromQL、transform、阈值、列宽配置):
grafana-大屏-v10.json:通用版(全名不缩写,schemaVersion 36)grafana-大屏-v11.json:适配 Grafana 9.5+(文本列 width+minWidth 双锁)
导入:Dashboards → New → Import → 上传 JSON → 数据源选 Thanos-Query。
7. Grafana 版本选择
| Grafana 版本 | 说明 |
|---|---|
| 8.3.x | 老版本,表格面板列宽(custom.width)导入不生效、拖拽不持久,不建议 |
| 9.5.x | 推荐,对旧 dashboard 兼容性好、稳定,列宽支持更完善 |
升级方法(内网):
# 1. 外网机器中转镜像(见第 5 节)
docker pull docker.io/grafana/grafana:9.5.21
docker tag docker.io/grafana/grafana:9.5.21 <harbor地址>/grafana/grafana:9.5.21
docker push <harbor地址>/grafana/grafana:9.5.21
# 2. 中心集群改镜像
kubectl set image deploy/grafana -n monitoring \
grafana=<harbor地址>/grafana/grafana:9.5.21
# 3. 若镜像 tag 未变导致未滚动,强制重启
kubectl rollout restart deploy/grafana -n monitoring
# 4. 验证
kubectl exec -n monitoring deploy/grafana -- grafana -v
升级前备份 Grafana 数据(PVC / SQLite),跨大版本会自动迁移,一般无损。
8. 告警规则(可选,建议部署)
在中心集群用 PrometheusRule 对接 Alertmanager(大盘的"超阈值表"是可视化,告警是主动通知,两者互补):
# 磁盘使用率告警(LVM)
- alert: NodeFilesystemUsageHigh
expr: |
100 * (1 - node_filesystem_avail_bytes{device=~"/dev/mapper/.*"}
/ node_filesystem_size_bytes{device=~"/dev/mapper/.*"}) > 85
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 磁盘使用率过高"
# CPU 使用率告警
- alert: NodeCPUUsageHigh
expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
# 内存使用率告警
- alert: NodeMemoryUsageHigh
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 10m
labels:
severity: warning
9. 常见问题排查速查表
| 现象 | 排查方向 | 解决 |
|---|---|---|
| MinIO PVC Pending | 无默认 StorageClass | 显式指定 storageClassName |
MinIO CrashLoopBackOff (exec: server: not found) |
Bitnami 镜像带官方 args | 删除 args 字段 |
| Thanos ErrImagePull | quay.io 内网不通 | 镜像中转推 harbor(第 5 节) |
kubectl edit 改 CR 报错 |
中文注释导致校验失败 | 改用 kubectl patch |
| remote_write 后中心无数据 | 单套 Prometheus 没配 remoteWrite | patch 一次性加 remoteWrite + externalLabels |
| Grafana 数据源 Test 置灰 | 无 provisioning sidecar | 网页手工添加数据源 |
| 大盘报 "panels not loadable" | JSON 按高版本写、实际低版本 | 用与 Grafana 版本匹配的 JSON |
| LVM 卷漏采 | mountpoint 标签带前导空格 | 改用 device=~"/dev/mapper/.*" 过滤 |
| 表格列宽不生效 | Grafana 8.3.x 缺陷 | 升级到 9.5.x |
10. 完整落地步骤清单(TL;DR)
- 确认前置条件(1.3 节清单逐条打勾)。
- 验证 LVM 指标已采集(2.1)。
- 内网镜像仓库准备好 thanos / grafana / minio 镜像(第 5 节)。
- 中心集群部署 MinIO → Receive → Query(第 3 节,三个 YAML 完整内容已内嵌),建 bucket、记凭证。
- 验证中心侧 remote_write 入口(3.4)。
- 试点接入 2~3 套业务集群(第 4 节),验证
count by (cluster) (up)。 - Grafana 配数据源 + 导入大盘 JSON(第 6 节)。
- 观察稳定后批量接入剩余集群。
- (可选)部署告警规则(第 8 节)。
- (可选)加
writeRelabelConfigs只保留三类指标,节省存储。
11. 优化建议(落地后可逐步做)
-
省存储:remote_write 加
writeRelabelConfigs,只保留node_cpu_seconds_total/node_memory_*/node_filesystem_*三类指标。 -
访问控制:Grafana 分账号、只读 viewer 给值班/领导查看。
-
降采样:若后续需更长留存,再考虑 sidecar + Thanos Compactor 降采样(5m/1h)。
-
修改文本被缩起来的问题;
右侧 Overrides 里,依次选中每个文本列的 override(磁盘(LV)、以及切到 集群、节点/主机 那几条) 找到 Cell display mode → 把 Basic gauge 改成 Color text(颜色文本) 或者干脆点该属性右边的 × 删掉这一条,让它用默认 Auto——字符串列 Auto 就是纯文本 三个表格面板都照做(超阈值表、总览表、LVM 明细表) 右上角 Save dashboard 保存

浙公网安备 33010602011771号