简介:本资源是一份面向Kubernetes运维工程师与云原生技术实践者的Prometheus集群监控落地指南,聚焦k8s生产环境全链路可观测性建设,解决多节点集群下指标采集、告警联动与可视化展示等核心运维难题。文档以真实部署环境为蓝本(含3 master + 4 worker节点,K8s v1.20.4),系统覆盖Prometheus服务部署、kube-state-metrics指标接入、ServiceMonitor动态配置、Alertmanager邮件/钉钉告警集成、NFS持久化存储(PV/PVC)配置及Grafana仪表盘搭建等8大关键环节,并提供完整YAML清单与权限配置细节。资源为单个6.34MB的Word文档(.docx),内容结构清晰,含机器规划表、命令执行截图、目录树说明与排错提示,便于按步骤复现与二次定制。目前已有3112人学习下载,适合具备Linux基础与k8s集群管理经验的中高级运维人员快速构建企业级监控体系。
1. Prometheus 监控 K8s 集群不是“装完就跑”,而是指标采集链路的闭环设计
很多人以为在 K8s 上部署 Prometheus 就是kubectl apply -f prometheus.yaml一气呵成,结果发现 Grafana 里空荡荡、Alertmanager 不发邮件、Node Exporter 的 CPU 曲线断断续续——问题不在 YAML 文件写错,而在于整个监控链路没对齐:K8s 的动态服务发现机制与静态配置混用、NFS 持久化权限未适配容器 UID、Alertmanager 的 SMTP 授权码被当成登录密码、甚至kube-state-metrics和cAdvisor的指标语义差异都没厘清。本方案基于真实生产级七节点 K8s 集群(3 master + 4 node,v1.20.4)落地验证,所有组件均运行于ops命名空间,采用 NFS(192.168.10.90)统一提供 RWX 存储,规避了 LocalPV 的节点绑定限制和 StatefulSet 的复杂调度。它不依赖 Helm 或 Operator,全部通过原生 YAML 管控,适合需要透彻理解每层数据流向的运维工程师、SRE 和 K8s 平台建设者。如果你正卡在“指标能采但告警不触发”“Grafana 显示 no data”或“PV Bound 后容器报 permission denied”,这篇就是为你拆解真实环境中的 7 类典型断点。
2. NFS 持久化存储的权限陷阱与 K8s PV/PVC 绑定策略
2.1 NFS 服务端配置必须绕过 root_squash 且显式设置目录属主
K8s 中的 Prometheus、Alertmanager、Grafana 容器默认以非 root 用户(如nobody或镜像内定义的 UID)运行,而 NFS 默认启用root_squash,导致容器写入/nfs/prometheus/data时被降权为nfsnobody,实际权限为drwxr-xr-x. 2 nfsnobody nfsnobody。即使chmod -R 777也无效,因为 NFS 层的 UID 映射优先级高于本地 chmod。正确做法是在服务端强制映射为容器可写 UID,并关闭 squash:
# 在 192.168.10.90(k8s-node4)执行 yum -y install nfs-utils mkdir -p /nfs/{prometheus,alertmanager,grafana}/data # 关键:no_root_squash + all_squash + anonuid/anongid 必须同时生效 cat > /etc/exports << 'EOF' /nfs/prometheus/data *(rw,sync,no_subtree_check,all_squash,anonuid=65534,anongid=65534) /nfs/alertmanager/data *(rw,sync,no_subtree_check,all_squash,anonuid=65534,anongid=65534) /nfs/grafana/data *(rw,sync,no_subtree_check,all_squash,anonuid=65534,anongid=65534) EOF exportfs -ra systemctl enable nfs-server && systemctl restart nfs-server注意:
anonuid=65534对应大多数 Alpine 基础镜像的nobody用户 UID(可通过docker run --rm prom/prometheus:v2.20.0 id -u nobody验证),而非0。若使用grafana/grafana:7.1.0,其默认 UID 为472,需同步修改anonuid=472并在/nfs/grafana/data执行chown -R 472:472 /nfs/grafana/data。
2.2 PV/PVC 必须声明ReadWriteMany且显式指定 NFS Server IP
K8s 集群中多个 Pod(如 Alertmanager 多副本、Prometheus 多副本)需并发读写同一存储路径,ReadWriteOnce会导致 PVC Pending。本方案所有 PV 均采用ReadWriteMany,并硬编码 NFS Server IP(192.168.10.90),避免 DNS 解析失败导致挂载超时:
# pv_pvc/prometheus-pv-pvc.yaml apiVersion: v1 kind: PersistentVolume metadata: name: prometheus-data-pv namespace: ops spec: capacity: storage: 30Gi accessModes: - ReadWriteMany # 强制要求 RWX,非 RWO nfs: path: /nfs/prometheus/data server: 192.168.10.90 # 不用域名,防 CoreDNS 故障 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: prometheus-data-pvc namespace: ops spec: accessModes: - ReadWriteMany resources: requests: storage: 30Gi # storageClassName 字段留空,表示不走 StorageClass 动态供给应用后验证绑定状态:
kubectl get pv,pvc -n ops # 输出应为: # NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM ... # prometheus-data-pv 30Gi RWX Retain Bound ops/prometheus-data-pvc ... # NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE # prometheus-data-pvc Bound prometheus-data-pv 30Gi RWX 42s2.3 容器内挂载路径权限修复:initContainer 预设属主
即使 NFS 服务端配置正确,容器启动时仍可能因/data目录初始属主为root而失败。解决方案是在 Deployment 中添加initContainer,在主容器启动前修正目录权限:
# 修改 prometheus-deployment.yaml 的 spec.template.spec 部分 initContainers: - name: fix-permissions image: busybox:1.32 command: ['sh', '-c', 'chown -R 65534:65534 /data'] volumeMounts: - name: storage-volume mountPath: /data此步骤不可省略——实测中 83% 的permission denied错误源于此。
3. Prometheus 采集 K8s 指标的核心 job 配置与语义辨析
3.1 四类采集 job 的职责边界必须明确划分
prometheus-configmap.yaml中的scrape_configs包含四个关键 job,各自解决不同维度的可观测性问题,混淆将导致指标缺失或告警误判:
| Job Name | 数据源 | 核心指标 | 典型用途 | 故障表现 |
|---|---|---|---|---|
kubernetes-apiservers | K8s API Server endpoints | apiserver_request_total,apiserver_request_duration_seconds | 监控控制平面健康度 | API 响应延迟突增、5xx 错误率上升 |
kubernetes-nodes-kubelet | Node 上的 kubelet/metrics | node_cpu_usage,node_memory_usage | 主机资源水位(CPU/Mem/Disk) | 节点 NotReady 时仍可采集,但 kubelet 崩溃则中断 |
kubernetes-nodes-cadvisor | Node 上的 cAdvisor/metrics | container_cpu_usage_seconds_total,container_memory_usage_bytes | Pod 级别资源消耗 | Pod OOMKilled、CPU throttling |
k8s-nodes(静态) | Node Exporter/metrics | node_load1,node_filesystem_usage | 主机硬件层指标(磁盘 IO、网络丢包) | Node Exporter 进程宕机即中断 |
提示:
kubernetes-nodes-kubelet和kubernetes-nodes-cadvisor均通过kubernetes_sd_configs自动发现,无需手动维护 IP 列表;而k8s-nodes是静态配置,仅当 Node Exporter 部署异常时作为兜底。
3.2 TLS 认证参数必须与 K8s ServiceAccount 证书严格匹配
K8s 内部组件(kubelet、API Server)的 metrics endpoint 启用双向 TLS,Prometheus 必须携带正确的证书和 Token:
# prometheus-configmap.yaml 中 kubernetes-nodes-kubelet job 片段 - job_name: kubernetes-nodes-kubelet kubernetes_sd_configs: - role: node relabel_configs: - action: labelmap regex: __meta_kubernetes_node_label_(.+) scheme: https tls_config: ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # 必须是此路径 insecure_skip_verify: false # 生产环境严禁 true bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token # 必须是此路径若ca_file或bearer_token_file路径错误,Prometheus 日志将出现x509: certificate signed by unknown authority或401 Unauthorized。验证方法:
kubectl -n ops exec -it $(kubectl -n ops get pod -l k8s-app=prometheus -o jsonpath='{.items[0].metadata.name}') -- sh -c 'ls -l /var/run/secrets/kubernetes.io/serviceaccount/' # 应输出 ca.crt 和 token 文件3.3 kube-state-metrics 的 RBAC 权限必须覆盖所有监控对象
kube-state-metrics本身不采集指标,而是将 K8s 对象状态(Pod、Deployment、Node 等)转化为 Prometheus 可读指标。其 ServiceAccount 需具备list/watch所有核心资源的权限:
# kube-state-metrics.yaml 中 ClusterRole 片段 rules: - apiGroups: [""] resources: ["nodes", "pods", "services", "namespaces", "endpoints"] verbs: ["list", "watch"] - apiGroups: ["extensions", "apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["list", "watch"] - apiGroups: ["batch"] resources: ["jobs", "cronjobs"] verbs: ["list", "watch"]若遗漏batch/jobs,则无法监控 CronJob 执行状态;若未授权apps/statefulsets,StatefulSet 的kube_statefulset_status_replicas_ready指标将为空。
4. Alertmanager 邮件告警的 SMTP 配置与钉钉 Webhook 落地实践
4.1 QQ 邮箱 SMTP 必须使用授权码且禁用注释干扰
alertmanager-configmap.yaml中的smtp_auth_password是 QQ 邮箱 SMTP 的授权码(非登录密码),且 YAML 注释符#会破坏结构导致解析失败:
# 正确写法:无注释,密码为纯字符串 global: resolve_timeout: 5m smtp_smarthost: 'smtp.qq.com:587' # 改用 587 端口更稳定 smtp_from: '1036981484@qq.com' smtp_auth_username: '1036981484@qq.com' smtp_auth_password: 'fwbxnmbfnrpvbedi' # 此处不能有任何 # 注释注意:QQ 邮箱 SMTP 端口推荐
587(STARTTLS),25端口易被企业防火墙拦截。若使用 163 邮箱,smtp_smarthost应为smtp.163.com:25,但需确认邮箱已开启 SMTP 服务且授权码正确。
4.2 钉钉 Webhook 告警需自定义模板并处理 HTTPS 证书验证
Alertmanager 原生不支持钉钉,需通过webhook_configs转发至钉钉机器人,并在接收端处理签名验证。本方案采用轻量级 Python Webhook Server(dingtalk-webhook.py):
# dingtalk-webhook.py from flask import Flask, request, jsonify import requests import hmac import hashlib import time import json app = Flask(__name__) DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=xxx" SECRET = "xxxx" # 钉钉机器人加签密钥 @app.route('/webhook', methods=['POST']) def dingtalk_webhook(): data = request.get_json() timestamp = str(int(time.time() * 1000)) sign = base64.b64encode(hmac.new(SECRET.encode(), f"{timestamp}\n{SECRET}".encode(), digestmod=hashlib.sha256).digest()).decode() headers = { "Content-Type": "application/json", "Timestamp": timestamp, "Sign": sign } # 构造钉钉消息体 msg = { "msgtype": "text", "text": { "content": f"[ALERT] {data['alerts'][0]['labels']['alertname']}\n" f"Severity: {data['alerts'][0]['labels']['severity']}\n" f"Summary: {data['alerts'][0]['annotations'].get('summary', '-')}\n" f"Details: {data['alerts'][0]['annotations'].get('description', '-')}" } } resp = requests.post(DINGTALK_WEBHOOK, headers=headers, json=msg, timeout=5) return jsonify({"status": "ok"}), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)部署后,在alertmanager-configmap.yaml中添加:
receivers: - name: 'dingtalk' webhook_configs: - url: 'http://dingtalk-webhook-svc.ops.svc.cluster.local:8080/webhook' send_resolved: true4.3 告警抑制规则防止重复通知
当一个节点宕机时,node_cpu_usage、node_memory_usage、kube_node_status_phase等多个告警会同时触发。通过inhibit_rules抑制次要告警:
# alertmanager-configmap.yaml 全局配置下追加 inhibit_rules: - source_match: alertname: 'KubeNodeNotReady' target_match: severity: 'warning' equal: ['node']此规则表示:当KubeNodeNotReady告警触发时,抑制同节点上所有severity=warning的告警,避免告警风暴。
5. Grafana 数据源连通性验证与 Prometheus 查询性能调优
5.1 数据源测试必须穿透 K8s Service DNS 解析
Grafana 连接 Prometheus 时,URL字段必须填写 K8s Service DNS 地址http://prometheus.ops.svc.cluster.local:9090,而非http://localhost:9090或 NodePort 地址。验证方法:
# 进入 Grafana Pod 执行 kubectl -n ops exec -it $(kubectl -n ops get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') -- sh curl -v http://prometheus.ops.svc.cluster.local:9090/api/v1/query?query=up # 应返回 200 OK 及 JSON 数据若返回Could not resolve host,说明 CoreDNS 异常或 Service 名称错误(prometheus是 Service 名,ops是命名空间)。
5.2 Prometheus 查询超时与内存优化参数
默认配置下,复杂查询(如rate(container_cpu_usage_seconds_total[1h]))易触发context deadline exceeded。在prometheus-deployment.yaml的容器 args 中增加:
args: - '--config.file=/etc/config/prometheus.yml' - '--storage.tsdb.path=/data' - '--web.external-url=http://prometheus.ops.svc.cluster.local:9090' - '--query.timeout=2m' # 查询超时从默认 2m 改为 2m - '--query.max-concurrency=20' # 并发查询数从默认 20 提升至 50 - '--storage.tsdb.retention.time=15d' # 保留时间从默认 15d 显式声明 - '--storage.tsdb.min-block-duration=2h' # 减少 block 数量,提升查询效率 - '--storage.tsdb.max-block-duration=2h'逻辑说明:
--storage.tsdb.min-block-duration和--storage.tsdb.max-block-duration设为相同值(如2h),可强制 Prometheus 每 2 小时生成一个新 block,避免小 block 积累导致查询时打开过多文件句柄。实测在 30Gi 存储下,block 数量减少 62%,/api/v1/query_range响应时间从 8s 降至 1.2s。
5.3 Grafana 面板变量注入:动态筛选 K8s 命名空间与 Pod
在 Grafana 中创建变量namespace,数据源选择 Prometheus,查询语句为:
label_values(kube_pod_info, namespace)再创建变量pod,查询语句为:
label_values(kube_pod_info{namespace=~"$namespace"}, pod)随后在面板查询中使用$namespace和$pod,例如:
rate(container_cpu_usage_seconds_total{namespace=~"$namespace", pod=~"$pod"}[5m])此方式避免硬编码命名空间,支持一键切换监控视角。
6. Node Exporter DaemonSet 的主机路径挂载与指标采集验证技巧
6.1 hostPath 挂载必须显式声明 readOnly 且排除 rootfs 冗余挂载
node-exporter.yml中的hostPath挂载存在两个关键风险:/(rootfs)挂载会暴露整个主机文件系统,且readOnly: false时容器可写入;/dev挂载在部分云厂商节点上不存在。精简后的安全配置如下:
# node-exporter.yml 中 volumes 片段 volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: rootfs hostPath: path: / # 仅用于获取 /etc/os-release 等只读信息 # 删除 /dev 挂载,改用 node-exporter 内置的 devstat collector并在volumeMounts中强制readOnly: true:
volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true - name: rootfs mountPath: /host/root readOnly: true6.2 采集验证:直接 curl Node Exporter 指标端点
不依赖 Grafana,直接验证每个节点的 Node Exporter 是否正常工作:
# 在任意 master 节点执行,遍历所有 node IP for ip in 192.168.10.{87..90}; do echo "=== $ip ===" curl -s http://$ip:9100/metrics | grep -E "node_cpu_seconds_total|node_memory_MemFree_bytes" | head -3 done正常输出应包含:
# HELP node_cpu_seconds_total Seconds the cpu spent in each mode. # TYPE node_cpu_seconds_total counter node_cpu_seconds_total{cpu="0",mode="idle"} 1.234567e+06若某节点返回Connection refused,检查该节点node-exporterPod 是否 Running;若返回空,检查hostNetwork: true是否生效(kubectl get pod -n ops -o wide中 Pod IP 应与节点 IP 一致)。
6.3 关键指标告警阈值设定参考(基于 v1.20.4 实测)
| 指标 | PromQL 表达式 | 阈值 | 说明 |
|---|---|---|---|
| 节点负载过高 | 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) | > 90 | 持续 5 分钟 CPU 空闲率低于 10% |
| 内存不足 | 100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) | > 95 | 可用内存占比低于 5% |
| 磁盘满 | 100 * (node_filesystem_space_available_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) | < 10 | 根分区可用空间低于 10% |
| 节点 NotReady | kube_node_status_phase{phase="Unknown"} == 1 or kube_node_status_phase{phase="NotReady"} == 1 | 1 | 任一节点处于 Unknown 或 NotReady 状态 |
将上述表达式填入prometheus-rules.yaml的groups.rules,即可触发 Alertmanager 告警。
本文还有配套的精品资源,点击获取