Prometheus 监控体系

本文整合了 Prometheus 监控体系的完整实践:从监控原理与架构入手,依次介绍基于 kube-prometheus-stack 的部署、PromQL 查询语言、Exporter 与 Pushgateway 数据采集、服务发现(文件 / ServiceMonitor / PodMonitor / Probe)、告警(AlertManager 规则与企业微信通知)、Grafana 个性化定制,以及常见监控问题的排查方法。

1. 监控概述

1.1 常见的监控数据类型

监控数据主要分为两大类:

  1. 应用级别的数据:了解应用自身的监控指标,例如 CPU 使用率、内存使用率、请求失败率等。
  2. 集群级别的数据:了解集群自身的运行状况,例如节点的 CPU、内存、网络吞吐等指标,及时掌握 kubelet 的负载情况与 Kubernetes 各组件的状态。

此外,运行过程中 Kubernetes 产生的各种 event 数据,以及 Deployment、StatefulSet、DaemonSet、Pod 等对象的状态、资源请求、调用和 API 延迟等指标,也是必须收集的内容。

1.2 常见的监控数据收集工具

  1. cAdvisor:至今仍编译在 kubelet 里,容器级指标经 kubelet 的 /metrics/cadvisor 暴露。v1.12 移除的只是它的独立 Web UI(4194 端口)。
  2. Heapster:通过 cAdvisor 收集汇总各种性能数据,是自动伸缩(Autoscale)所依赖的组件,现已废弃,由 metrics-server 替换。
  3. metrics-server:集群范围内的监控数据聚合工具,可以通过标准的 Kubernetes API 访问这些监控数据。
  4. kube-state-metrics:监听 kube-apiserver 中的数据,生成有关资源对象的状态指标,例如 Deployment、Node、Pod 的状态(这是它与 metrics-server 的最大区别)。
  5. node-exporter:Prometheus 的一个 exporter,用于获取节点级别的监控指标。
  6. Prometheus + Grafana:推荐方案,也是本文的核心。

2. Prometheus 架构与原理

prometheus

Prometheus 项目工作的核心是 Prometheus Server,它使用 Pull(拉取) 的方式搜集被监控对象的 Metrics 数据(监控指标数据),再把这些数据写进自带的本地 TSDB--storage.tsdb.path 下的 WAL 加每 2 小时一个 block,默认保留 15 天),以便后续按照时间进行检索。OpenTSDB、InfluxDB 这类是外部远端存储,只能通过 remote_write/remote_read 对接,不是 Prometheus 的内置后端;要长期存储或全局视图,一般再挂 Thanos、VictoriaMetrics。有了这套核心监控机制,Prometheus 的其余组件都是用来配合它运行的。

2.1 核心组件

  1. Pushgateway:允许被监控对象以 Push 的方式向 Prometheus 推送 Metrics 数据。主要用于 Job 类程序——这类程序存活时间较短,不适合采用拉取的方式。
  2. Alertmanager:根据 Metrics 信息灵活地设置报警。
  3. Grafana:前台界面,对外暴露可灵活配置的监控数据可视化界面。
  4. Exporters:用于一些第三方服务,通过转换提供符合 Prometheus 规范的 Metrics 信息,例如 Node Exporter 提供节点相关的信息。
  5. Service Discovery:用于服务发现,获取服务对应的 EndPoint 用于数据采集,默认支持 DNS、Consul、Kubernetes 等多种发现方案,可自动发现新服务并纳入监控,减少手动配置。
  6. Client Library:为各种语言提供的客户端库,提供 HTTP 服务的 Metrics 接口,当 Prometheus Server 拉取时提供实时的 Metrics 数据。

2.2 Metrics 数据类型

通过 /metrics 接口可以查看某个目标暴露的所有指标:

1
curl 10.56.67.105:9090/metrics

Prometheus 的指标分为四种类型:

  1. Counter:只增不减的计数器,例如 http_requests_totalnode_cpu
  2. Gauge:可增可减,例如主机的 CPU、内存、磁盘使用率,当前的并发量。
  3. Histogram:近似的百分比估算数值,用于统计和分析样本的分布情况(例如请求响应时间在 0.5s、0.5~1s、1s 以上各自的占比)。
  4. Summary:分位数由客户端在滑动窗口里算好后直接暴露。关键差别不在”精准还是近似”,而在分位数在哪算、能不能聚合:Summary 的分位数在 Prometheus 侧没法再聚合(多个实例的 p99 求平均在数学上没有意义),而且埋点时就定死了分位点,事后改不了。Histogram 暴露的是可累加的 bucket,服务端用 histogram_quantile 估算,精度取决于 bucket 划分,但能跨实例聚合、分位点也能事后调整。所以多副本服务通常应该优先选 Histogram。

Exporter 提交给 Prometheus 的 key-value 中,value 必须是浮点数或整数。

2.3 kube-aggregator 与聚合 API

kube-aggregator

kubeadm 默认开启的是聚合层本身,但 metrics.k8s.io/v1beta1 这个 APIService 是由 metrics-server 注册的,而 kubeadm 并不会帮你装它。没装时访问下面的地址只会得到 404,先确认 kubectl get apiservices v1beta1.metrics.k8s.io 是 True 再来取指标:

1
http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/namespaces/<namespace-name>/pods/<pod-name>

当 Kubernetes 的 API Server 开启了 Aggregator 模式之后,访问 apis/metrics.k8s.io/v1beta1 时,实际访问到的是一个叫作 kube-aggregator 的代理。而 kube-apiserver 正是这个代理的一个后端,Metrics Server 则是另一个后端。在这个机制下,还可以添加更多的后端给 kube-aggregator。所以 kube-aggregator 其实就是一个根据 URL 选择具体 API 后端的代理服务器,通过这种方式可以很方便地扩展 Kubernetes 的 API。

3. 部署

