# k8s 可观测性


## 导读

Kubernetes 集群中工作负载动态调度、频繁更迭，传统的登录节点排障方式已无法跟上节奏。可观测性通过日志、指标、链路三个维度，将集群内部状态转化为可查询、可告警的数据。本文围绕三支柱模型展开，覆盖日志采集、Prometheus 监控栈、Event 查询、链路追踪与告警体系的实战配置。

## 核心概念

可观测性的三支柱各有分工。日志（Logs）记录离散事件，以文本形式承载上下文细节，适合排查具体错误。指标（Metrics）是按时间序列采样的数值数据，低存储成本、高聚合能力，适合趋势分析与阈值告警。链路（Tracing）追踪单个请求在多个微服务间的调用路径，定位跨服务延迟瓶颈。三者互补：指标告诉你出了问题，链路告诉你问题在哪，日志告诉你为什么。

分布式链路追踪在微服务架构中尤为关键。一个外部请求可能依次经过网关、认证服务、业务逻辑服务和数据库，任一环节的延迟都会拖慢整体响应。**Jaeger** 和 OpenTelemetry 是当前主流方案，通过在 HTTP 头中注入 trace context（trace ID 和 span ID）实现跨服务传播。每个服务创建 span 并上报到 collector，最终拼装成完整的调用树。

Kubernetes 场景下，**Prometheus** 是事实标准的指标系统，采用 pull 模式主动抓取目标端点。它通过服务发现机制找到 Pod、Node、Service 等目标，定时调用 `/metrics` 接口拉取数据，存入内置的时序数据库（TSDB）。配套组件各司其职：Grafana 负责可视化，Alertmanager 负责告警路由与抑制，kube-state-metrics 暴露 Deployment、Pod 等资源对象的状态指标，cAdvisor 提供容器维度的 CPU、内存、网络数据。

```mermaid
graph TB
    subgraph Cluster["Kubernetes Cluster"]
        Kubelet[kubelet]
        Cadvisor[cAdvisor]
        KSM[kube-state-metrics]
        API[API Server]
    end
    Kubelet --- Cadvisor
    Cadvisor -->|/metrics 容器指标| Prom[Prometheus]
    Kubelet -->|/metrics 节点指标| Prom
    KSM -->|资源对象状态| Prom
    API -->|服务发现| Prom
    Prom -->|存储| TSDB[(TSDB)]
    Prom -->|PromQL 查询| Grafana[Grafana]
    Prom -->|告警规则触发| AM[Alertmanager]
    AM -->|路由分发| Receiver[邮件 / Slack / PagerDuty]
    Grafana -->|仪表盘展示| User[运维人员]
```

图 1 展示了 Prometheus 监控栈的整体拓扑。kubelet 内嵌 cAdvisor，两者共同暴露节点与容器维度的指标端点。Prometheus 从 API Server 获取服务发现信息后，按配置间隔主动拉取这些端点的数据，写入本地 TSDB。Grafana 和 Alertmanager 作为消费者，分别从 TSDB 读取数据用于展示和告警判断。kube-state-metrics 独立部署，补充资源对象层面的状态信息，弥补 cAdvisor 只关注运行时指标的不足。

## 图解原理

指标采集链路的起点是 cAdvisor。它在每个节点上以常驻进程运行，通过 cgroups 和 Linux 内核接口收集容器的资源使用数据。kubelet 将 cAdvisor 的数据与自身汇总的节点指标合并，统一暴露在 `https://<node-ip>:10250/metrics` 端点上。Prometheus 按抓取配置中定义的间隔（默认 15 秒）向该端点发起 HTTP GET 请求，获取 plain text 格式的指标数据。

抓取到的原始数据经过解析后写入 TSDB。Prometheus 支持通过 recording rules 预计算高频查询，将复杂 PromQL 的结果提前固化，降低查询时的计算压力。告警规则同样基于 PromQL 表达，当表达式持续满足指定时长（for 子句）后，Prometheus 将告警推送到 Alertmanager，由后者负责去重、分组和路由分发。

```mermaid
sequenceDiagram
    participant C as cAdvisor
    participant K as kubelet
    participant P as Prometheus
    participant T as TSDB
    participant G as Grafana
    C->>K: 采集容器 CPU/内存/网络
    K->>K: 聚合到 /metrics 端点
    loop 每 15 秒
        P->>K: HTTP GET /metrics
        K-->>P: 返回指标文本
    end
    P->>P: 解析并应用 recording rules
    P->>T: 写入时序数据
    G->>P: PromQL 查询
    P->>T: 读取时序点
    T-->>P: 返回数据
    P-->>G: 返回查询结果集
    G->>G: 渲染时序图表
```

