云原生可观测性落地:用 OpenTelemetry 把指标、日志、链路一次打通
问题不是"没有监控",而是"三个系统各查一遍"
很多团队上云之后并不缺监控:主机有基础监控,应用打了 Prometheus 指标,日志进了 LTS,链路有 APM。但真出故障时,值班的人还是要开着四五个页面来回对照——先在告警里看到 P99 飙高,再去日志里搜报错,最后靠猜去翻链路。一次排查半小时,其中二十分钟花在"对齐时间戳和找同一个请求"上。
根因不是工具不够,而是三份数据之间没有共同的锚点。指标告诉你"哪里慢了",日志告诉你"报了什么错",链路告诉你"慢在哪一跳",只有三者能通过同一个 trace ID 串起来,排查才会从"翻找"变成"顺着走"。
这篇记录一次实际改造:一个跑在 CCE 上的 Java 服务,用 OpenTelemetry 统一埋点,Collector 做汇聚,指标进 Prometheus,日志带上 trace ID,最后在 Grafana 上做成一个能下钻的面板。
先想清楚三类数据各自回答什么问题
改造前最容易犯的错,是把三类数据当成"都要有",结果每类都采了一点,每类都不好用。它们的定位其实很清楚:
| 数据类型 | 回答的问题 | 成本特征 | 采样策略 |
|---|---|---|---|
| 指标 Metrics | 系统现在健康吗,趋势往哪走 | 低,可按时间聚合长期保留 | 全量,但要控标签基数 |
| 日志 Logs | 这一刻具体发生了什么错 | 高,随流量线性增长 | 全量采错误,正常日志分级 |
| 链路 Traces | 这个请求慢在哪一跳 | 很高,随调用深度放大 | 头部采样 + 错误全采 |
记住这个分工,就不会出现"用日志算 QPS"或者"用链路做容量规划"这类事倍功半的做法。
埋点:别手写,用 SDK 的自动注入打底
OpenTelemetry 的 Java Agent 能自动埋住常见框架(Spring MVC、JDBC、Redis、HTTP Client),零代码改动就能拿到 span。先让它跑起来,再考虑手动补关键业务属性:
java -javaagent:/opt/otel/opentelemetry-javaagent.jar \ -Dotel.service.name=order-service \ -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \ -Dotel.resource.attributes=deployment.environment=prod,k8s.namespace.name=prod \ -jar app.jar
自动埋点之后,业务上真正需要手写的只有一件事:把"这次请求是谁的、在做什么"写进 span。比如订单服务里补一条:
Span span = Span.current();
span.setAttribute("order.id", orderId);
span.setAttribute("order.channel", channel);
span.setAttribute("biz.result", result);
就这三个属性,让后面所有按订单号、按渠道的筛选成为可能。反过来,如果只加了 trace 却没有业务属性,链路图看着漂亮,实际问题来了还是定位不到具体哪类订单。
Collector 是整个方案里最值钱的一层
很多团队省掉 Collector,让应用直连后端。这在服务数量超过十个之后一定会出问题:每个服务都要配一遍导出地址、采样率、批处理参数,换后端等于全量改配置。Collector 的价值在于把"采集"和"发送"解耦:
receivers:
otlp:
protocols:
grpc: {endpoint: 0.0.0.0:4317}
processors:
batch: {}
memory_limiter:
check_interval: 1s
limit_percentage: 75
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow
type: latency
latency: {threshold_ms: 500}
exporters:
prometheus:
endpoint: 0.0.0.0:8889
otlphttp/logs:
endpoint: https://lts.example.com/v3/ingest
service:
pipelines:
traces: {receivers: [otlp], processors: [memory_limiter, tail_sampling, batch], exporters: [otlphttp/logs]}
注意 tail_sampling:它要等整条 trace 结束才能判断,所以必须先于 batch 且 decision_wait 不能太短。这个位置放错,采样策略会静默失效——日志里不报错,只是错误链路莫名其妙被丢掉了。我们踩过一次,花了半天才定位到是 processor 顺序问题。
指标:黄金信号 + 严格控基数
指标最容易失控的不是数量,是标签。曾经有同事给 HTTP 指标加了 user_id 标签,一天之内 Prometheus 内存翻了三倍。经验是:任何可能取值为高基数的字段(用户 ID、订单号、URL 全路径、错误堆栈)一律不进标签,需要就用日志或 trace 去查。
真正值得建的指标就四类,对应四个黄金信号:延迟(P50/P95/P99 直方图)、流量(QPS)、错误(按状态码分组)、饱和度(线程池队列、连接池使用率、GC 停顿)。这四类做扎实,比堆几十个自定义指标有用得多。
面板:从"总览"一路下钻到"某一条 trace"
Grafana 上我们只做了一个主面板,纵向四段:顶部是服务级 QPS 与错误率折线;第二段是 P99 延迟热力图,能一眼看出是哪个时间窗异常;第三段是错误码分布,点某个错误码会带上时间范围跳转到日志查询;第四段是慢链路 Top N,点进去直接是 trace 详情。
关键是所有面板共享同一个时间范围和同一个变量(服务名、环境),点图下钻时筛选条件自动带上。这一步做完,值班同事的动作从"开五个页面找"变成"点三下",平均定位时间从半小时降到五分钟以内。
四个真实的坑
一是时钟漂移。容器节点之间差几秒,链路时间线就会错位,看着像"子调用早于父调用"。上线前统一开 NTP,并且在面板上标注时区。
二是日志没带 trace ID。日志和链路各自独立采集,如果没有把 trace ID 打进日志的 MDC,前面所有工作等于白做。改造成本很低,一行日志格式加 %X{trace_id} 就够。
三是采样率一刀切。开发环境 100% 采样没问题,生产环境按 10% 头部采样会漏掉长尾错误。用尾部采样按"错误 + 慢请求"保留,正常流量再降采样,成本能降一半以上而关键信息不丢。
四是把可观测性当成上线后再补的事。服务已经跑了半年再回头加埋点,业务属性往往补不全。新服务从第一天就带 trace 上下文,成本几乎为零。
小结
可观测性不是买几个产品,而是让三类数据共享一个锚点。落地顺序建议是:先上 Agent 自动埋点拿到链路骨架,再用 Collector 统一出口并配好尾部采样,然后把指标标签收紧到黄金信号,最后做一块能下钻的面板。这套东西不需要大改架构,一个服务半天能改完,但收益是长期的——故障来的时候,你不用再靠猜。
- 点赞
- 收藏
- 关注作者
评论(0)