在 Kubernetes 中推荐使用 Prometheus Operator / kube-prometheus-stack 进行部署,它以 Operator + CRD 的方式统一管理 Prometheus、Alertmanager、Grafana 及各类采集配置。

使用 Helm 可以方便地修改配置内容。

3.1 使用 Helm 部署 kube-prometheus-stack

配置思路是安装前基于文件修改,可以先用 --dry-run 渲染出最终结果进行检查:

1
2
3
4
/etc/kubeasz/bin/helm upgrade prometheus --install -n monitor \
-f prom-values.yaml \
/etc/kubeasz/roles/cluster-addon/files/kube-prometheus-stack-45.23.0.tgz \
--dry-run > prometheus.yaml

prom-values.yaml 的完整示例如下,涵盖 Alertmanager(含抑制规则与路由)、Grafana、各控制平面组件(apiserver / kubelet / controller-manager / scheduler / etcd / proxy / coreDns)的抓取,以及 Operator 与 Prometheus 的存储配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
## Provide a k8s version to auto dashboard import script example: kubeTargetVersionOverride: 1.16.6
kubeTargetVersionOverride: "1.27.2"

## Configuration for alertmanager
alertmanager:
alertmanagerSpec:
image:
registry: easzlab.io.local:5000
storage:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
service:
nodePort: 30902
type: NodePort