图 2 刻画了从数据产生到可视化展示的完整链路。cAdvisor 持续向 kubelet 上报容器指标，kubelet 聚合后通过 `/metrics` 端点对外提供。Prometheus 以轮询方式拉取数据，写入 TSDB 后供 Grafana 查询。整个链路中数据始终单向流动，Prometheus 不会向目标推送任何指令，这种 pull 架构降低了采集端的复杂度，也便于通过防火墙策略控制访问。

## 动手实操

### 查看容器日志

kubectl logs 是最直接的日志查看方式。指定 Pod 名称即可获取标准输出内容，追加 `-f` 参数可实时跟踪。多容器 Pod 需要用 `-c` 指定目标容器，否则命令会报错提示选择。`--previous` 参数在容器崩溃重启后查看上一次的日志，是排查 CrashLoopBackOff 的关键手段。

```bash
# 查看 Pod 日志
kubectl logs nginx-deploy-7c5b9b6f6d-x9k2m

# 跟踪实时日志
kubectl logs -f nginx-deploy-7c5b9b6f6d-x9k2m

# 查看多容器 Pod 中指定容器
kubectl logs nginx-deploy-7c5b9b6f6d-x9k2m -c sidecar

# 查看最近 1 小时的日志
kubectl logs --since=1h nginx-deploy-7c5b9b6f6d-x9k2m

# 查看前一个容器的日志（崩溃后排查）
kubectl logs --previous nginx-deploy-7c5b9b6f6d-x9k2m
```

容器日志存储在节点 `/var/log/containers/` 目录下，以软链接指向 `/var/log/pods/` 中的实际文件。节点重启或日志轮转后，历史日志可能丢失。生产环境需要部署节点级采集方案，将日志持久化到外部存储。

### 部署 Fluent Bit 采集节点日志

Fluent Bit 以 DaemonSet 方式运行在每个节点，挂载日志目录并转发到后端存储。相比 Fluentd，Fluent Bit 用 C 语言编写，内存占用更低，适合资源受限的节点。以下配置将容器日志采集后转发到 Elasticsearch：

```yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: kube-system
  labels:
    k8s-app: fluent-bit
spec:
  selector:
    matchLabels:
      k8s-app: fluent-bit
  template:
    metadata:
      labels:
        k8s-app: fluent-bit
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:2.2.0
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: config
          mountPath: /fluent-bit/etc/
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: config
        configMap:
          name: fluent-bit-config
```

ConfigMap 定义采集规则和输出目标。tail 插件监控 `/var/log/containers/*.log` 路径下的所有日志文件，Parser 指定解析格式。Mem_Buf_Limit 控制内存缓冲上限以防 OOM，Skip_Long_Lines 跳过超长行避免解析器阻塞。以下是完整的配置示例：

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: kube-system
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Log_Level     info

    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Parser            docker
        Tag               kube.*
        Mem_Buf_Limit     10MB
        Skip_Long_Lines   On

    [OUTPUT]
        Name            es
        Match           *
        Host            elasticsearch.logging.svc.cluster.local
        Port            9200
        Index           k8s-logs
        Type            _doc
```

### 部署 Prometheus 监控栈

使用 kube-prometheus-stack Helm chart 一步部署 Prometheus、Grafana、Alertmanager 及默认的告警规则。该 chart 内置了 node-exporter、kube-state-metrics 和 ServiceMonitor 配置，开箱即用。部署前确认集群已安装 Helm 3 并添加 prometheus-community 仓库：

```bash
# 添加 Helm 仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 安装到 monitoring 命名空间
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace

# 查看 Pod 状态
kubectl get pods -n monitoring
```

安装完成后，将 Grafana Service 改为 NodePort 以便从集群外部访问。Prometheus 和 Alertmanager 同理，按需暴露端口。默认账号为 admin，密码可通过 Secret 获取：

```bash
kubectl patch svc kube-prometheus-stack-grafana -n monitoring \
  -p '{"spec":{"type":"NodePort"}}'

# 获取初始密码
kubectl get secret -n monitoring kube-prometheus-stack-grafana \
  -o jsonpath="{.data.admin-password}" | base64 -d ; echo
```

Grafana 内置了 kube-prometheus-stack 预配置的数据源和仪表盘。登录后可直接查看节点资源使用率、Pod 状态、API Server 延迟等关键面板。数据源指向集群内的 Prometheus 实例，无需手动配置即可开始查询。

### 配置告警规则

Prometheus 告警规则定义在 PrometheusRule CRD 中，kube-prometheus-stack 会自动识别并加载。规则包含 expr（PromQL 表达式）、for（持续时间）和 annotations（描述信息）三部分。以下示例覆盖节点高 CPU 和容器频繁重启两个场景：

```yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: node-alerts
  namespace: monitoring
  labels:
    prometheus: k8s
