一、本地全链路实战:docker-compose
一套"应用 → Collector → Jaeger"的最小生产形态,十分钟搭好。
docker-compose.yaml:
services:jaeger:image:jaegertracing/all-in-one:latestports:["16686:16686"]# UI;OTLP 端口只给 collector 内网用networks:[otel]otelcol:image:otel/opentelemetry-collector-contrib:latestcommand:["--config=/etc/otelcol/config.yaml"]volumes:-./otelcol.yaml:/etc/otelcol/config.yaml:roports:["4317:4317","4318:4318","13133:13133"]depends_on:[jaeger]networks:[otel]networks:{otel:{}}otelcol.yaml:
receivers:otlp:protocols:grpc:{endpoint:0.0.0.0:4317}http:{endpoint:0.0.0.0:4318}processors:memory_limiter:{check_interval:1s,limit_mib:512,spike_limit_mib:128}batch:{timeout:5s,send_batch_size:1024}exporters:otlp/jaeger:endpoint:jaeger:4317tls:{insecure:true}debug:{verbosity:basic}extensions:health_check:{endpoint:0.0.0.0:13133}service:extensions:[health_check]pipelines:traces:receivers:[otlp]processors:[memory_limiter,batch]exporters:[otlp/jaeger,debug]启动与验证:
dockercompose up-dcurlhttp://localhost:13133# Collector 健康# 跑 03 章的应用(SDK 端点指 http://localhost:4317)# 打开 http://localhost:16686 查链路改造练习(强烈建议动手):
- 加
spanmetricsconnector + Prometheus exporter,把 RED 指标抓出来; - 把 batch 参数调小,观察 debug 输出频率变化;
- 故意配错 jaeger 地址,观察 exporter 重试日志与队列指标——体会"数据在哪丢的"。
二、采样策略决策
按场景对号入座:
| 场景 | 方案 | 理由 |
|---|---|---|
| 开发/测试 | AlwaysOn | 全量好排查 |
| 低流量内部服务 | AlwaysOn 或高比例 ratio | 数据量可承受,全量价值大 |
| 常规生产 | SDK:ParentBased(TraceIDRatioBased(0.05~0.5)) | 简单稳定,链路完整性由 ParentBased 保证 |
| 错误必须全留 | SDK 全采 + Collectortail_sampling | 见下 |
| 高价值交易链路 | 对特定 route 强制采样(SDK 端按属性自定义采样器) | 交易全留,其余降比 |
processors:tail_sampling:decision_wait:10s# 等链路走完再做决策的窗口num_traces:100000# 内存中缓存的链路数policies:[{name:errors,type:status_code,status_code:{status_codes:[ERROR]}},{name:slow,type:latency,latency:{threshold_ms:1000}},{name:baseline,type:probabilistic,probabilistic:{sampling_percentage:1}},]注意:尾部采样要求链路完整经过同一 Collector 集群(Gateway 层),且decision_wait期间内存缓存全量链路——容量规划要做足。另一个坑:SDK 端已做了头采,尾采只能在"已采集"的 10% 里再选,错误链路早在 SDK 就被丢了。所以"错误全留"必须 SDK 全采或调高头采比例,成本核算要清楚。
三、SDK 端开销控制清单
埋点是侵入业务进程的,以下清单控制住"税负":
- 采样前置:
ParentBased(TraceIDRatioBased(p)),Drop 的 Span 零分配(04 章原理); - SpanLimits:限制属性数/长度,防止恶意或超大属性撑爆内存;
- 只用 Batcher,不要用 Syncer(同步导出阻塞业务);
- 批量参数按吞吐调:高 QPS 服务把
OTEL_BSP_MAX_QUEUE_SIZE调到 10000+,并监控队列水位; - 属性低基数:attributes 只放有限取值集合;user_id、URL 全文放日志;
- 优雅退出:监听 SIGTERM →
tp.Shutdown(ctx),K8s 环境给 preStop/terminationGracePeriodSeconds 留时间; - 按需选信号:不是每个服务都要三信号全开,内部工具先 trace 即可;
- eBPF 方案兜底:存量服务不想改代码?看 08 章 Go 自动插桩(odigos / go-instrumentation)。
四、Collector 高可用与扩容
- 无状态化:Agent 层天然无状态;Gateway 队列数据在内存,挂了会丢——重要链路可配
file_storage扩展把队列落盘; - 水平扩容:OTLP/HTTP 经 LB 直接轮询;OTLP/gRPC 需要 LB 支持 HTTP/2 长连接分摊(istio/nginx h2 或头部 LB),否则流量倾斜;
- 限流保护:exporter 侧配
sending_queue+ retry;后端故障时队列顶住,恢复后追平; - 分级部署:Agent(每节点,轻)→ Gateway(集中,重)双层;Gateway 按命名空间/业务域拆多套,爆炸半径小;
- 自监控告警(必做):
otelcol_exporter_queue_size接近上限;otelcol_exporter_send_failed_spans持续增长;otelcol_receiver_refused_spans(内存限流触发);- Collector 副本数/重启次数。
五、安全:TLS、认证、多租户
- 传输加密:应用 → Collector、Collector → 后端全程 TLS(本地开发才允许 insecure);
- 认证:
- 应用 → Collector:bearer token 或 mTLS(用 Collector 的
bearertokenauth扩展 + receivers 的auth配置); - Collector → 后端:后端凭据只配在 Gateway,Agent 层零凭据;
- 应用 → Collector:bearer token 或 mTLS(用 Collector 的
- 脱敏:
attributes/transformprocessor 删改敏感字段(http.authorization、手机号正则替换),在数据出门前完成; - 多租户路由:
# 请求带 tenant 头 → 路由到对应租户的 exporterprocessors:routing:from_attribute:tenantdefault_exporters:[otlp/pool]table:-value:acmeexporters:[otlp/acme]exporters:otlp/acme:{endpoint:acme-backend:4317,tls:{insecure:false}}- 网络面:K8s 里用 NetworkPolicy 限制 4317/4318 只接受业务 Pod 与 Collector 互访。
六、后端选型速查
| 信号 | 后端 | 特点 |
|---|---|---|
| Traces | Jaeger | 最经典,UI 成熟,本地开发首选;大规模存储依赖 ES/Cassandra |
| Traces | Tempo(Grafana) | 对象存储廉价,Grafana 深度集成 |
| Traces | SigNoz / Uptrace | 自带 OTLP + ClickHouse,APM 一体化开源 |
| Metrics | Prometheus / Mimir / VictoriaMetrics | 事实标准 / 大规模 / 高性能 |
| Logs | Loki | 对象存储便宜,标签索引模型 |
| Logs | Elasticsearch / ClickHouse | 全文检索强 |
| 全家桶 | Grafana Stack(LGTM) + Grafana | 一站式,OTel 原生支持好 |
选型原则:先用 Jaeger + Prometheus + Loki 跑通,量级/预算变化再迁移——OTel 的意义正是"埋点不变,后端随便换"。
七、常见坑 Top 10
| # | 现象 | 原因 → 解法 |
|---|---|---|
| 1 | Jaeger 偶尔丢链路 | 没调Shutdown,批量队列残留 → defer shutdown + 优雅退出 |
| 2 | 跨服务链路断 | ctx 没传 / propagator 没注入提取 → 检查 otelhttp/otelgrpc 是否两端都包 |
| 3 | 采样后错误链路也没了 | 头采把错误 trace 丢了 → ParentBased+ratio 改为尾采,或错误请求强制采样 |
| 4 | 后端被 Span 洪峰打挂 | 没配 batch / exporter 没队列 → batch + sending_queue + 限流 |
| 5 | Collector OOMKilled | 无 memory_limiter 或 limit 太高 → 必配 memory_limiter + GOMEMLIMIT |
| 6 | 日志和 trace 对不上 | 打日志没传 ctx →logger.InfoContext(ctx,...)或 otelslog 桥接 |
| 7 | 高基数指标打爆存储 | 属性放了 user_id/URL 全文 → 低基数化,明细落日志 |
| 8 | gRPC 过 LB 流量不均 | HTTP/2 长连接粘住 → LB 支持 h2 轮换,或换 OTLP/HTTP |
| 9 | K8s 元数据缺失 | k8sattributes 无 RBAC → 给 Collector SA 加 get/list/watch pods 权限 |
| 10 | 时序图上父子时间错乱 | 跨机时钟偏移 → 保证 SERVER/CLIENT 时间戳语义,部署 NTP/chrony |