基于opentelemetry-envoy的非侵入式可观测链路追踪实践

前言

第一次接手系统时无从入手的感觉,对于一个初出茅庐的小卡拉米(博主)影响颇深,只能非常蛋疼的从 Linux基础指标 逐一从进程开始排查,使用topsystatperfsar等工具去查询CPU使用率/内存占用、平均负载、软中断、某进程活跃连接数等基础指标。

显然基础指标不是云原生系统入手排查的第一选择,它只适合用于Linux系统性能优化

那么对于系统规模扩大,服务之间的调用链条变得越来越复杂的云原生系统,对现代请求响应协议(截至目前常用的HTTP/1.1、HTTP/2.0)进行埋点与解析则尤为重要,一个完善的链路追踪系统可以帮助运维成员提升MTBF(平均故障时间间隔),降低MTTR(故障平均修复时间)

所以针对该背景的研究方向,博主基于自身工作需求进行了追踪系统的分析,并在这里分享,希望可以帮助路过的各位。

虽然没什么人看

从需求出发

对于一个团队的系统,链路追踪系统虽然重要,但是该系统实现方式不一,需要根据各团队的需求和已知能力进行研判。

方法 侵入性 数据粒度 支持多语言 典型工具
日志 中低 Fluentd, ELK, OpenTelemetry Logging
Envoy/Proxy Envoy, Istio, OpenTelemetry Collector
eBPF 高(系统级) Pixie, Cilium Hubble
SDK/Agent OpenTelemetry Agent, Jaeger Agent
手动埋点 OpenTelemetry SDK, Jaeger Client

博主这里按侵入程度数据来源方式来分类:

非侵入式/低侵入式:

  1. 基于日志:直接在业务逻辑中打印日志(可包含 trace_id、span_id、parent_id),通过日志收集系统(如 ELK、Fluentd、Loki)聚合并分析调用链。

    不过粒度有限,难以捕获网络层细节。

  2. L7网络代理:在服务入口/出口部署 Envoy、Istio sidecar 或 Nginx/L7 代理,自动注入 Trace Header(traceparent 等),捕获请求信息并上报到 Collector。

    中间层的加入可能增加系统复杂度,对管理系统的选手有一定挑战,例如http/1.1、http/2.0的代理层管控。
    同时使用代理可能会影响一定性能,会有一定资源占用。

  3. 内核追踪 — eBPF:利用 Linux eBPF 在内核层捕获网络包、系统调用、函数调用等事件,生成 trace。

    显然的学习成本高,调试困难。团队有高手的话可以使用。

  4. 代码SDK:在服务中加载 OpenTelemetry / Jaeger SDK 或 Java/Go agent,自动 hook HTTP、gRPC、数据库等客户端调用。

    目前已经有现成的工具可用,即otel。但是在某些场景下可能与业务框架冲突!

侵入式

有且只有一种,即开发者在关键业务逻辑处手动创建 span 并设置 trace_id。这非常费人力,特别在已成型的IT系统中更改(很蛋疼了)。不过完全可控,可以精确捕获自定义事件。

涉及的组件介绍

简单介绍一下本次实践分享使用的组件与工具。

OpenTelemetry

是一个可观测性框架和工具包,详见什么是OpenTelemetry

其中OpenTelemetry Collector是核心组件,它可以收集、处理,也可以导出遥测数据(traces、metrics、logs),应用只需发给 Collector,不用直接对接 Jaeger/Prometheus/Elastic。

Jaeger

是一个开源的 分布式追踪(Distributed Tracing)系统,提供UI和API。

从架构上来说jaeger由agent、collector、query和ingester这四个部分组成。

  • agent:数据源,将追踪数据发送到collector,如本次实践的envoy
  • collector:接收Agent发送的数据,验证、索引、转换、存储,如ElasticSearch、Kafka
  • query:提供查询服务API和用户界面
  • ingester:从Kafka读取数据并写入其他存储

对于单主机或者性能要求不大的直接使用 jaegertracing/all-in-one 这一个容器就可以满足部分需求了,通常内存占用50MB。

jaeger主页

基于sidecar+otel-sdk的实践

博主从自身工作出发,尝试对一个已搭建了一段时间的系统实现链路追踪系统。

首要考虑就是不费大量人力,毕竟只作一个突出绩效的功能。

采集方式选型:envoy(完成L7代理的同时可自行生成traceID)+otel-SDK(针对java进行探针,可以hook HTTP请求和SQL相关操作)。

otel+jaeger搭建

docker-compose.yaml部分:注意otel-collector的挂载,有一个config文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3.8'
services:

otel-collector:
image: otel/opentelemetry-collector:v0.83.0
container_name: otel-collector
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml:ro
command: ["--config", "/etc/otel-collector-config.yaml"]
ports:
- "4317:4317" # 外部服务export指标的端口
restart: unless-stopped

