Chapter 09

可观测性:Prometheus + Grafana

构建云原生可观测性三支柱:指标(Metrics)、日志(Logs)、链路(Traces)。掌握 Prometheus 采集、PromQL 查询、Grafana 可视化与告警配置。

可观测性三支柱

云原生可观测性体系

┌─────────────────────────────────────────────────────────┐
                  可观测性 (Observability)                 
├───────────────┬────────────────────┬────────────────────┤
  指标 Metrics    日志 Logs          链路 Traces     
                                                       
  Prometheus      EFK Stack          Jaeger/Tempo      
  Grafana         (ES+Fluentd+KB)    OpenTelemetry     
                                                       
  "是否正常?"    "发生了什么?"      "哪里慢?"        
└───────────────┴────────────────────┴────────────────────┘

Prometheus 架构与采集模型

Prometheus 在 K8s 中的工作流程

  应用 Pod              Prometheus Server           Grafana
  ┌─────────┐           ┌───────────────────┐        ┌────────┐
   /metrics  ◄─ 拉取─  Scrape Manager                    
  └─────────┘            TSDB Storage       ─查询─►         
                          PromQL Engine                     
  ServiceMonitor CRD     Alert Rules               └────────┘
  ┌─────────┐  ─配置─►  └───────────┬───────┘
   告诉 Prom                       │ 触发告警
   去哪采集             ┌───────────▼───────┐
  └─────────┘            Alertmanager      
                          去重/分组/静默    
                          Slack/PagerDuty   
                         └───────────────────┘
Pull 模型
Prometheus 主动从目标(应用的 /metrics 端点)拉取指标,而非应用推送。好处:Prometheus 控制采集频率,可以检测目标是否存活。
TSDB
Time Series Database,时序数据库。Prometheus 自带的高性能本地时序存储,每个指标样本包含时间戳 + 值 + 标签集。
ServiceMonitor
Prometheus Operator 提供的 CRD,声明式地告诉 Prometheus 去采集哪些 Service 的 /metrics 端点,替代了手动编辑 prometheus.yaml 配置文件。
Exporter
为不支持 Prometheus 格式的应用或系统提供 /metrics 端点的适配器。如 node-exporter(系统指标)、mysql-exporter(数据库指标)。

使用 Helm 部署 kube-prometheus-stack

kube-prometheus-stack 是一个包含 Prometheus Operator、Alertmanager、Grafana、node-exporter、kube-state-metrics 的完整可观测性套件。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 安装完整监控栈
helm install kube-prom-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword=admin123

# 访问 Grafana(本地转发)
kubectl port-forward -n monitoring svc/kube-prom-stack-grafana 3000:80
# 浏览器访问 http://localhost:3000,用户名 admin,密码 admin123

ServiceMonitor:声明式采集配置

# 为你的应用创建 ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  namespace: monitoring
  labels:
    release: kube-prom-stack      # 必须匹配 Prometheus Operator 的 selector
spec:
  namespaceSelector:
    matchNames:
    - default                     # 监控哪个 namespace 的 Service
  selector:
    matchLabels:
      app: my-app                 # 选择带此标签的 Service
  endpoints:
  - port: metrics               # Service 的端口名称(需与 Service 端口名匹配)
    path: /metrics              # 指标路径
    interval: 30s               # 采集间隔
    scrapeTimeout: 10s

PromQL 基础

PromQL(Prometheus Query Language)是 Prometheus 的查询语言,用于实时查询和聚合时序数据。

Counter
只增不减的累计值,如请求总数、错误总数。通常配合 rate() 或 increase() 使用,计算速率。
Gauge
可任意增减的瞬时值,如内存使用量、并发连接数、Pod 数量。可以直接使用,也可做聚合。
Histogram
将观测值分桶统计,如请求延迟。包含 _bucket、_sum、_count 三类时间序列。配合 histogram_quantile() 计算百分位数。
Summary
类似 Histogram,但分位数在客户端计算,不支持聚合。Histogram 更灵活,推荐使用。
# ── 常用 PromQL 示例 ──