spec:
  groups:
  - name: node.rules
    rules:
    - alert: NodeHighCPUUsage
      expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "节点 CPU 使用率过高"
        description: "节点 {{ $labels.instance }} CPU 使用率超过 80%，持续 5 分钟。"

    - alert: PodCrashLooping
      expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "容器频繁重启"
        description: "Pod {{ $labels.pod }} 在 1 小时内重启超过 5 次。"
```

Alertmanager 负责告警的去重、分组和路由。通过 Secret 配置通知接收者。`group_by` 按标签聚合关联告警，`group_wait` 控制首次发送前的等待时间，`repeat_interval` 防止未恢复告警重复通知：

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: alertmanager-config
  namespace: monitoring
type: Opaque
stringData:
  alertmanager.yaml: |
    global:
      resolve_timeout: 5m
    route:
      group_by: ['alertname']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 4h
      receiver: 'default'
    receivers:
    - name: 'default'
      webhook_configs:
      - url: 'https://hooks.slack.com/services/xxx'
```

### 查询 Event

Event 是 Kubernetes 内置的事件机制，记录资源生命周期中的状态变更。Pod 调度失败、镜像拉取错误、探针检测失败等都会生成 Event，默认保留 1 小时。`kubectl describe` 输出尾部的 Events 段是排查 Pod 异常的第一入口：

```bash
# 查看某 Pod 的所有事件
kubectl get events --field-selector involvedObject.name=<pod-name>

# 按类型筛选 Warning 事件
kubectl get events --field-selector type=Warning

# 按时间排序查看命名空间事件
kubectl get events -n default --sort-by=.metadata.creationTimestamp

# 以 YAML 格式查看事件详情
kubectl get event <event-name> -o yaml
```

Event 中包含 reason（事件原因）、message（描述信息）、source（事件来源组件）等字段。结合 `kubectl describe pod <name>` 可以快速定位调度失败和探针异常的根因。生产环境中建议将 Event 导出持久化，弥补默认 1 小时 TTL 的局限。

### 分布式链路追踪概览

在 Kubernetes 微服务场景中，单个请求可能跨越多个 Pod 和 Service。**OpenTelemetry** 提供统一的 SDK 和 instrumentation 标准，将 trace context 通过 W3C Trace Context 规范在 HTTP 头中传播。部署 OpenTelemetry Collector 作为 DaemonSet 或 Deployment，接收各服务上报的 span 数据并导出到 Jaeger 等后端：

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  namespace: tracing
spec:
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
      - name: otel-collector
        image: otel/opentelemetry-collector-contrib:0.90.0
        ports:
        - containerPort: 4317
          name: otlp-grpc
        - containerPort: 4318
          name: otlp-http
        - containerPort: 16686
          name: jaeger-ui
```

应用侧需引入 OpenTelemetry SDK，在发起 HTTP 请求时自动注入 trace context，接收请求时提取并续接。Jaeger UI 以火焰图形式展示调用链，每个 span 记录服务名、操作名、耗时和标签，帮助快速定位慢调用节点。追踪数据与指标、日志关联后，可以从告警直接跳转到对应时间段的调用链和日志，实现三支柱联动。

## 常见问题与避坑

指标端点抓取超时。cAdvisor 在容器数量过多的节点上响应变慢，Prometheus 默认 10 秒超时可能不够。在 ServiceMonitor 中调大 `scrapeTimeout`，或为节点添加资源限制防止单节点过载。抓取间隔不宜低于 10 秒，过高的频率会给 API Server 和 kubelet 带来压力。

日志采集丢数据。Fluent Bit 的 tail 插件默认从文件末尾开始读取，重启后已读取的偏移量记录在 position 文件中。若 position 文件丢失，会重复采集或遗漏数据。**切勿将 position.db 放在 emptyDir 上**，Pod 重启后偏移量清零会导致数据丢失。应将 position 文件挂载到 hostPath 或持久化卷。

告警风暴。一条 Service 故障可能触发数十条关联告警，淹没关键信息。在 Alertmanager 中配置 `group_by` 按标签分组，设置 `group_wait` 延迟发送，利用 `inhibit_rules` 抑制下级告警。例如节点 Down 时应抑制该节点上所有 Pod 的告警，避免重复通知。

Event 过期丢失。Event 默认 TTL 为 1 小时，事后排查时往往已不存在。部署 **kube-event-exporter** 将 Event 导出到 Elasticsearch 或对象存储，延长留存周期以支持历史回溯。

## 小结与进阶

可观测性的核心在于用日志、指标、链路三维度数据还原系统真实状态，Prometheus 监控栈提供了从采集到告警的完整工具链。掌握三支柱的采集机制和告警配置，是构建可靠运维体系的基础。下一篇将深入 Helm 包管理，学习如何用模板化方式管理复杂应用的部署与升级。

