K8s监控:kube-state-metrics+node-exporter+Prometheus的完整搭建
K8s监控:kube-state-metrics+node-exporter+Prometheus的完整搭建
适读人群:负责K8s集群可观测性建设的工程师 | 阅读时长:约25分钟 | 适用版本:K8s 1.22+、Prometheus 2.47+
开篇故事
我们集群有段时间总是出现神秘的Pod重启,没有规律,不知道哪个节点,不知道什么时间,重启后服务恢复正常,但用户已经感知到了。问题持续了三周,我们一直没法找到根因。
后来装上了完整的Prometheus监控栈,才发现规律:每次Pod重启都发生在凌晨3点到5点之间,对应的是批处理任务运行期间,某几台Node的内存使用率超过了95%,触发了OOM Kill。批处理任务内存用量估算不足,侵占了其他服务的资源。
有了监控,三分钟定位到了问题,调整了批处理任务的resource limit,问题消失。没有监控,可能还在黑暗中摸索。
今天把K8s监控栈的完整搭建方案写出来,从组件选型到告警规则,把我们踩过的坑都说清楚。
一、核心问题分析
K8s需要监控哪些层面
K8s的可观测性需要覆盖三个层次:
基础设施层(Node):CPU、内存、磁盘IO、网络IO等硬件指标,由node-exporter(DaemonSet方式部署,每个Node一个)采集。
K8s集群层(控制面和资源对象):Pod数量、Deployment状态、PVC使用情况、节点条件等K8s对象状态,由kube-state-metrics(单个Deployment)采集。注意:kube-state-metrics与metrics-server不同,后者是给HPA用的,前者是给监控用的。
应用层(业务指标):HTTP请求速率、响应时间、错误率等业务指标,由应用自身暴露(Spring Boot通过/actuator/prometheus端点),或由Prometheus的Pushgateway收集。
二、原理深度解析
整体架构
三、完整配置实现
使用kube-prometheus-stack一键部署(推荐)
# 使用Helm一键安装完整监控栈
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 安装kube-prometheus-stack(包含Prometheus、Grafana、node-exporter、kube-state-metrics、AlertManager)
helm install kube-prometheus-stack \
prometheus-community/kube-prometheus-stack \
-n monitoring \
--create-namespace \
-f monitoring-values.yaml自定义values.yaml配置
# monitoring-values.yaml
# kube-prometheus-stack的定制配置
# Prometheus配置
prometheus:
prometheusSpec:
# 数据保留时间
retention: 30d
# 数据存储
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: alicloud-disk-efficiency
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 500Gi
# 资源配置
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "2000m"
memory: "8Gi"
# 抓取间隔(默认30s,可以缩短到15s提高实时性)
scrapeInterval: "15s"
evaluationInterval: "15s"
# 允许Prometheus抓取任意namespace的ServiceMonitor
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
# Grafana配置
grafana:
adminPassword: "your-strong-password"
persistence:
enabled: true
storageClassName: alicloud-disk-efficiency
size: 10Gi
# 预安装仪表盘(K8s监控经典仪表盘)
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: default
folder: "K8s"
type: file
options:
path: /var/lib/grafana/dashboards/default
dashboards:
default:
kubernetes-cluster:
gnetId: 7249 # K8s集群概览
revision: 1
node-exporter:
gnetId: 1860 # Node Exporter完整仪表盘
revision: 37
spring-boot:
gnetId: 12900 # Spring Boot监控仪表盘
revision: 1
# AlertManager配置
alertmanager:
alertmanagerSpec:
storage:
volumeClaimTemplate:
spec:
storageClassName: alicloud-disk-efficiency
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
config:
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'dingtalk'
routes:
- match:
severity: critical
receiver: 'pagerduty'
- match:
severity: warning
receiver: 'dingtalk'
receivers:
- name: 'dingtalk'
webhook_configs:
- url: 'http://dingtalk-webhook:8060/dingtalk/ops/send'
send_resolved: true
- name: 'pagerduty'
pagerduty_configs:
- routing_key: 'your-pagerduty-key'
# node-exporter:每个Node部署一个
nodeExporter:
enabled: true
resources:
requests:
cpu: "50m"
memory: "32Mi"
limits:
cpu: "200m"
memory: "128Mi"
# kube-state-metrics配置
kubeStateMetrics:
enabled: true为Spring Boot服务配置ServiceMonitor
# servicemonitor-spring-boot.yaml
# ServiceMonitor是Prometheus Operator的CRD,告诉Prometheus抓哪些服务
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service-monitor
namespace: production
labels:
# 这个label必须匹配Prometheus的serviceMonitorSelector
release: kube-prometheus-stack
spec:
# 匹配哪些namespace的Service
namespaceSelector:
matchNames:
- production
# 匹配哪些Service
selector:
matchLabels:
app: order-service
# 端点配置
endpoints:
- port: http # Service的端口名称(要和Service.spec.ports[].name匹配)
path: /actuator/prometheus
interval: 15s
scrapeTimeout: 10s
# 如果需要认证
# bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token确保Spring Boot的Service有对应的标签和端口名:
# service-order.yaml
apiVersion: v1
kind: Service
metadata:
name: order-service
namespace: production
labels:
app: order-service
spec:
selector:
app: order-service
ports:
- name: http # 端口名必须和ServiceMonitor里的port一致
port: 8080
targetPort: 8080关键告警规则配置
# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: java-service-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: java-service
interval: 30s
rules:
# Pod重启频率告警
- alert: PodRestartingTooOften
expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} 重启频率过高"
description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 过去15分钟重启超过7次"
# Pod不可用告警
- alert: PodNotReady
expr: kube_pod_status_ready{condition="false"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} 不可用超过5分钟"
# Node内存压力告警
- alert: NodeMemoryPressure
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 15
for: 5m
labels:
severity: warning
annotations:
summary: "Node {{ $labels.instance }} 内存使用率超过85%"
description: "当前可用内存仅剩 {{ $value | humanize }}%"
# JVM堆内存告警(需要Spring Boot暴露JVM指标)
- alert: JvmHeapUsageHigh
expr: >
(jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "服务 {{ $labels.application }} JVM堆内存超过85%"
# HTTP错误率告警
- alert: HttpErrorRateHigh
expr: >
rate(http_server_requests_seconds_count{status=~"5.."}[5m]) /
rate(http_server_requests_seconds_count[5m]) * 100 > 5
for: 2m
labels:
severity: critical
annotations:
summary: "服务 {{ $labels.application }} 5xx错误率超过5%"
# 部署副本数不足告警
- alert: DeploymentReplicasNotAvailable
expr: >
kube_deployment_status_replicas_available <
kube_deployment_spec_replicas
for: 5m
labels:
severity: critical
annotations:
summary: "Deployment {{ $labels.deployment }} 可用副本数不足"四、生产最佳实践
Prometheus存储容量规划
Prometheus的存储需求取决于:时间序列数量、采集频率、保留时间。经验公式:
每个时间序列每2字节/秒(默认压缩后)。
举例:1000个服务,每个服务100个指标序列,共100,000个序列,15秒采集间隔,保留30天:
100,000 × 2B/s × (30×24×3600)s ≈ 518GB
根据实际情况预留20%额外空间,建议配置600Gi的PVC。
分层告警策略
告警太多等于没有告警,要按紧急程度分层:
P1(立即处理,1分钟内响应):服务完全不可用、核心接口错误率超过10%、数据库连接失败。
P2(30分钟内处理):服务降级、错误率升高(1%~10%)、副本数减少、节点内存>90%。
P3(工作时间处理):性能下降趋势、资源使用率持续攀升、非核心服务告警。
五、踩坑实录
坑一:Prometheus存储写满,历史数据全部丢失
第一次部署时,只给Prometheus分配了50Gi的存储,两个月后存储写满,Prometheus开始报错,无法写入新数据。更糟糕的是,存储写满时Prometheus会删除最旧的数据来腾空间,导致历史数据丢失。
修复后教训:Prometheus的存储要定期检查,并设置告警:
# 告警:Prometheus存储使用超过80%
- alert: PrometheusStorageSpaceLow
expr: >
(prometheus_tsdb_head_chunks_storage_size_bytes + prometheus_tsdb_blocks_loaded_bytes) /
(prometheus_tsdb_head_chunks_storage_size_bytes + prometheus_tsdb_blocks_loaded_bytes +
prometheus_tsdb_wal_storage_size_bytes) > 0.8生产建议:至少500Gi起步,开启WAL压缩(--storage.tsdb.wal-compression),按实际增长情况每季度评估。
坑二:kube-state-metrics内存OOM
集群规模扩大后(800个Pod),kube-state-metrics的内存使用从默认的256Mi上涨到了1GB+,触发OOM Kill,导致Prometheus无法采集K8s对象状态指标,告警漏报。
kube-state-metrics的内存消耗和集群规模成正比,大集群需要调高limits:
kubeStateMetrics:
resources:
requests:
cpu: "100m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "2Gi" # 按集群规模调整坑三:ServiceMonitor配置了但Prometheus没有采集
配了ServiceMonitor,但Prometheus的targets列表里没有出现对应的服务。排查了半天,发现是两个问题:
一是ServiceMonitor的label没有匹配Prometheus的serviceMonitorSelector。kube-prometheus-stack默认只处理有release: kube-prometheus-stack标签的ServiceMonitor,忘记加这个label就会被忽略。
二是Service的端口名和ServiceMonitor里配的不一致。ServiceMonitor的endpoints[].port必须是Service端口的名称(spec.ports[].name),不是端口号。
六、总结
K8s监控栈的搭建,kube-prometheus-stack(原kube-prometheus)是目前最成熟的一键方案,包含了所有必要组件,经过了大规模生产验证。除非有特殊需求,直接用这个方案就行,不用自己拼各个组件。
监控搭完只是第一步,关键是告警规则的调优和仪表盘的建设。告警太少会漏报,太多会告警疲劳,找到合适的阈值需要根据实际情况持续迭代。
对于Java服务,Spring Boot Actuator提供了完整的Prometheus指标端点,覆盖JVM、HTTP、数据库连接池等核心指标,配合ServiceMonitor可以实现应用级别的深度监控,这是K8s基础设施监控之外不可缺少的一层。