# 所有 Pod 的 CPU 使用率(rate 计算 5min 内的平均速率)
rate(container_cpu_usage_seconds_total[5m])

# 某个 Deployment 的内存用量(字节)
sum(container_memory_working_set_bytes{deployment="my-app"}) by (pod)

# HTTP 请求 QPS(每秒请求数)
sum(rate(http_requests_total[2m])) by (service)

# HTTP 错误率(5xx 占比)
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

# P99 请求延迟(Histogram)
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)

# 节点 CPU 空闲率
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 集群中 NotReady 的 Node 数量
count(kube_node_status_condition{condition="Ready",status="false"})

告警规则与 Alertmanager

PrometheusRule:声明式告警规则

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: app-alerts
  namespace: monitoring
  labels:
    release: kube-prom-stack
spec:
  groups:
  - name: app.rules
    interval: 1m
    rules:
    - alert: HighErrorRate
      expr: |                           # PromQL 表达式
        sum(rate(http_requests_total{status=~"5.."}[5m]))
        / sum(rate(http_requests_total[5m])) > 0.05
      for: 5m                           # 持续 5 分钟才触发告警
      labels:
        severity: critical
      annotations:
        summary: "HTTP 错误率超过 5%"
        description: "当前错误率 {{ printf \"%.2f\" $value }}%,超过阈值 5%"
    - alert: PodCrashLooping
      expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 5 > 5
      for: 15m
      labels:
        severity: warning
      annotations:
        summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 频繁重启"

Alertmanager 配置(Slack 告警)

global:
  resolve_timeout: 5m

route:
  group_by: ['alertname', 'namespace']
  group_wait: 30s             # 同组告警等待 30s 再发送(聚合)
  group_interval: 5m          # 同组新告警等待 5m 发送
  repeat_interval: 4h         # 相同告警 4h 后再次发送
  receiver: slack-critical
  routes:
  - match:
      severity: warning
    receiver: slack-warning

receivers:
- name: slack-critical
  slack_configs:
  - api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
    channel: '#alerts-critical'
    title: '[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}'
    text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
    send_resolved: true

日志收集:EFK Stack

EFK = Elasticsearch(存储与搜索)+ Fluentd/Fluent Bit(采集与转发)+ Kibana(可视化)。

K8s 日志收集架构

  ┌──────────────────────────────────────────────┐
    每个 Node 上的 Fluent Bit DaemonSet        
    采集 /var/log/containers/*.log              
    解析 JSON,注入 K8s 元数据(Pod/NS/Label)  
  └───────────────────────┬──────────────────────┘
                          │ Forward
  ┌───────────────────────▼──────────────────────┐
           Elasticsearch(StatefulSet)         
           分布式全文搜索,按日期索引             
  └───────────────────────┬──────────────────────┘
                          
  ┌───────────────────────▼──────────────────────┐
      Kibana(Deployment)                      
      日志搜索 + 仪表板 + 告警                   
  └──────────────────────────────────────────────┘
# Fluent Bit ConfigMap(简化版)
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush     5
        Log_Level info

    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Parser            docker
        Tag               kube.*

    [FILTER]
        Name    kubernetes
        Match   kube.*
        Merge_Log On        # 将 JSON 日志解构

    [OUTPUT]
        Name   es
        Match  kube.*
        Host   elasticsearch.logging.svc.cluster.local
        Port   9200
        Index  k8s-logs
现代替代方案:Grafana Loki

Elasticsearch 资源消耗大。Grafana Loki 是更轻量的日志聚合方案——它不为日志内容建立全文索引,只索引标签(如 namespace、pod),日志内容按时间压缩存储,资源消耗比 ES 少 10 倍以上。配合 Promtail(采集)和 Grafana(查询),是 K8s 日志的热门替代方案。

常见错误:没有告警就是没有监控

搭建了 Prometheus 和 Grafana 但没有配置告警规则,等同于没有监控——问题不会自动通知你,只能事后靠图表排查。生产环境必须配置:Pod OOMKilled 告警、CrashLoopBackOff 告警、节点磁盘/内存高告警、关键接口延迟/错误率告警。这些是最低告警基线。