K8s 可观测性全景图:监控、日志、链路追踪怎么做 原创
一个 Pod 重启了。你看了一眼 Grafana——CPU 正常,内存正常。你 kubectl logs,发现容器刚启动就退出了。你看不出为什么。日志没有 panic,没有 OOM,就是没了。
这时候你需要的不是更多指标,也不是更多日志。你需要的是链路追踪——这个 Pod 在退出前,到底和谁通信了,谁调用了它,哪个环节断了。
这就是可观测性三支柱存在的意义。
一、可观测性 ≠ 监控
先纠正一个普遍误解:可观测性不是监控的升级版。
监控是"你知道该问什么问题,提前设置好检查点"。可观测性是"出了事,你能从系统外部行为推导出内部状态"。
在 K8s 这种高度动态、高度分布式的环境里,传统监控的天花板很快就到了——一个微服务链路涉及 20 个 Pod,延迟波动在哪一层?你得逐个排查。而可观测性的三支柱,让你能在几秒内定位到问题所在的环节。
三支柱各自的角色定位:
| 支柱 | 回答的问题 | 典型工具 | 数据特征 |
|---|---|---|---|
| Metrics(指标) | "什么时候、什么服务、数值是多少" | Prometheus | 时序数值,聚合查询 |
| Logs(日志) | "发生了什么,上下文是什么" | Loki / ELK | 非结构化文本,离散事件 |
| Traces(链路) | "请求经过了哪些节点,每一步耗时多少" | Tempo / Jaeger | 树状结构,因果链路 |
关键认知:三支柱不是三个独立系统,而是通过 TraceID 关联起来的统一观测体系。 后面会专门讲怎么打通关联。
二、Metrics:Prometheus 生态落地
Metrics 是三支柱的基石,也是你最先该搭好的那一根。
2.1 kube-prometheus-stack 一把梭
不要从零拼凑 Prometheus + Grafana + Alertmanager + Node Exporter。用 kube-prometheus-stack Helm Chart,一条命令搞定全家桶:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set prometheus.prometheusSpec.retention=30d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=fast-ssd \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=200Gi这条命令部署后,你会得到:
- Prometheus:指标采集和存储
- Grafana:可视化面板(自带 20+ K8s 仪表盘)
- Alertmanager:告警路由
- Node Exporter:节点级指标(CPU / 内存 / 磁盘 / 网络)
- kube-state-metrics:K8s 对象状态指标(Pod / Deployment / Service)
- Prometheus Operator:用 CRD 管理监控配置
2.2 四类黄金指标
Google SRE 提出的四类黄金指标,是 Metrics 选型的北极星:
| 指标类型 | 含义 | PromQL 示例 |
|---|---|---|
| 延迟(Latency) | 请求处理耗时 | histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) |
| 流量(Traffic) | 请求量或数据量 | sum(rate(http_requests_total[5m])) by (service) |
| 错误(Errors) | 错误请求比率 | sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) |
| 饱和度(Saturation) | 资源使用率 | container_cpu_usage_seconds_total / kube_node_status_allocatable |
一个核心建议:用 USE 方法监控基础设施(节点),用 RED 方法监控服务(Pod)。
- USE = Utilization(使用率)+ Saturation(饱和度)+ Errors(错误)—— 适合 Node、Disk、Network
- RED = Rate(请求率)+ Errors(错误率)+ Duration(延迟)—— 适合 HTTP/gRPC 服务
2.3 ServiceMonitor:声明式监控配置
Prometheus Operator 的杀手特性——用 CRD 管理采集目标,不再手写 prometheus.yml:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: payment-api
namespace: monitoring
labels:
release: kube-prometheus-stack # 必须匹配 Prometheus 的 selector
spec:
selector:
matchLabels:
app: payment-api # 自动发现带这个 label 的 Service
namespaceSelector:
matchNames:
- production
endpoints:
- port: metrics # Service 中定义的端口名
interval: 15s # 采集间隔
path: /metrics # 指标暴露路径
relabelings:
- sourceLabels: [__meta_kubernetes_pod_node_name]
targetLabel: nodeServiceMonitor 的优势在于:开发团队可以在自己的 namespace 里声明监控目标,不需要 SRE 改全局配置。 这和 GitOps 的理念完全契合——监控配置和应用代码一起版本管理。
三、日志:Loki vs ELK 的取舍
日志系统的选型,是 K8s 可观测性里争议最大的一个环节。
3.1 为什么我推荐 Loki
ELK(Elasticsearch + Logstash + Kibana)是传统选择,但在 K8s 环境下有三个痛点:
| 痛点 | ELK 的问题 | Loki 的解法 |
|---|---|---|
| 存储成本 | 全文索引,1TB 日志约占 50% 的索引开销 | 不建索引,只索引 label,存储成本降 10 倍 |
| 运维复杂 | Elasticsearch 集群调优、分片、副本 | 单二进制,S3/对象存储后端 |
| K8s 集成 | 需要额外的 Filebeat/Fluentd | Promtail 原生支持 K8s,自动发现 Pod 日志 |
Loki 的核心设计哲学:不做全文索引,只索引 metadata(label)。 查询时先按 label 过滤,再扫描日志内容。这和 Prometheus 的设计哲学完全一致——先索引再查询。
3.2 Loki Stack 部署
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki-stack grafana/loki-stack \
--namespace monitoring \
--set loki.persistence.enabled=true \
--set loki.persistence.size=100Gi \
--set promtail.enabled=true \
--set promtail.config.snippets.pipelineStages[0].docker={}3.3 Promtail:日志采集与结构化
Promtail 是 Loki 的采集 Agent,部署为 DaemonSet,每个节点一个 Pod,自动采集 /var/log/pods/ 下的容器日志。
一个实战技巧:用 pipeline_stages 从非结构化日志中提取字段:
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
# 解析 JSON 格式日志
- json:
expressions:
level: level
trace_id: trace_id
msg: message
# 把提取的字段变成 Loki label
- labels:
level:
trace_id: # 关键!这是三支柱关联的纽带
# 时间戳对齐
- timestamp:
source: timestamp
format: RFC3339Nano注意最后一行 trace_id 作为 label 提取——这是打通三支柱关联的关键步骤。 后面会讲为什么这很重要。
四、链路追踪:Tempo + OpenTelemetry
链路追踪是三支柱里技术门槛最高、但价值也最大的一个。
4.1 OpenTelemetry Operator:零侵入埋点
传统链路追踪需要在代码里手动埋点——每个微服务都要引入 SDK,每个 HTTP 调用都要加 span。这在几十个微服务的场景下是噩梦。
OpenTelemetry Operator 解决了这个问题:通过 Admission Webhook 自动注入追踪 SDK。
# 安装 OTel Operator
kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
# 部署 Tempo(链路后端存储)
helm install tempo grafana/tempo \
--namespace monitoring \
--set persistence.enabled=true \
--set persistence.size=50Gi4.2 自动注入:不用改代码
OTel Operator 的核心能力是自动注入。你只需要给 namespace 打个 label:
# 给 namespace 开启自动注入
kubectl label namespace production instrumentation.opentelemetry.io/inject-java="true"然后定义 Instrumentation 配置:
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: production
spec:
exporter:
endpoint: http://tempo.monitoring.svc:4317
propagators: ["tracecontext", "baggage"]
sampler:
type: parentbased_traceidratio
argument: "0.1" # 采样率 10%,高流量场景调低
java:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest
python:
image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest效果:这个 namespace 里新部署的 Java/Python 应用,Pod 启动时会自动注入 OTel Agent,无需改一行代码,链路数据自动上报到 Tempo。
4.3 链路数据的结构
一条完整的 Trace 是一棵树:
Trace (trace_id: abc123)
├── Span 1: HTTP GET /checkout (service: gateway, 850ms)
│ ├── Span 2: HTTP POST /payment (service: payment-api, 620ms)
│ │ ├── Span 3: DB Query (service: payment-api, 180ms)
│ │ └── Span 4: HTTP POST /risk (service: risk-api, 380ms)
│ │ └── Span 5: Redis GET (service: risk-api, 45ms)
│ └── Span 6: HTTP GET /inventory (service: inventory-api, 180ms)从这棵树里你一眼能看到:checkout 请求总耗时 850ms,其中 payment-api 的 620ms 是瓶颈,而 payment-api 内部 380ms 花在了调用 risk-api 上。这种因果链路,Metrics 和 Logs 都给不了你。
五、三支柱关联:TraceID 贯穿一切
这是整篇文章最核心的部分。三支柱如果各自为战,价值打了五折。 真正的可观测性,是能在三支柱之间自由跳转的。
5.1 关联链路
Metrics 面板看到延迟突增
↓ 点击指标上的 Exemplar
Traces 面板打开具体那条慢请求
↓ 从 Trace 的 Span 里拿到 TraceID
Logs 面板用 TraceID 过滤日志
↓ 看到完整的错误堆栈5.2 启用 Prometheus Exemplar
Exemplar 是 Prometheus 2.26+ 的特性,允许在指标数据点上附加一个 TraceID。这样你就能从 Grafana 指标图表直接跳转到对应的 Trace。
# Prometheus 配置
prometheus:
prometheusSpec:
enableFeatures:
- exemplar-storage
# Exemplar 保留时长
retentionExemplars: 7d然后在应用的 metrics 暴露端点里,给指标附加 trace_id:
# 原来的指标
http_request_duration_seconds_bucket{le="0.5",service="payment"} 1234
# 带 Exemplar 的指标
http_request_duration_seconds_bucket{le="0.5",service="payment"} 1234 # {trace_id="abc123"} 0.425.3 Grafana 配置 Trace 到 Log 跳转
在 Grafana 的 Tempo 数据源配置里,开启 Trace 到 Logs 的关联:
tracesToLogs:
datasourceUid: loki-datasource
tags: ['service.name', 'trace_id']
mappedTags:
'service.name': 'service'
filterByTraceID: true
filterBySpanID: false效果:在 Grafana 里看一条 Trace,点一下就能跳转到 Loki,自动用 trace_id 过滤出所有相关日志。 这就是三支柱联动的威力。
六、Grafana:统一面板
不要用三个不同的 UI 看三支柱。Grafana 可以把 Metrics / Logs / Traces 统一在一个面板里。
6.1 数据源配置
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus.monitoring.svc:9090
isDefault: true
- name: Loki
type: loki
url: http://loki.monitoring.svc:3100
jsonData:
derivedFields:
- datasourceUid: tempo
matcherRegex: 'trace_id=(\w+)'
name: TraceID
url: '$${__value.raw}'
- name: Tempo
type: tempo
url: http://tempo.monitoring.svc:3200
jsonData:
tracesToLogs:
datasourceUid: loki
filterByTraceID: true6.2 统一 Dashboard 设计原则
一个完整的排障 Dashboard 应该分四层:
| 层次 | 内容 | 数据源 |
|---|---|---|
| 第一层:全局健康 | SLO 达成率、错误预算燃烧率 | Prometheus |
| 第二层:服务级 | RED 指标(Rate/Errors/Duration) | Prometheus |
| 第三层:基础设施 | Pod 状态、节点资源 | Prometheus + kube-state-metrics |
| 第四层:排障下钻 | 具体日志、具体 Trace | Loki + Tempo |
排障时从第一层往下钻:SLO 烧了 → 哪个服务延迟高 → 是哪个节点的问题 → 看 Pod 日志和 Trace。
七、eBPF:可观测性的新范式
前面讲的三支柱都需要应用侧配合——暴露 metrics、输出日志、注入 trace。eBPF 改变了这个游戏规则。
7.1 什么是 eBPF
eBPF 是 Linux 内核的可编程虚拟机。你可以在内核态运行沙箱程序,拦截系统调用、网络数据包,完全不需要修改应用代码。
对可观测性来说,这意味着:
- 不需要应用暴露 /metrics 端口——eBPF 从内核层采集 CPU / 内存 / 网络
- 不需要 SDK 埋点——eBPF 拦截 syscall 自动生成 Trace
- 不需要改 Pod——eBPF 在节点层运行,Pod 完全无感
7.2 Cilium Hubble:网络可观测性
如果你用 Cilium 作为 CNI(容器网络接口),Hubble 是开箱即用的网络观测工具:
# 启用 Hubble
helm upgrade cilium cilium/cilium \
--namespace kube-system \
--reuse-values \
--set hubble.enabled=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http}" \
--set hubble.ui.enabled=true
# 命令行查看实时流量
hubble observe --pod production/payment-api --protocol httpHubble 能看到的东西是传统监控看不到的:
- 哪个 Pod 连接哪个 Pod 被拒绝了(NetworkPolicy 问题)
- DNS 查询延迟和失败率
- HTTP 请求的 method/path/status(不依赖应用埋点)
- TCP 重传率(网络质量问题)
7.3 Pixie:Auto-Tracing
Pixie 是另一个基于 eBPF 的工具,核心卖点是全自动链路追踪:
# 一键部署
helm install pixie pixie-operator/pixie-operator-chart \
--namespace pl \
--create-namespace
# 查看 HTTP 请求
px run http_data --cluster productionPixie 直接在内核层拦截 HTTP/gRPC 请求,自动构建 Trace 和 Span,不需要 OTel SDK,不需要修改代码。 对于那些没有条件埋点的遗留系统,Pixie 是救命稻草。
八、选型决策矩阵
不同规模和阶段的团队,三支柱的选型完全不同。下面是实战推荐:
8.1 四种规模的推荐方案
| 规模 | Metrics | Logs | Traces | 方案特点 |
|---|---|---|---|---|
| 小团队(<50 Pod) | Prometheus 单节点 | Loki 单节点 | 暂不部署 | 先把 Metrics 做扎实 |
| 中等规模(50-500 Pod) | kube-prometheus-stack | Loki + Promtail | Tempo + OTel | 三支柱全套 |
| 大规模(500-5000 Pod) | VM 单节点替换 Prometheus | Loki 集群 | Tempo 集群 | 存储换 VM 降成本 |
| 超大规模(>5000 Pod) | VM 集群 / Mimir | Loki 微服务模式 | Tempo + 联邦 | 存算分离 + 多租户 |
8.2 关键选型原则
原则一:先 Metrics,再 Logs,最后 Traces。 Metrics 投入产出比最高,Traces 技术门槛最高。
原则二:Loki 优先于 ELK,除非你有全文搜索的刚需。 Loki 和 Grafana 原生集成,运维成本低一个量级。
原则三:OTel 是唯一的长期答案。 Jaeger、Zipkin、SkyWalking 都在向 OTel 靠拢,现在投入 OTel 不会浪费。
原则四:不要一开始就上 eBPF。 先把三支柱搭好,eBPF 是锦上添花,不是雪中送炭。
九、避坑指南
坑 1:Prometheus 高基数指标炸内存
症状: Prometheus 内存持续上涨,最终 OOM。
原因: 应用在 metrics 里把 user_id、request_id 这种高基数值作为 label。每个 label 值组合生成一条新的时间序列,基数爆炸。
解法:
# 错误:user_id 作为 label
http_requests_total{user_id="12345"} 1
http_requests_total{user_id="12346"} 1
# 100 万用户 = 100 万条时间序列
# 正确:user_id 放进 Exemplar 或日志
http_requests_total{method="GET",status="200"} 1000000用 prometheus_cardinality 指标监控序列数:
prometheus_tsdb_head_series坑 2:Loki 查询超时
症状: 查 24 小时的日志,Loki 返回 504 超时。
原因: Loki 没有全文索引,大范围查询会扫描大量数据块。
解法: 查询时必须带上 namespace 或 app label 来缩小范围:
# 慢查询(全量扫描)
{job="kube-system/kubelet"}
# 快查询(带 label 过滤)
{namespace="production", app="payment-api"} |= "error"坑 3:OTel 采样率设太高
症状: Tempo 后端存储暴涨,查询很慢。
原因: 全量采集链路数据,每秒上万条 Trace。
解法: 用 Tail-based Sampling——先全量采集,在 Collector 层按错误/慢请求采样:
# OTel Collector 配置
processors:
tail_sampling:
decision_wait: 30s
policies:
- name: errors
type: status_code
status_code:
status_codes: [ERROR]
- name: slow
type: latency
latency:
threshold_ms: 500
- name: baseline
type: probabilistic
probabilistic:
sampling_percentage: 10效果:所有错误请求和慢请求 100% 保留,正常请求只留 10%。存储降 80%,排障不受影响。
坑 4:Grafana 面板太多导致浏览器卡死
症状: 打开 Dashboard 浏览器卡几秒甚至崩溃。
原因: 一个 Dashboard 放了 30+ Panel,每个 Panel 发多个查询。
解法: 一个 Dashboard 不超过 12 个 Panel,按层次拆分到多个 Dashboard。
坑 5:Promtail 丢失 Pod 标签
症状: Loki 里查不到某些 Pod 的日志。
原因: Pod 启动时 Promtail 还没来得及采集,Pod 就 CrashLoop 了。
解法: 在 Promtail 配置里加 relabel_configs 保留完整的 K8s metadata:
relabel_configs:
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app坑 6:三支柱 TraceID 对不上
症状: Grafana 里从 Trace 跳转 Logs,查不到数据。
原因: 应用日志里的 trace_id 格式和 Tempo 里的格式不一致(一个有 0x 前缀,一个没有)。
解法: 统一 TraceID 格式。在 Promtail 的 pipeline_stages 里做规范化:
- regex:
expression: 'trace_id=(\w+)'
source: message
- template:
source: trace_id
template: 'Lower(.TrimPrefix(Value "0x"))'
- labels:
trace_id:十、落地路线图
不要试图一次搭完三支柱。按以下三个阶段推进:
阶段一:Metrics 先行(第 1-2 周)
目标: 建立基础设施和服务级监控。
交付物:
- kube-prometheus-stack 部署完成
- 所有节点和核心服务有 metrics 暴露
- Grafana 上有 3 个 Dashboard:集群概览 / 节点资源 / 服务 RED
- Alertmanager 配好基础告警规则
验收标准: 能在 Grafana 上看到任意 Pod 的 CPU/内存使用率、任意服务的 QPS和延迟。
阶段二:日志接入(第 3-4 周)
目标: 所有应用日志统一收集和查询。
交付物:
- Loki + Promtail 部署完成
- Promtail 正确提取 namespace/pod/app label
- 日志查询延迟 < 3 秒(24 小时范围)
- 应用日志格式统一为 JSON,包含 trace_id 字段
验收标准: 在 Grafana 里能用 namespace + app label 查询任意服务的最近日志。
阶段三:链路追踪 + 三支柱关联(第 5-8 周)
目标: 实现链路追踪,打通三支柱关联。
交付物:
- Tempo 部署完成
- OTel Operator 配置自动注入
- Prometheus 启用 Exemplar
- Grafana 配置好 Trace → Logs 跳转
- 一个端到端排障 Dashboard
验收标准: 从 Grafana 指标图表 → 点击 Exemplar → 打开 Trace → 跳转日志,全链路 < 5 次点击。
写在最后
三支柱不是终点,是起点。
当你的三支柱跑起来之后,下一步是 Profiles(性能剖析)——这是可观测性的第四支柱。用 Pyroscope 或 Parca 做 CPU/内存 profiling,能看到函数级别的资源消耗。
再往后是 eBPF 全面接管——所有指标、日志、链路都从内核层自动采集,应用完全无感。这是 2025-2026 年可观测性领域最大的趋势。
但不管工具怎么变,核心原则不变:可观测性的目的是缩短排障时间(MTTR),而不是堆工具。 一个简单的三支柱 + 良好的关联跳转,远胜于十个互不相通的高级工具。
从 Metrics 开始,一步步来。先把基础打扎实,再追求高级特性。
本文是 SRE 实战系列第 6 篇。系列前 5 篇覆盖了 SLO 落地、Sloth/Pyrra 工具实战、告警治理、监控存储选型,和本文一起构成了从"监控"到"可观测性"的完整路径。