作者信息:https://home.cnblogs.com/u/huangjiabobk

在运维工作中,如何搭建 多套 K8s 集群统一监控平台

多套 K8s 集群统一监控平台

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

image


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. 确认前置条件(1.3 节清单逐条打勾)。
  2. 验证 LVM 指标已采集(2.1)。
  3. 内网镜像仓库准备好 thanos / grafana / minio 镜像(第 5 节)。
  4. 中心集群部署 MinIO → Receive → Query(第 3 节,三个 YAML 完整内容已内嵌),建 bucket、记凭证。
  5. 验证中心侧 remote_write 入口(3.4)。
  6. 试点接入 2~3 套业务集群(第 4 节),验证 count by (cluster) (up)。
  7. Grafana 配数据源 + 导入大盘 JSON(第 6 节)。
  8. 观察稳定后批量接入剩余集群。
  9. (可选)部署告警规则(第 8 节)。
  10. (可选)加 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 保存
    
posted @ 2026-10-15 17:05  黄嘉波  阅读(22)  评论(0)    收藏  举报
版权声明:原创作品,谢绝转载!否则将追究法律责任。--作者 黄嘉波