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 告警、节点磁盘/内存高告警、关键接口延迟/错误率告警。这些是最低告警基线。

分布式链路追踪(Tracing)

在微服务架构中,一个用户请求可能经过 10+ 个服务。当出现高延迟或错误时,仅靠日志和指标很难定位是哪个服务出了问题。分布式链路追踪为每次请求生成唯一 Trace ID,记录跨服务调用的完整路径和耗时。

OpenTelemetry(OTel)
CNCF 主导的可观测性标准化项目,统一了 Metrics、Logs、Traces 的 API、SDK 和传输协议(OTLP)。是业界推荐的可观测性数据采集标准,已被 Jaeger、Tempo、Datadog 等后端原生支持。
Span
一次链路追踪的最小单元,记录单个操作的开始时间、结束时间、状态和属性(如 HTTP 路径、DB 查询语句)。多个 Span 按父子关系组成一棵树,树的根即为整个请求的 Span。
Trace
一次完整请求经过所有服务的调用树。每个 Trace 有唯一 Trace ID(通过 HTTP Header traceparent 传递),使跨服务关联成为可能。
Grafana Tempo
轻量级分布式链路存储后端,与 Prometheus + Loki 组成 Grafana 的完整可观测性栈(Metrics + Logs + Traces)。比 Jaeger 占用资源更少,支持 OTLP 直接写入。
# OpenTelemetry Collector DaemonSet(采集节点上的遥测数据)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector
spec:
  template:
    spec:
      containers:
      - name: collector
        image: otel/opentelemetry-collector-contrib:latest
        args:
        - --config=/etc/otel/config.yaml
        volumeMounts:
        - name: config
          mountPath: /etc/otel

SLI / SLO / SLA 实践

可观测性的最终目标是定义和维护服务质量目标。这三个概念是 SRE(Site Reliability Engineering)的核心:

SLI(Service Level Indicator)服务水平指标
度量服务质量的具体指标,如:请求成功率、P99 延迟、可用性百分比。PromQL 可直接计算 SLI:sum(rate(http_requests_total{status!~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
SLO(Service Level Objective)服务水平目标
对 SLI 设定的目标值,如"请求成功率 ≥ 99.9%"(3个9)。SLO 不应设得过高(99.999% 意味着每年最多 5 分钟停机),要结合业务需求和团队运维能力。
Error Budget 错误预算
SLO 允许的失败空间。99.9% 的 SLO 意味着 30 天内有 43.2 分钟的错误预算。当错误预算耗尽时,停止新功能发布,集中精力提升稳定性。
# Prometheus 告警规则:SLO 违规告警
groups:
- name: slo-alerts
  rules:
  - alert: HighErrorRate
    expr: |
      sum(rate(http_requests_total{status=~"5.."}[5m]))
        /
      sum(rate(http_requests_total[5m])) > 0.001  # 错误率超过 0.1%
    for: 5m                                          # 持续 5 分钟才告警
    labels:
      severity: critical
    annotations:
      summary: "SLO 违规:错误率 {{ $value | humanizePercentage }}"
      runbook: "https://wiki.example.com/runbooks/high-error-rate"
本章小结

可观测性是生产系统的"眼睛",由指标(Metrics)日志(Logs)链路追踪(Traces)三支柱组成。Prometheus 通过 Pull 模式采集指标,PromQL 强大的聚合能力让 SLI 计算变得简单;Grafana 提供可视化看板和告警通知。日志推荐 Grafana Loki(轻量)替代 Elasticsearch。链路追踪选择 OpenTelemetry 标准 + Grafana Tempo 存储。生产告警必须覆盖最低基线(OOMKilled、CrashLoop、磁盘/内存高、接口延迟/错误率)。SLO 驱动的 Error Budget 是平衡稳定性与交付速度的工程实践。