Prometheus + Grafana 监控体系
本页目录
场景
生产环境 K8s 集群需要全方位可观测性:集群资源、应用指标、业务指标、告警通知、仪表盘可视化。
栈选择:Prometheus(采集+存储+查询)+ Grafana(可视化)+ Alertmanager(告警路由去重)+ kube-state-metrics + node-exporter + kube-prometheus-stack(Helm 一键部署)。
前置条件
- K8s 1.20+,Helm 3
- 集群资源够:Prometheus 约 2C4G+50G 存储 / Grafana 约 500M
- 存储类支持 PVC(Prometheus 需持久化)
- 域名 + TLS(Grafana 对外访问需 HTTPS)
配置
1. 一键部署 kube-prometheus-stack
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 创建命名空间
kubectl create namespace monitoring
# 生产环境推荐配置
helm install kps prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace \
--version 45.0.0 \
-f values-prod.yaml
values-prod.yaml 关键配置
# prometheus 配置
prometheus:
prometheusSpec:
retention: 30d # 保留 30 天
retentionSize: 50GB # 最大 50G
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: ssd # 必须有快速存储类
resources:
requests:
storage: 100Gi
resources:
requests:
cpu: 1000m
memory: 4Gi
limits:
cpu: 2000m
memory: 8Gi
# 远程存储(可选,长期保留)
# remoteWrite:
# - url: https://thanos-receive.example.com/api/v1/receive
# 服务发现额外配置
additionalScrapeConfigs:
- job_name: 'myapp'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# alertmanager 配置
alertmanager:
alertmanagerSpec:
replicas: 2
storage:
volumeClaimTemplate:
spec:
storageClassName: ssd
resources:
requests:
storage: 10Gi
config:
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'pagerduty'
continue: true
- match:
severity: warning
receiver: 'slack'
receivers:
- name: 'default'
email_configs:
- to: 'ops@example.com'
send_resolved: true
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts'
send_resolved: true
- name: 'pagerduty'
pagerduty_configs:
- service_key: 'xxx'
# grafana 配置
grafana:
enabled: true
persistence:
type: pvc
storageClassName: ssd
size: 10Gi
adminPassword: "StrongPass123"
sidecar:
dashboards:
enabled: true
searchNamespace: ALL
datasources:
enabled: true
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: 'default'
orgId: 1
folder: 'Kubernetes'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards/default
# 预装常用仪表盘
dashboards:
default:
k8s-cluster:
gnetId: 315
revision: 3
datasource: Prometheus
k8s-pods:
gnetId: 6417
datasource: Prometheus
node-exporter:
gnetId: 1860
datasource: Prometheus
# node-exporter(主机指标)
prometheus-node-exporter:
enabled: true
resources:
requests:
cpu: 100m
memory: 128Mi
# kube-state-metrics(K8s 对象状态)
kubeStateMetrics:
enabled: true
# kubelet 监控
kubelet:
enabled: true
serviceMonitor:
enabled: true
# 安装
helm install kps prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace \
-f values-prod.yaml --version 45.0.0
2. 验证部署
# 检查组件
kubectl get pods -n monitoring
# Prometheus UI
kubectl port-forward -n monitoring svc/kps-kube-prometheus-prometheus 9090:9090
# 浏览器访问 http://localhost:9090
# Grafana
kubectl port-forward -n monitoring svc/kps-grafana 3000:80
# 用户 admin / StrongPass123
# 浏览器访问 http://localhost:3000
3. 域名 + HTTPS(生产环境)
# ingress-grafana.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: monitoring
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
ingressClassName: nginx
tls:
- hosts:
- grafana.example.com
secretName: grafana-tls
rules:
- host: grafana.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kps-grafana
port:
number: 80
kubectl apply -f ingress-grafana.yaml
3. 常用 PromQL(复制即用)
集群资源概览
# 集群 CPU 使用率
sum(rate(container_cpu_usage_seconds_total{container!="",container!="POD"}[5m]))
/ sum(kube_node_status_capacity{resource="cpu"}) * 100
# 集群内存使用率
sum(container_memory_working_set_bytes{container!="",container!="POD"})
/ sum(kube_node_status_capacity{resource="memory"}) * 100
# 节点 CPU 使用率
sum by (instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m]))
/ sum by (instance) (rate(node_cpu_seconds_total[5m])) * 100
# Pod 重启次数
increase(kube_pod_container_status_restarts_total[1h])
# Pod OOMKilled
kube_pod_container_status_terminated_reason{reason="OOMKilled"} == 1
# PVC 使用率
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes * 100
应用指标(需应用暴露 /metrics)
# 请求 QPS
sum(rate(http_requests_total{job="myapp"}[5m])) by (path, method)
# 请求延迟 P99
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{job="myapp"}[5m])) by (le, path))
# 错误率
sum(rate(http_requests_total{job="myapp",code=~"5.."}[5m]))
/ sum(rate(http_requests_total{job="myapp"}[5m]))
4. Grafana 仪表盘导入
# UI 导入:+ Import → Dashboard JSON → 上传 JSON 文件
# 或配置 sidecar 自动发现
# 常用 Dashboard ID(Grafana.com)
# 315: Kubernetes Cluster Overview
# 315: Kubernetes Cluster Monitoring
# 6417: Kubernetes Pods
# 1860: Node Exporter Full
# 13948: Kubernetes / Compute Resources / Cluster
# 13946: Kubernetes / Compute Resources / Namespace
# 14282: Kubernetes / Network
# 15661: Kubernetes / Persistent Volumes
# 11074: etcd
# 12559: Kubernetes / API Server
# 11078: Kubernetes / Scheduler
# 11079: Kubernetes / Controller Manager
# 11102: Kubernetes / Kubelet
5. Alertmanager 告警规则(常用)
# custom-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: custom-rules
namespace: monitoring
spec:
groups:
- name: k8s.rules
rules:
- alert: KubePodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} 崩溃重启"
description: "{{ $labels.namespace }}/{{ $labels.pod }} 最近 15 分钟重启 {{ $value }} 次"
- alert: KubePodNotReady
expr: kube_pod_status_phase{phase="Running"} == 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} 长时间 NotReady"
description: "{{ $labels.namespace }}/{{ $labels.pod }} 超过 5 分钟 NotReady"
- alert: KubeCPUOvercommit
expr: sum(kube_pod_container_resource_requests_cpu_cores) / sum(kube_node_status_capacity_cpu_cores) > 1.5
for: 10m
labels:
severity: warning
annotations:
summary: "CPU 过度承诺"
description: "集群 CPU 请求总量超过容量 1.5 倍"
- alert: KubeMemoryOvercommit
expr: sum(kube_pod_container_resource_requests_memory_bytes) / sum(kube_node_status_capacity_memory_bytes) > 1.5
for: 10m
labels:
severity: warning
annotations:
summary: "内存过度承诺"
- alert: PVCUsageHigh
expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "PVC 使用率超过 85%"
description: "{{ $labels.persistentvolumeclaim }} 使用率 {{ $value | humanizePercentage }}"
- alert: NodeDiskPressure
expr: kube_node_status_condition{condition="DiskPressure",status="true"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.node }} 磁盘压力"
- alert: NodeMemoryPressure
expr: kube_node_status_condition{condition="MemoryPressure",status="true"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.node }} 内存压力"
- alert: PodOOMKilled
expr: kube_pod_container_status_terminated_reason{reason="OOMKilled"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} OOMKilled"
description: "{{ $labels.namespace }}/{{ $labels.pod }} 被 OOM Killer 杀掉"
- name: node.rules
rules:
- alert: NodeDown
expr: up{job="node-exporter"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 宕机"
description: "{{ $labels.instance }} 超过 2 分钟无心跳"
- alert: NodeCPUHigh
expr: (1 - avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} CPU 持续高负载"
- alert: NodeMemoryHigh
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 内存使用率超过 90%"
- alert: NodeDiskFull
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 磁盘剩余不足 10%"
kubectl apply -f custom-rules.yaml -n monitoring
5. 推送到钉钉 / 企业微信 / 飞书
# alertmanager 配置片段
receivers:
- name: 'dingtalk'
webhook_configs:
- url: 'https://oapi.dingtalk.com/robot/send?access_token=xxx'
send_resolved: true
http_config:
basic_auth:
username: ''
password: ''
- name: 'wechat'
webhook_configs:
- url: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx'
send_resolved: true
6. Grafana 仪表盘变量(动态筛选)
{
"type": "query",
"name": "namespace",
"label": "Namespace",
"datasource": "Prometheus",
"query": "label_values(kube_pod_info, namespace)",
"multi": true,
"includeAll": true,
"allValue": ".*"
}
面板查询引用:
sum(rate(container_cpu_usage_seconds_total{namespace=~"$namespace",container!="",container!="POD"}[5m])) by (pod)
7. 长期存储(Thanos / VictoriaMetrics)
# Thanos Sidecar(对接对象存储)
prometheus:
prometheusSpec:
retention: 24h # 本地只留 24h
retentionSize: 50GB
# 远程写入 Thanos
remoteWrite:
- url: https://thanos-receive.example.com/api/v1/receive
# VictoriaMetrics 单机版(轻量替代)
docker run -d --name vmsingle \
-p 8428:8428 \
-v /data/victoria:/victoria \
victoriametrics/victoria-metrics \
-retentionPeriod=90d \
-storageDataPath=/victoria \
-httpListenAddr=:8428
7. 常用运维操作
# Prometheus 重载配置(不重启)
curl -X POST http://prometheus:9090/-/reload
# 清理 Prometheus 数据
kubectl exec -n monitoring prometheus-0 -- rm -rf /prometheus/*
kubectl delete pod -n monitoring prometheus-0
# Grafana 重置密码
kubectl exec -n monitoring deploy/kps-grafana -- grafana-cli admin reset-admin-password NewPass123
# 导出 Grafana Dashboard
curl -H "Authorization: Bearer $TOKEN" \
http://grafana/api/dashboards/uid/xxx > dashboard.json
# 备份 Grafana
kubectl exec -n monitoring deploy/kps-grafana -- \
tar czf - /var/lib/grafana | gzip > grafana-backup.tar.gz
坑
| 坑 | 现象 | 避坑 |
|---|---|---|
| Prometheus OOM | 内存不足被 Kill | 设置 resources.limits.memory、开启 --storage.tsdb.retention.size |
| 告警风暴 | 一次故障发几百条告警 | 配置 group_by、group_wait、group_interval、repeat_interval |
| Grafana 看不了历史 | PVC 没挂 / 没开启 persistence | grafana.persistence.enabled=true + PVC |
| 告警风暴刷屏钉钉 | 告警没分组、没抑制 | 配置 group_by、group_wait、repeat_interval、inhibit_rules |
| Prometheus 磁盘满 | 磁盘满导致写入失败 | 设置 retentionSize、开启 remoteWrite |
| Grafana 看不到数据 | datasource 没配 / namespace 没选 | 检查 Datasource 设置、namespace 变量 |
| 告警不触发 | 表达式写错 / for 时长太长 | 用 promtool check rules 校验、缩短 for |
| Alertmanager HA 不生效 | 没配 gossip / 没共享存储 | 配置 cluster、peer、storage |
| Grafana 看不到历史数据 | 没配 PVC / retention 太短 | grafana.persistence.enabled=true + retention |
| Namespace 变量不生效 | 变量没绑定 datasource / label 名不对 | 变量 Query 用 label_values(metric, label) |
| 告警恢复不发 Resolved | send_resolved: false / 无 Resolved 模板 |
send_resolved: true + 模板里写 {{ if eq .Status "resolved" }} |