痛点:可观测性组件太多,运维成本比业务还高
典型的中大型 Kubernetes 集群里,可观测性栈长这样:
- Metrics: Prometheus + Grafana
- Logs: Fluentd/Filebeat → Elasticsearch → Kibana
- Traces: Jaeger/Zipkin + 各语言 SDK
三套系统、三种配置语法、三条数据管道。升级、扩容、故障排查都要分别处理。新服务接入时,开发要集成三种 SDK。运维花在"监控系统本身"的时间占比越来越高。
核心矛盾:可观测性的三大支柱(Metrics、Logs、Traces)本质上是同一个数据流的不同视角,却被割裂成完全独立的基础设施。
方案:OpenTelemetry Collector 作为统一数据管道
OpenTelemetry(简称 OTel)是 CNCF 毕业项目,目标是提供厂商无关的可观测性标准。其核心组件 OTel Collector 可以同时接收、处理、导出 Metrics + Logs + Traces,一个进程替代多个 Agent。
架构对比:
| 维度 | 传统方案 | OTel Collector 统一方案 |
|---|---|---|
| Agent 数量 | 3-4 个 DaemonSet | 1 个 DaemonSet |
| 配置语言 | Prometheus YAML + Fluentd conf + Jaeger env | 统一 YAML |
| 数据格式 | 各自私有协议 | OTLP(开放标准) |
| 后端切换成本 | 重写 pipeline | 改一行 exporter |
| 资源占用(典型值) | 800MB-1.2GB/节点 | 200-400MB/节点 |
实操:3 步部署 OTel Collector 统一管道
第 1 步:用 Helm 部署 OTel Collector DaemonSet
# 添加 OTel Helm repo
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
# 安装为 DaemonSet 模式(每节点一个 Collector)
helm install otel-collector open-telemetry/opentelemetry-collector \
--namespace monitoring --create-namespace \
--set mode=daemonset \
--set image.tag="0.102.0" \
-f otel-values.yaml
第 2 步:编写统一配置(Metrics + Logs + Traces 一个文件搞定)
otel-values.yaml 核心配置:
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# 兼容 Prometheus 抓取
prometheus:
config:
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# 采集节点日志
filelog:
include: [/var/log/pods/*/*/*.log]
operators:
- type: router
routes:
- output: parser-json
expr: 'body matches "^\\{"'
- id: parser-json
type: json_parser
timestamp:
parse_from: attributes.time
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
processors:
batch:
timeout: 5s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 512
spike_limit_mib: 128
k8sattributes:
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.deployment.name
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.ip
exporters:
# Metrics → Prometheus Remote Write
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
resource_to_telemetry_conversion:
enabled: true
# Logs → Elasticsearch
elasticsearch:
endpoints: ["https://elasticsearch:9200"]
logs_index: "otel-logs"
tls:
insecure_skip_verify: false
ca_file: /etc/ssl/certs/es-ca.crt
# Traces → Jaeger (OTLP)
otlp/jaeger:
endpoint: "jaeger-collector:4317"
tls:
insecure: true
service:
pipelines:
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, k8sattributes, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, k8sattributes, batch]
exporters: [elasticsearch]
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/jaeger]
第 3 步:应用侧接入(以 Python 服务为例)
# requirements.txt 新增:
# opentelemetry-api==1.25.0
# opentelemetry-sdk==1.25.0
# opentelemetry-exporter-otlp==1.25.0
# opentelemetry-instrumentation-fastapi==0.46b0
from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
# 统一指向 OTel Collector
OTEL_ENDPOINT = "http://otel-collector:4317"
# Traces
trace_provider = TracerProvider()
trace_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint=OTEL_ENDPOINT, insecure=True))
)
trace.set_tracer_provider(trace_provider)
# Metrics
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint=OTEL_ENDPOINT, insecure=True),
export_interval_millis=10000,
)
metrics.set_meter_provider(MeterProvider(metric_readers=[metric_reader]))
# 业务代码直接用
tracer = trace.get_tracer("my-service")
meter = metrics.get_meter("my-service")
request_counter = meter.create_counter("http_requests_total")
with tracer.start_as_current_span("handle_request"):
request_counter.add(1, {"method": "GET", "path": "/api/users"})
一套 SDK、一个 Endpoint、Metrics + Traces 同时上报。Logs 通过 Collector 的 filelog receiver 自动采集,应用零改动。
避坑:3 个常见问题
1. 内存 OOM:Collector 处理高流量时被 Kill
原因:默认无内存限制,高峰期 batch 堆积导致 OOM。
解决:必须配置 memory_limiter processor,并放在 pipeline processors 列表的第一个位置:
processors:
memory_limiter:
check_interval: 1s
limit_mib: 512 # 硬上限
spike_limit_mib: 128 # 突发缓冲
同时 K8s resources.limits 要大于 limit_mib,建议设为 1.5 倍(768Mi)。
2. Prometheus 指标丢失:抓取目标找不到
原因:k8sattributes processor 需要 ServiceAccount 有 Pod list 权限,否则 relabel 失败。
解决:确认 Helm Chart 的 RBAC 配置:
clusterRole:
create: true
rules:
- apiGroups: [""]
resources: ["pods", "namespaces"]
verbs: ["get", "list", "watch"]
3. 日志重复采集:filelog receiver 重启后重读
原因:Collector 重启后 checkpoint 丢失,重新从文件头读取。
解决:启用 file_storage 扩展持久化 offset:
extensions:
file_storage:
directory: /var/lib/otelcol/file_storage
receivers:
filelog:
storage: file_storage
include: [/var/log/pods/*/*/*.log]
并将 /var/lib/otelcol/ 挂载为 hostPath 或 PVC。
总结
| 迁移收益 | 效果 |
|---|---|
| Agent 数量 | 3-4 → 1,DaemonSet 维护成本直降 |
| 节点内存占用 | 减少 40-60%(实测) |
| 新服务接入 | 1 套 SDK + 1 个 Endpoint,5 分钟搞定 |
| 后端替换 | 改 exporter 配置即可,不动应用代码 |
| 数据关联 | TraceID 贯穿 Metrics/Logs/Traces,排障效率翻倍 |
迁移建议:不需要一步到位。先部署 Collector 作为 Proxy 模式接收 OTLP,新服务直接对接;老服务保留现有 Agent,逐步迁移。Prometheus 可以继续用——Collector 的 prometheus receiver 完全兼容现有 scrape 配置,平滑过渡零风险。
OpenTelemetry 已经是 CNCF 活跃度第二的项目(仅次于 Kubernetes),2026 年 Logs 规范也已 GA。现在上车,正是时候。