jaeger:
image: jaegertracing/all-in-one:1.70.0
container_name: jaeger
ports:
- "16686:16686" # Jaeger UI
#- "14250:14250" # Jaeger gRPC (collector)
restart: unless-stopped

下面是otel-collector-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
receivers:
otlp:
protocols:
grpc:
http:

processors:
batch:
timeout: 5s

exporters:
jaeger:
endpoint: "jaeger:14250" # 要确保otel与jaeger在一个容器
tls:
insecure: true
logging:
loglevel: info

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger, logging]

要确保otel与jaeger在一个容器,否则通过”jaeger”连接时gRPC会报错"error": "failed to push trace data via Jaeger exporter: rpc error: code = Unavailable desc = connection error: desc = 'transport: Error while dialing: dial tcp: lookup jaeger on 114.114.114.114:53: no such host'"

envoy实践

envoy适合项目中使用容器管理的服务,只需利用envoy将流量经由其流入流出即可。当然前提得是bridge网络。

这里使用的是envoy1.30.2。

envoy.yaml部分:请注意注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
static_resources:
listeners:
- name: grpc_listener
address:
socket_address:
address: 0.0.0.0
port_value: 23333 # 输入你服务监听的端口
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: grpc_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: FishingrodService_svc
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

tracing:
provider:
name: envoy.tracers.opentelemetry
typed_config:
"@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig
grpc_service:
envoy_grpc:
cluster_name: otel_collector
timeout: 0.5s
service_name: "FishingrodService-envoy"
spawn_upstream_span: true # spawn_upstream_span 允许 Envoy 为 gRPC 上游请求生成 child span,并自动处理 propagation
access_log: # 这行可以打印收到的http请求日志
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /tmp/envoy-access.log
log_format:
text_format: "[%START_TIME%] %REQ(:AUTHORITY)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %UPSTREAM_HOST%\n"

clusters:
- name: FishingrodService_svc
connect_timeout: 0.5s
type: logical_dns
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: FishingrodService_svc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: FishingrodService
port_value: 23333 # 输入你服务监听的端口
- name: otel_collector
type: logical_dns
lb_policy: round_robin
http2_protocol_options: {}
load_assignment:
cluster_name: otel_collector
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: otel-collector
port_value: 4317 # otel的端口

docker-compose.yaml改造部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: "3"
services:
FishingrodService:
... # 原有配置就掠过
expose:
- 23333 # 从ports改为expose
networks:
- FishingrodService_net
envoy:
container_name: FishingrodService-envoy
image: envoyproxy/envoy:v1.30.2
depends_on:
- FishingrodService
networks:
- FishingrodService_net
volumes:
- /opt/FishingrodService/envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "23333:23333" # 不会导致FishingrodService和envoy端口冲突的,两者网络命名空间不同,不理解的自行补习
extra_hosts:
- "otel-collector:169.254.169.254" # 这里填写otel-collector的IP地址或宿主机地址,毕竟已经暴露到了宿主机
command: >
/usr/local/bin/envoy
-c /etc/envoy/envoy.yaml
--log-level debug # 这行command可以打印envoy的debug日志


networks:
FishingrodService_net:
driver: bridge

otel-SDK实践: java

参考otel示例入门,在Github仓库下载SDK。

对于java容器就不使用envoy了,sdk也能生成traceID。

docker-compose.yaml改造部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
IamJava:
container_name: IamJava
image: IamJava:latest
volumes:
...
# - /etc/hosts:/etc/hosts:ro # 如果你挂载了hosts则extra_hosts会失效,注意选择挂载hosts的话需要写入"169.254.169.254 otel-collector"
- /opt/IamJava/opentelemetry-javaagent.jar:/opt/opentelemetry-javaagent.jar
extra_hosts:
- "otel-collector:169.254.169.254" # 同理
restart: always
network_mode: host
working_dir: "/opt/IamJava"
command: java -Xmx4g -Dfile.encoding=UTF-8 -Dclient.encoding.override=UTF-8 -javaagent:/opt/opentelemetry-javaagent.jar -jar /opt/IamJava/gugugaga.jar
environment:
...
- OTEL_SERVICE_NAME=IamJava
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # grpc端口
- OTEL_EXPORTER_OTLP_PROTOCOL=grpc # 走grpc, otel应该默认是http/protobuf即http/1.1
- OTEL_PROPAGATORS=tracecontext,baggage
- OTEL_LOGS_EXPORTER=none # 不处理日志, 暂时只考虑链路追踪

最终效果

jaeger链路追踪效果

采样

todo

猜你需要

追踪 - envoy

上下文传播故障排除


基于opentelemetry-envoy的非侵入式可观测链路追踪实践
https://www.fishingrodd.cn/2025/11/28/基于opentelemetry-envoy的非侵入式链路追踪实践/
作者
FishingRod
发布于
2025年11月28日
更新于
2025年11月28日
许可协议