config:
global:
resolve_timeout: 5m
inhibit_rules:
- equal:
- namespace
- alertname
source_matchers:
- severity = critical
target_matchers:
- severity =~ warning|info
- equal:
- namespace
- alertname
source_matchers:
- severity = warning
target_matchers:
- severity = info
- equal:
- namespace
source_matchers:
- alertname = InfoInhibitor
target_matchers:
- severity = info
receivers:
- name: web.hook
webhook_configs:
- send_resolved: false
url: http://prometheus-webhook-adapter:8060/adapter/wx
- name: "null"
route:
group_by:
- namespace
group_interval: 5m
group_wait: 30s
receiver: web.hook
repeat_interval: 12h
routes:
- matchers:
- alertname = "InfoInhibitor"
receiver: "null"
- matchers:
- alertname = "Watchdog"
receiver: "web.hook"
- matchers:
- "severity = critical"
receiver: "web.hook"
templates:
- /etc/alertmanager/config/*.tmpl

## Using default values from https://github.com/grafana/helm-charts/blob/main/charts/grafana/values.yaml
grafana:
defaultDashboardsTimezone: CST
enabled: true
adminUser: admin
adminPassword: password
image:
repository: easzlab.io.local:5000/prometheus/grafana
service:
nodePort: 30903
type: NodePort
sidecar:
image:
repository: easzlab.io.local:5000/prometheus/k8s-sidecar
skipTlsVerify: true

datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
uid: prometheus
url: http://prometheus-kube-prometheus-prometheus:9090
access: proxy
isDefault: true
jsonData:
httpMethod: POST
timeInterval: 30s

persistence:
type: pvc
enabled: true
storageClassName: local-path
accessModes:
- ReadWriteOnce
size: 10Gi

grafana.ini:
paths:
data: /var/lib/grafana/
logs: /var/log/grafana
plugins: /var/lib/grafana/plugins
provisioning: /etc/grafana/provisioning
analytics:
check_for_updates: true
log:
mode: console
grafana_net:
url: https://grafana.net
users:
default_theme: light
security:
allow_embedding: true
auth.anonymous:
enabled: true
org_role: Viewer
auth.basic:
enabled: false
server:
root_url: http://localhost/test/grafana/
serve_from_sub_path: true

## Component scraping the kube api server
kubeApiServer:
enabled: true

## Component scraping the kubelet and kubelet-hosted cAdvisor
kubelet:
enabled: true

## Component scraping the kube controller manager
kubeControllerManager:
enabled: true
endpoints:
- 172.20.19.51
- 172.20.19.52
- 172.20.19.53
service:
port: 10257
targetPort: 10257
serviceMonitor:
https: true
insecureSkipVerify: true
serverName: localhost

## Component scraping coreDns. Use either this or kubeDns
coreDns:
enabled: true

## Component scraping etcd
kubeEtcd:
enabled: true
endpoints:
- 172.20.19.51
- 172.20.19.52
- 172.20.19.53
service:
port: 2379
targetPort: 2379
serviceMonitor:
scheme: https
insecureSkipVerify: true
serverName: localhost
caFile: /etc/prometheus/secrets/etcd-client-cert/etcd-ca
certFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client
keyFile: /etc/prometheus/secrets/etcd-client-cert/etcd-client-key

## Component scraping kube scheduler
kubeScheduler:
enabled: true
endpoints:
- 172.20.19.51
- 172.20.19.52
- 172.20.19.53
service:
port: 10259
targetPort: 10259
serviceMonitor:
https: true
insecureSkipVerify: true

## Component scraping kube proxy
kubeProxy:
enabled: true
endpoints:
- 172.20.19.51
- 172.20.19.52
- 172.20.19.53
- 172.20.19.54
- 172.20.19.55

kubeStateMetrics:
enabled: true

## Configuration for kube-state-metrics subchart
kube-state-metrics:
image:
registry: easzlab.io.local:5000
repository: prometheus/kube-state-metrics

## Configuration for prometheus-node-exporter subchart
prometheus-node-exporter:
image:
registry: easzlab.io.local:5000
repository: prometheus/node-exporter

## Manages Prometheus and Alertmanager components
prometheusOperator:
enabled: true
admissionWebhooks:
enabled: true
patch:
enabled: true
image:
registry: easzlab.io.local:5000
repository: prometheus/kube-webhook-certgen
tag: v1.5.1
image:
registry: easzlab.io.local:5000
repository: prometheus/prometheus-operator
service:
nodePort: 30899
nodePortTls: 30900
type: NodePort
prometheusConfigReloader:
image:
registry: easzlab.io.local:5000
repository: prometheus/prometheus-config-reloader

## Deploy a Prometheus instance
prometheus:
enabled: true
service:
nodePort: 30901
type: NodePort

prometheusSpec:
image:
registry: easzlab.io.local:5000
replicas: 1
secrets:
- etcd-client-cert

additionalScrapeConfigsSecret:
enabled: true
name: additional-scrape-configs
key: prometheus-additional.yaml

storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi

确认无误后正式安装(若需要重装可先 uninstall):

1
2
3
4
5
/etc/kubeasz/bin/helm uninstall prometheus -n monitor

/etc/kubeasz/bin/helm upgrade prometheus --install -n monitor \
-f /etc/kubeasz/clusters/k8s-01/yml/prom-values.yaml \
/etc/kubeasz/roles/cluster-addon/files/kube-prometheus-stack-45.23.0.tgz

3.2 部署验证

安装完成后,检查 monitoring(或自定义的 monitor)命名空间下的 Service 与 Pod 是否就绪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@test-249 manifests]# kubectl get svc -n monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
alertmanager-main NodePort 10.56.182.15 <none> 9093:31714/TCP 12d
alertmanager-operated ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 12d
blackbox-exporter ClusterIP 10.56.38.158 <none> 9115/TCP,19115/TCP 12d
grafana NodePort 10.56.185.87 <none> 3000:31996/TCP 12d
kube-state-metrics ClusterIP None <none> 8443/TCP,9443/TCP 12d
node-exporter ClusterIP None <none> 9100/TCP 12d
prometheus-adapter ClusterIP 10.56.34.10 <none> 443/TCP 12d
prometheus-k8s NodePort 10.56.67.105 <none> 9090:31788/TCP 12d
prometheus-operated ClusterIP None <none> 9090/TCP 12d
prometheus-operator ClusterIP None <none> 8443/TCP 12d
# 下面这段输出来自 kube-prometheus 原始 manifests 版(命名空间 monitoring、资源名 prometheus-k8s / alertmanager-main),
# 与上面 Helm 装出来的那一套(release=prometheus、-n monitor、prometheus-kube-prometheus-prometheus)资源名和副本数都不同,仅作对照。
[root@test-249 manifests]# kubectl get pod -n monitoring
NAME READY STATUS RESTARTS AGE
alertmanager-main-0 2/2 Running 0 11d
alertmanager-main-1 2/2 Running 0 11d
alertmanager-main-2 2/2 Running 0 11d
blackbox-exporter-556d889b47-khgck 3/3 Running 0 12d
grafana-665447c488-245vl 1/1 Running 0 43h
kube-state-metrics-6f4dfb9ffb-rj2df 3/3 Running 0 12d
node-exporter-9tgm9 2/2 Running 0 12d
node-exporter-gvjsr 2/2 Running 0 12d
node-exporter-klprn 2/2 Running 0 12d
node-exporter-rdkvx 2/2 Running 0 12d
prometheus-adapter-767f58977c-2w9jn 1/1 Running 0 12d
prometheus-k8s-0 2/2 Running 1 11d
prometheus-k8s-1 2/2 Running 1 11d
prometheus-operator-764cb46c94-8ds59 2/2 Running 0 12d

3.3 安装后的定制入口:Operator CRD

安装后主要通过以下 CRD 进行动态定制(无需重启,Operator 会自动 reload):

1
2
3
4
5
6
7
8
alertmanagerconfigs.monitoring.coreos.com
alertmanagers.monitoring.coreos.com
podmonitors.monitoring.coreos.com
probes.monitoring.coreos.com
prometheuses.monitoring.coreos.com
prometheusrules.monitoring.coreos.com
servicemonitors.monitoring.coreos.com
thanosrulers.monitoring.coreos.com

此外还有各种 ConfigMap 和 Secret。以 Alertmanager 为例,动态追加的配置会与最初的文件合并,一起形成新的配置:

1
kubectl edit alertmanagerconfigs.monitoring.coreos.com -n monitor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
labels:
alertmanagerConfig: example
name: config-example
namespace: monitor
spec:
receivers:
- name: webhook
webhookConfigs:
- url: http://example.com/
route:
groupBy:
- job
groupInterval: 5m
groupWait: 30s
receiver: webhook
repeatInterval: 12h

后文的服务发现(ServiceMonitor / PodMonitor / Probe)与告警(PrometheusRule)均通过这些 CRD 完成。注意:这些自定义资源需要带上 release: prometheus 标签才能被 Prometheus 匹配,其选择器配置如下:

1
kubectl get prometheus -n monitor prometheus-kube-prometheus-prometheus -o yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
spec:
podMonitorSelector:
matchLabels:
release: prometheus
probeSelector:
matchLabels:
release: prometheus
ruleSelector:
matchLabels:
release: prometheus
serviceMonitorSelector:
matchLabels:
release: prometheus

4. PromQL 查询语言

4.1 向量与选择器

  • 瞬时向量:包含该时间序列中最新的一个样本值。
  • 区间向量:一段时间范围内的数据。

offset 用于查看多少时间之前的数据,例如 offset 30m

标签(Labelsets)选择器:

1
2
3
4
5
6
7
8
9
10
11
# 过滤出具有 handler="/login" 的 label 的数据
http_request_total{handler="/login"}

# 正则匹配
http_request_total{handler=~".*login.*"}

# 剔除某个 label
http_request_total{handler!~".*login.*"}

# 匹配两个值
http_request_total{handler=~"/login|/password"}

4.2 运算符

数学运算:+ - * / % ^。例如查看主机内存总大小(Mi):

1
2
3
node_memory_MemTotal_bytes / 1024 / 1024

node_memory_MemTotal_bytes / 1024 / 1024 < 3000

集合运算:andorunless(排除):

1
2
3
node_memory_MemTotal_bytes / 1024 / 1024 <= 2772 or node_memory_MemTotal_bytes / 1024 / 1024 == 3758.59765625

node_memory_MemTotal_bytes / 1024 / 1024 >= 2772 unless node_memory_MemTotal_bytes / 1024 / 1024 == 3758.59765625

运算符优先级从高到低为:^(右结合)→ * / % atan2+ -== != <= < >= >and unlessor。注意 and/unlessor 高,a and b or c 实际是 (a and b) or c

4.3 聚合操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 求和
sum(node_memory_MemTotal_bytes) / 1024^2

# 根据某个字段进行统计
sum(http_request_total) by (statuscode, handler)

# 最小值 / 最大值 / 平均值
min(node_memory_MemTotal_bytes)
max(node_memory_MemTotal_bytes)
avg(node_memory_MemTotal_bytes)

# 标准差 stddev / 标准差异 stdvar
stddev(node_memory_MemTotal_bytes)

# 计数
count(http_request_total)

# 对 value 进行统计计数
count_values("count", node_memory_MemTotal_bytes)

# 取前 N 条 / 后 N 条时序
topk(5, sum(http_request_total) by (statuscode, handler))
bottomk(3, sum(http_request_total) by (statuscode, handler))

# 取中位数
quantile(0.5, http_request_total)

4.4 内置函数

指标的增长率相关函数:

1
2
3
# 某指标的增长量 / 增长率
increase(http_request_total{job="grafana"}[1h]) / 3600
rate(http_request_total{job="grafana"}[1h])
  • rate:区间内的平均增长率,适合观察长期趋势与告警规则;由于取平均,会有“长尾效应”。
  • irate:瞬时增长率,取最后两个数据点计算,不适合用于长期趋势分析或告警规则。

其他常用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 预测统计:预测 4 小时后磁盘可用空间是否会小于 0
predict_linear(node_filesystem_avail_bytes{mountpoint="/",fstype!~"tmpfs"}[1h], 4*3600) < 0

# node_filesystem_files_free 里的 files 指的是 inode,拿它预测的是 inode 会不会耗尽,跟空间无关:
predict_linear(node_filesystem_files_free{mountpoint="/"}[1d], 4*3600) < 0

# absent():判断数据是否在正常采集。样本不为空返回 no data,为空则返回 1
absent(up{job="node-exporter"})

# 去除小数点:ceil() 向上取整(2.79 -> 3),floor() 向下取整(2.79 -> 2)
ceil(node_load1)
floor(node_load1)

# delta():差值,例如 free 内存差异
delta(node_memory_MemFree_bytes[2h])

# 排序:sort 正序,sort_desc 倒序
sort(node_memory_MemFree_bytes)

# label_join:将一个或多个 label 的值拼接后赋值给新 label
label_join(node_filesystem_files_free, "new_label", ",", "instance", "mountpoint")

# label_replace:根据某个 label 值正则匹配后赋值给新 label
label_replace(node_filesystem_files_free, "host", "$2", "instance", "(.*)-(.*)")

5. 数据采集:Exporter 与 Pushgateway

5.1 Exporter

Exporter 需要满足以下要求:

  1. 是 HTTP 服务器,可以响应外部发来的 HTTP GET 请求。
  2. 后台运行,并可定期抓取本地的监控数据。
  3. 提供给 Prometheus Server 的内容需要符合 Prometheus 规定的 metrics 类型。
  4. key-value 中 value 必须是浮点数或整数。

选型优先级:优先选择开源的 exporter,其次使用 Pushgateway,最后才考虑自己编写 exporter。

5.2 Pushgateway

Pushgateway 适用于生命周期较短的 Job 类程序。下面的脚本统计本机 TCP WAIT 连接数并推送到 Pushgateway:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
instance_name=`hostname -f | cut -d'.' -f1` # 本机机器名,用于之后的标签
if [ $instance_name == "localhost" ]; then # 要求机器名不能是 localhost,否则标签无法区分
echo "Must FQDN hostname"
exit 1
fi

# For waitting connections
label="count_netstat_wait_connections" # 定义一个新的 key
count_netstat_wait_connections=`netstat -an | grep -i wait | wc -l` # netstat 中 wait 的数量

echo "$label : $count_netstat_wait_connections"
# 最后把 key value 推送给 pushgateway
echo "$label $count_netstat_wait_connections" | curl --data-binary @- http://prometheus.server.com:9091/metrics/job/pushgateway/instance/$instance_name

4.5 rate / irate / increase 的三个坑

这三个函数是 counter 类指标的全部日常用法,但它们有几个反直觉的行为,不知道就会怀疑数据出错。

一、increase 的结果不是整数。

1
increase(http_requests_total[5m])     # 可能返回 3.7

请求数怎么会是 3.7 个?因为这三个函数都做外插(extrapolation):窗口的两个端点几乎不可能正好落在采样点上,Prometheus 会按已有采样点算出斜率,再把结果线性外推到窗口的完整长度。所以 increase 实际是”按这个速率,5 分钟应该涨多少”,而不是”我数到了几个”。

increase(x[5m]) 严格等价于 rate(x[5m]) * 300,只是换了个单位。真要精确计数就别用它,用 x - x offset 5m(不过这个写法遇到 counter reset 会算出负数)。

二、[range] 必须显著大于抓取间隔。

窗口内至少要有 2 个采样点才能算出斜率,只有 1 个或 0 个就直接 no data。所以按 scrape_interval 的整数倍取窗口是不够的——抓取有抖动、偶尔失败一次,正好卡在 2 个点的边界上就会间歇性出现空洞(图上表现为断断续续的虚线)。

经验规则是 range ≥ 4 × scrape_interval:抓取间隔 15s 就用 [1m] 起步,30s 用 [2m]。留出的余量能容忍一两次抓取失败。

三、irate 不能用在告警里。

取样方式 适合
rate 窗口内所有采样点做线性回归 告警、趋势图
irate 只取窗口内最后两个采样点 看瞬时毛刺

irate 只看最后两个点,意味着它对窗口长度几乎不敏感、但对单次抖动极其敏感。配 for: 5m 的告警时,每次 evaluation 都可能正好落在尖峰或低谷上,结果就是告警反复 firing/resolved 抖动,或者真实的持续高负载因为某次 evaluation 落在低谷而被漏掉。

顺带说清一个容易叠错的延迟:从指标越界到收到通知,中间有四段延迟累加

1
2
3
4
5
6
7
8
抓取间隔      scrape_interval        指标本身的滞后
+
规则评估间隔 evaluation_interval 默认 30s,Prometheus 多久算一次表达式
+
持续时间 for: 5m 条件必须连续满足这么久才转为 firing
+
分组等待 group_wait: 30s AlertManager 攒一批再发
= 端到端可能十分钟才收到第一条通知

同理,恢复通知(resolved)的延迟是 evaluation_interval + resolve_timeout + group_interval——这就是”问题早就好了,恢复通知半天不来”的原因。而 §3.1 里 send_resolved: false 又把恢复通知整个关掉了,两件事放一起看:恢复通知不来,先确认它是不是压根没开

6. 服务发现

使用 static_configs 静态定义监控目标不太方便。可以引入一个中间的代理人(服务注册中心),由它掌握当前所有监控目标的访问信息,Prometheus 只需向这个代理人询问有哪些监控目标即可,而不需要重启——这种模式被称为服务发现

6.0 先理解 relabel 的执行模型

下面几节全是 YAML,但不理解 relabel 怎么跑,配起来就只能靠抄。

服务发现返回的每个目标,都自带一堆以 __meta_ 开头的元数据标签(__meta_kubernetes_pod_annotation_*__meta_kubernetes_service_label_* 等,具体有哪些取决于用的哪种 SD)。这些标签只存在于 relabel 阶段,抓取一开始就被丢弃,所以指标上是看不到它们的——想留下来就必须在 relabel 里显式复制到一个正常标签上。

还有几个”魔法标签”,它们的值直接决定最终发出的那个 HTTP 请求:

标签 决定什么 默认
__address__ 抓取的 host:port SD 给出的地址
__scheme__ http 还是 https http
__metrics_path__ 请求路径 /metrics
__param_<name> URL 查询参数

所以”改抓取端口”的本质就是 relabel 出一个新的 __address__,”抓 https”就是改 __scheme__——不存在另外的专用配置项。

最容易搞混的是这两个阶段:

relabel_configs metric_relabel_configs
时机 抓取之前 抓取之后
作用对象 目标(target) 抓回来的每条指标
能做什么 决定抓不抓、抓哪里、带什么标签 改标签、丢弃指标
典型用途 keep/drop 目标、labelmap 注解 治理高基数:把爆炸的指标丢掉

治高基数只能用后者——目标已经抓回来了,是里面某个 label(比如带 URL 路径或用户 ID 的)取值太多,这时候 relabel_configs 帮不上,得在 metric_relabel_configs 里 drop:

1
2
3
4
5
6
metric_relabel_configs:
- source_labels: [__name__]
regex: 'go_gc_duration_seconds.*' # 用不上的指标直接丢
action: drop
- regex: 'id|image_id' # 丢掉高基数的标签本身,保留指标
action: labeldrop

常用 action 的取舍:

  • keep / drop:白名单 / 黑名单选目标。注解驱动的自动发现全靠 keep(见 6.2)。
  • replace:改写标签,最常用。
  • labelmap:批量把 __meta_xxx_(.+) 映射成 $1,一次搬走所有注解或标签。
  • hashmod + keep分片抓取。目标太多单实例扛不住时,起 N 个 Prometheus,每个只抓自己那一份:
1
2
3
4
5
6
7
- source_labels: [__address__]
modulus: 4
target_label: __tmp_shard
action: hashmod
- source_labels: [__tmp_shard]
regex: 0 # 第 0 个分片;其他实例改成 1/2/3
action: keep

最后一步能把 CRD 和原生配置的心智模型彻底打通:ServiceMonitor 最终会被 Operator 渲染成普通的 scrape_configs,可以直接解出来看:

1
2
kubectl get secret prometheus-<name> -n monitor \
-o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip | less

看过一次就明白:ServiceMonitor 不是什么新机制,只是一套帮你生成 relabel_configs 的模板。之后遇到”target 为空”这类问题,直接在这里搜自己的 job 名字,看渲染出来的规则哪一条把目标 drop 掉了,比对着 CRD 猜快得多。

6.1 基于文件的服务发现

这种方式适用于非 Kubernetes 环境或独立部署的 Prometheus。编辑 /etc/prometheus/prometheus.yml,设置每两分钟基于文件更新一次目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# my global config
global:
scrape_timeout: 10s
scrape_interval: 15s # 抓取间隔,默认 1 分钟
evaluation_interval: 15s # 规则评估间隔,默认 1 分钟

# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
scrape_configs:
- job_name: 'prometheus'
file_sd_configs:
- files:
- targets/prometheus-*.yaml
refresh_interval: 2m

- job_name: 'nodes'
file_sd_configs:
- files:
- targets/nodes-*.yaml
refresh_interval: 2m

新建目标文件后,Prometheus 会自动更新,无需重启。

/etc/prometheus/targets/nodes-linux.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- targets:
- 10.0.10.192:9100
- 10.0.10.141:9100
labels:
app: node-exporter
job: node
env: other

- targets:
- localhost:9100
labels:
app: node-exporter
job: node
env: main

/etc/prometheus/targets/prometheus-server.yaml(文件名必须能被上面的 prometheus-*.yaml 匹配到;叫 prometheus.yaml 是匹配不上的,而 Prometheus 对匹配不到文件的 file_sd 不报错,只在 targets 页面空着,很难查):

1
2
3
4
5
- targets:
- localhost:9090
labels:
app: prometheus
job: prometheus

基于文件的服务发现

6.2 基于注解的自动发现(不推荐)

prometheus.io/scrape 这类注解本身没有任何内建语义,Prometheus 不会因为你打了它就自动抓取——要靠下面那份 relabel_configs 显式把注解翻译成抓取规则才生效。但这种方式配置起来不方便,一般更推荐使用 ServiceMonitor 和 PodMonitor。

被发现的 Service 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.io/port: "80"
prometheus.io/scrape: "true"
labels:
app: test-frontend-250
name: test-frontend-250-svc
namespace: default
spec:
ports:
- name: test-frontend-250-http
nodePort: 31250
port: 80
targetPort: 80
selector:
app: test-frontend-250
type: NodePort

对应的额外抓取配置 prometheus-additional.yaml(配合 §3.1 中 additionalScrapeConfigsSecret 使用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name

将其创建为 Secret,并让 Prometheus 引用:

1
2
3
kubectl create secret generic additional-scrape-configs --from-file=prometheus-additional.yaml -n monitor   # 名字要和 values 里 additionalScrapeConfigsSecret 引用的一致,否则 Operator 找不到 Secret 会拒绝生成配置,这段额外抓取完全不生效

kubectl edit prometheus -n monitor
1
2
3
4
spec:
additionalScrapeConfigs:
name: additional-scrape-configs
key: prometheus-additional.yaml

6.3 ServiceMonitor、PodMonitor 与 Probe

在 Operator 环境下,推荐使用以下三种 CRD 声明抓取目标(均需带 release: prometheus 标签,见 §3.3):

  • ServiceMonitor:针对 Service 发现。
  • PodMonitor:针对 Pod 发现。
  • Probe:一般用于自定义/黑盒探测。

ServiceMonitor(以 Redis 为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
release: prometheus
name: redis
namespace: redis
spec:
endpoints:
- interval: 30s
port: http-metrics
namespaceSelector:
matchNames:
- redis
selector:
matchLabels:
app.kubernetes.io/component: metrics
app.kubernetes.io/instance: redis
app.kubernetes.io/name: redis

ServiceMonitor 的 selector 匹配的是 Service 的 metadata.labels(注意不是 spec.selector,那个是给 Service 选 Pod 用的,两回事),endpoints.port 对应 Service 中的端口名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: metrics
app.kubernetes.io/instance: redis
app.kubernetes.io/name: redis # ServiceMonitor 的 matchLabels 要求三个标签齐全(AND),少一个就选不中
name: redis-metrics
namespace: redis
spec:
ports:
- name: http-metrics
port: 9121
protocol: TCP
targetPort: metrics # Pod 里 containerPort 的 name
selector:
app.kubernetes.io/instance: redis
app.kubernetes.io/name: redis

PodMonitor(以 PostgreSQL 主备为例)

当 Pod 标签中带有主备(master/replica)标识时,针对 Pod 发现更方便,还可以通过 relabelings 改写 label 以复用官方的告警与面板规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: crunchy-postgres-exporter-master
namespace: monitor
labels:
release: prometheus
spec:
podMetricsEndpoints:
- interval: 30s
port: exporter
path: /metrics
relabelings: # 改变 label 方便使用官方的告警以及面板规则
- replacement: master
targetLabel: role
- replacement: postgres-operator:test
targetLabel: pg_cluster
- replacement: test
targetLabel: cluster
- sourceLabels: [pod]
targetLabel: deployment
regex: (.*?)-\d+
replacement: $1
action: replace
- sourceLabels: [__meta_kubernetes_pod_ip]
targetLabel: ip
action: replace
- sourceLabels: [job]
targetLabel: job
regex: monitor/(.*)-(master|replica)
replacement: $1
action: replace
- sourceLabels: [namespace]
targetLabel: kubernetes_namespace
action: replace
namespaceSelector:
matchNames:
- postgres-operator
selector:
matchExpressions:
- key: postgres-operator.crunchydata.com/role
operator: In
values: [master]

---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: crunchy-postgres-exporter-replica
namespace: monitor
labels:
release: prometheus
spec:
podMetricsEndpoints:
- interval: 30s
port: exporter
path: /metrics
relabelings:
- replacement: replica
targetLabel: role
- replacement: postgres-operator:test
targetLabel: pg_cluster
- replacement: test
targetLabel: cluster
- sourceLabels: [pod]
targetLabel: deployment
regex: (.*?)-\d+
replacement: $1
action: replace
- sourceLabels: [__meta_kubernetes_pod_ip]
targetLabel: ip
action: replace
- sourceLabels: [job]
targetLabel: job
regex: monitor/(.*)-(master|replica)
replacement: $1
action: replace
- sourceLabels: [namespace]
targetLabel: kubernetes_namespace
action: replace
namespaceSelector:
matchNames:
- postgres-operator
selector:
matchExpressions:
- key: postgres-operator.crunchydata.com/role
operator: In
values: [replica]

Probe(黑盒探测)

Probe 借助 blackbox-exporter 对目标进行探测,需要先部署 blackbox-exporter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: ping
namespace: monitor
labels:
release: prometheus
spec:
jobName: ping # 任务名称
prober: # 指定 blackbox 的地址
url: blackbox-exporter.monitor:19115
module: icmp # 配置文件中的检测模块
targets: # 目标(可以是 static 配置也可以是 ingress 配置)
staticConfig: # 如果配置了 ingress,静态配置优先
static:
- www.baidu.com # icmp 模块的 target 会被当主机名交给 net.ResolveIPAddr 解析,带 scheme 的 URL 解析失败会得到 probe_success=0,看起来像网络不通;探 URL 用 module: http_2xx

7. 告警

7.1 告警规则(PrometheusRule)

在 Operator 环境下,告警规则通过 PrometheusRule CRD 定义(同样需要带 release: prometheus 标签)。以下是主机层面的常用规则(主机宕机、CPU、内存、磁盘、TCP 连接数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
groups:
- name: host_status_alert
rules:
- alert: 主机宕机
annotations:
description: 主机{{ $labels.instance }}宕机超过1分钟告警!
summary: 主机{{ $labels.instance }}宕机
expr: up{job="node-exporter"} == 0
for: 1m
labels:
severity: critical
- alert: CPU使用过高
annotations:
description: 主机{{ $labels.instance }}当前CPU使用率是{{ $value }}%,CPU使用率大于60%告警!
summary: 主机{{ $labels.instance }}CPU使用率过高
# 告警规则要用 rate 而不是 irate:irate 只取窗口内最后两个采样点,配 for: 5m 时每次 evaluation 都可能落在尖峰或低谷,导致抖动和漏报。irate 留给绘图看瞬时毛刺。
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 60
for: 5m
labels:
severity: warning
- alert: 内存使用过高
annotations:
description: 主机{{ $labels.instance }}当前内存使用率是{{ $value }}%,内存使用率大于80%告警!
summary: 主机{{ $labels.instance }}内存使用率过高
expr: ((node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes) / (node_memory_MemTotal_bytes)) * 100 > 80
for: 5m
labels:
severity: warning
- alert: 磁盘空间剩余不足
annotations:
description: 主机{{ $labels.instance }}的磁盘{{ $labels.mountpoint }}当前使用率是{{ $value }},磁盘使用率大于80%告警!
summary: 主机{{ $labels.instance }}的磁盘{{ $labels.mountpoint }}可用空间不足
expr: (1-(node_filesystem_free_bytes{fstype=~"ext4|xfs|ceph",mountpoint!~"/var.*"} / node_filesystem_size_bytes{fstype=~"ext4|xfs|ceph",mountpoint!~"/var.*"})) * 100 > 80
for: 5m
labels:
severity: warning
- alert: TCP连接数过多
annotations:
description: 主机{{ $labels.instance }}当前TCP_ESTABLISHED={{ $value }},TCP连接数大于1000告警!
summary: 主机{{ $labels.instance }}TCP连接数过多
expr: node_netstat_Tcp_CurrEstab > 1000
for: 5m
labels:
severity: page

对于业务组件(如 PostgreSQL),开源项目通常会自带一份 ConfigMap 形式的告警规则,只需将其改写成 PrometheusRule 并 apply 即可:

1
kubectl apply -f prometheusrule-postgres.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
labels:
release: prometheus # 带有这个 label 才能被匹配
name: postgres-operator.rule
namespace: monitor
spec:
groups:
- name: postgres-operator-rule
rules:
- alert: PGExporterScrapeError
expr: pg_exporter_last_scrape_error > 0
for: 60s
labels:
service: postgresql
severity: critical
severity_num: 300
annotations:
summary: 'Postgres Exporter running on {{ $labels.job }} (instance: {{ $labels.instance }}) is encountering scrape errors processing queries. Error count: ( {{ $value }} )'
- alert: ExporterDown
expr: avg_over_time(up[5m]) < 0.5
for: 10s
labels:
service: system
severity: critical
severity_num: 300
annotations:
description: 'Metrics exporter service for {{ $labels.job }} running on {{ $labels.instance }} has been down at least 50% of the time for the last 5 minutes. Service may be flapping or down.'
summary: 'Prometheus Exporter Service Down'

7.1.1 别忘了 recording rule

PrometheusRule 里能写两种规则,上面只用了 alert,另一种是 record

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
spec:
groups:
- name: node-recording
interval: 30s
rules:
# recording rule:把表达式的结果存成一个新的时间序列
- record: instance:node_cpu_utilisation:rate5m
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# alerting rule:直接引用上面算好的结果
- alert: NodeCPUHigh
expr: instance:node_cpu_utilisation:rate5m > 80
for: 10m

为什么值得多这一层:

一、成本。 复杂表达式(多层聚合、大范围 rate、高基数 join)每次被求值都要重算一遍。写成 recording rule 之后只在 interval 里算一次,Grafana 面板、多条告警规则、其他 recording rule 都直接读现成的结果。一个被十个面板引用的重表达式,能省掉九次计算。

二、Grafana 的响应速度。 看一个月的趋势图时,原始表达式要在查询时扫过一个月的原始数据;而 recording rule 产出的是一条已经降过采样的序列,扫描量小一到两个数量级。仪表盘从十几秒变成瞬间打开,多半就是这个差别。

三、表达式只写一遍。 上面那个 CPU 利用率的算法一旦要改(比如把 mode="idle" 换成排除 steal),改一处就够,不用去十个告警规则里逐个找。

命名有个业界约定,level:metric:operations,用冒号分三段:

1
2
3
4
instance:node_cpu_utilisation:rate5m
│ │ └─ 做了什么运算
│ └─ 原始指标名
└─ 聚合到哪个层级(instance / job / namespace ...)

冒号在 PromQL 里不允许出现在原始指标名中,所以看到带冒号的指标名就知道它是 recording rule 产出的——这个约定本身就是一种自文档。

7.2 AlertManager 路由与抑制

AlertManager 的抑制规则(inhibit_rules)、路由(route)与接收器(receivers)已在 §3.1 的 prom-values.yamlalertmanager.config 段落给出:通过 inhibit_rulescritical 告警抑制同一 namespace/alertname 下的 warning/info,通过 routenamespace 分组并将不同 severity 路由到不同的接收器。如需在运行时按命名空间动态追加路由,可使用 §3.3 中的 AlertmanagerConfig CRD,它会与基础配置合并。

7.3 对接企业微信 / 钉钉机器人

§3.1 中接收器 web.hook 的地址 http://prometheus-webhook-adapter:8060/adapter/wx 指向一个 webhook 适配器,用于把告警转发到企业微信(钉钉同理)。部署适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: prometheus-webhook-adapter
name: prometheus-webhook-adapter
namespace: monitor
spec:
selector:
matchLabels:
run: prometheus-webhook-adapter
template:
metadata:
labels:
run: prometheus-webhook-adapter
spec:
containers:
- args:
- --adapter=/app/prometheusalert/dingtalk.js=/adapter/dingtalk=https://oapi.dingtalk.com/robot/send?access_token={token}#{secret}
- --adapter=/app/prometheusalert/wx.js=/adapter/wx=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
image: imwl/prometheus-webhook-adapter:v1
name: prometheus-webhook-adapter
ports:
- containerPort: 80
protocol: TCP

---
apiVersion: v1
kind: Service
metadata:
labels:
run: prometheus-webhook-adapter
name: prometheus-webhook-adapter
namespace: monitor
spec:
ports:
- port: 8060
protocol: TCP
targetPort: 80
selector:
run: prometheus-webhook-adapter
type: ClusterIP

其中 dingtalk{token}#{secret}wxkey=xxxx 需替换为自己机器人的实际值。

8. 个性化定制(Grafana)

8.1 数据源、Dashboard 与运行时修改

Grafana 的数据源、grafana.ini 等已通过 §3.1 的 prom-values.yamlgrafana 段落声明。若部署后网页提示 datasource 找不到,可用 admin 账户手动添加数据源:http://prometheus-kube-prometheus-prometheus:9090

导入 Dashboard:开源组件通常自带 Dashboard 模板(也可从 grafana.com 下载或自行配置)。导入前需要把 JSON 文件里的 "datasource": "PROMETHEUS" 替换为 "datasource": "Prometheus"

1
2
3
4
find . -type f -exec sed -i 's/"datasource": "PROMETHEUS"/"datasource": "Prometheus"/g' {} \;
# 或:grep -rl '"datasource": "PROMETHEUS"' . | xargs sed -i 's/"datasource": "PROMETHEUS"/"datasource": "Prometheus"/g'

kubectl -n monitor create cm grafana-postgres-overview --from-file=./

然后给 ConfigMap 打上 label,才能被 Grafana 的 sidecar 动态识别(默认 grafana_dashboard=1):

1
kubectl -n monitor label cm grafana-postgres-overview grafana_dashboard=1

sidecar 的 provider 配置如下(决定 Dashboard 的加载路径):

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: 1
providers:
- name: 'sidecarProvider'
orgId: 1
folder: ''
type: file
disableDeletion: false
allowUiUpdates: false
updateIntervalSeconds: 30
options:
foldersFromFilesStructure: false
path: /tmp/dashboards

运行时修改:部署完成后也可以直接 kubectl edit cm -n monitor prometheus-grafana 调整(配置项与 prom-values.yaml 中的 grafana.grafana.ini 一致,只是运行时以 INI 格式呈现)。

8.2 开启 OIDC 单点登录

编辑 Grafana 的 grafana.ini(kubectl edit cm -n monitor prometheus-grafana,release 叫 prometheus 时 cm 就是这个名字),配置 auth.generic_oauth

注意 grafana.ini 改完 sidecar 不会热加载(它只监听 dashboard 和 datasource 目录),必须 kubectl rollout restart deploy/prometheus-grafana 重启 Pod;而且直接 edit cm 的改动会在下次 helm upgrade 时被覆盖,长期改动应该落到 values 里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[server]
protocol = http
domain = grafana.test.com
root_url = https://grafana.test.com/
[auth.generic_oauth]
enabled = true
allow_sign_up = true
auto_login = false
client_id = app_xxxxx
client_secret = <YOUR_SECRET_KEY>
scopes = openid profile email
auth_url = https://xxxx.aliyunidaas.com/login/app/app_xxxxx/oauth2/authorize
token_url = https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_xxxx/app_xxxxx/oauth2/token
api_url = https://eiam-api-cn-hangzhou.aliyuncs.com/v2/idaas_xxxx/app_xxxxx/oauth2/userinfo
redirect_uri = https://grafana.test.com/login/generic_oauth
email_attribute_path = email
role_attribute_path = contains(email, 'admin@') && 'Admin' || endsWith(email, '@example.com') && 'Editor' || 'Viewer'

说明:client_secretclient_idauth_url 等均为占位/脱敏值,请替换为自己 IdP 的实际配置;role_attribute_path 用于根据邮箱把用户映射为 Admin / Editor / Viewer 角色。

9. 常见监控问题排查

9.1 CPUThrottlingHigh

CPUThrottlingHigh 反映的是最近 5 分钟内超过 25% 的 CPU 执行周期受到限制的 container,一般是 limit 设置得过低引起的。它通过两个指标计算:

  1. container_cpu_cfs_periods_total:container 生命周期中度过的 CPU 周期总数。
  2. container_cpu_cfs_throttled_periods_total:container 生命周期中度过的受限的 CPU 周期总数。

计算表达式:

1
2
sum by(container, pod, namespace) (increase(container_cpu_cfs_throttled_periods_total{container!=""}[5m]))
/ sum by(container, pod, namespace) (increase(container_cpu_cfs_periods_total[5m])) > (25 / 100)

9.2 kube-scheduler / kube-controller-manager 无监控数据

在 Operator 环境下,控制平面组件的抓取由 §3.1 中的 kubeControllerManagerkubeScheduler 等配置负责(新版集群使用 10257/10259 等 https 端口)。

对于非 Operator 环境或旧版集群(controller-manager 使用 10252 http 端口),若采集不到数据,可以手动创建 Service + Endpoints 把控制平面组件暴露给 Prometheus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: kube-controller-manager
name: kube-controller-manage-monitor
namespace: kube-system
spec:
clusterIP: 10.96.23.157
ports:
- name: http-metrics
port: 10252
protocol: TCP
targetPort: 10252
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
labels:
k8s-app: kube-controller-manager
name: kube-controller-manage-monitor
namespace: kube-system
subsets:
- addresses:
- ip: 192.168.1.19
ports:
- name: http-metrics
port: 10252
protocol: TCP