Kubernetes 链路追踪 配置避坑指南
先说结论:大部分问题不是链路追踪坏了,是配置没对上
我一开始以为链路追踪就是装个 Collector、加个 Agent,然后 Jaeger 里就能看到漂亮的时间线。结果真在 Kubernetes 里配下来,发现坑比想象中多。最难受的是,它不像 Pod 起不来那样直接报错,很多时候是“有数据,但链路断了”“有 trace,但服务名全是 unknown_service”“采样率一开,Collector 先被压挂了”。
下面这些坑都是我实际踩过的,配置不一定适合所有人,但至少能帮你少绕几圈。
坑一:自动注入不是给所有 Pod 发福利
用 OpenTelemetry Operator 的时候,Instrumentation 建好了不代表所有 Pod 都会自动注入。它通常要靠 namespace 或 Pod 上的 annotation 触发。比如 Java 应用,我一开始只建了 Instrumentation,然后重启 Deployment,结果 Pod 里根本没有 agent。
后来才发现要给 Pod template 加 annotation,类似这样:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
而且这个 annotation 加在 Pod template 上,不是加在 Deployment 最外层。加错位置的话,kubectl describe pod 里看不到 init container,也不用怀疑人生,就是没注入上。
另外,多语言混跑的时候别指望一个 annotation 通吃。Java、Node、Python 的注入方式不一样,先挑一个服务跑通,再复制到其他服务,不然一起改完,出问题都不知道从哪查。
坑二:OTLP 地址写成 localhost,基本等于没配
这个坑非常经典。应用在 Pod 里,OTEL_EXPORTER_OTLP_ENDPOINT 如果写成 http://localhost:4317,那它连的是自己 Pod 里的 localhost,不是 Collector。除非你每个 Pod 都塞了 sidecar,否则数据根本出不去。
比较稳的做法是走 Collector 的 Service:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-collector.observability.svc.cluster.local:4317"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "grpc"
还要注意协议和端口对应。4317 一般是 gRPC,4318 是 HTTP。你端点写 4317,协议写 http/protobuf,有的 SDK 会直接不给你面子。别问我是怎么知道的,日志里一句 connection refused 都没有,就是没数据。
坑三:采样率别一上来就 100%
本地测试开 parentbased_always_on 没问题,生产也这么干就有点勇了。链路追踪的数据量比日志还夸张,尤其是 QPS 高的服务,Collector 的 CPU 和内存能肉眼可见地往上飙。
我一般会先这么配:
processors:
probabilistic_sampler:
sampling_percentage: 10
但要注意,如果你用的是 tail sampling,就不建议再在前面挂一个粗暴的概率采样,不然数据在进 tail 之前已经被砍掉一大半,后面想按错误率采样也采不到。tail sampling 最好放到 gateway Collector 上,而且要配 routing,不然同一个 trace 的 span 分散到不同 Collector,判定逻辑会很难受。
坑四:Collector 的 processor 顺序不是随便排的
Collector 配置里 processors 的顺序是真有讲究。我见过把 batch 放最前面、memory_limiter 放最后面的配置,平时看着没事,流量一上来就开始丢数据。
比较常见的顺序是:memory_limiter 靠前,k8sattributes、resourcedetection 这类补元数据的放中间,batch 放最后。大概像这样:
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/jaeger]
batch 不是越大越好。默认配置有时候偏保守,但你把 send_batch_size 调到几万,也不一定更快,可能只是让内存更刺激。先默认跑,观察 Collector 的 otelcol_processor_batch_batch_send_size 和内存,再慢慢调。
坑五:service.name 不填,后端全是 unknown_service
链路追踪后端最怕看到一堆 unknown_service。OpenTelemetry SDK 会尽量从环境变量或资源里取服务名,但你不显式配,它就可能给你一个默认值。Kubernetes 里更推荐用 OTEL_RESOURCE_ATTRIBUTES 或 OTEL_SERVICE_NAME 补上:
env:
- name: OTEL_SERVICE_NAME
value: "order-service"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "deployment.environment=prod,k8s.namespace.name=shop"
还有一点,service.name 别用 Pod 名。用 Pod 名做服务名,后端里会瞬间多出几千个“服务”,查起来很痛苦。Pod 名、节点名这些交给 k8sattributes 补,服务名保持稳定。
坑六:k8sattributes 要权限,也要等
k8sattributes 处理器很好用,能把 Pod 名、namespace、node 这些信息补到 span 上。但它不是开了就完事,Collector 的 ServiceAccount 得有权限看 Pod 和 Namespace。RBAC 没配好,日志里会提示拿不到元数据,然后你看到的 span 就只剩一个光秃秃的 service.name。
另外,刚启动的 Pod 可能还没被 informer 缓存到,Collector 那边会短暂拿不到属性。这个不是链路断了,等几秒或者重启一下 Collector 就好了。排查的时候先看 Collector 日志里有没有 k8sattributes 相关的报错,别一上来就怀疑应用。
坑七:Ingress 和 Mesh 可能把 traceparent 弄丢
跨服务调用能连上,但链路在网关那里断了,这种情况我也遇到过。HTTP 的 trace 上下文靠 traceparent 请求头传播,如果 Ingress、Nginx、Istio 之类的组件没有透传这个头,后面服务再努力也接不上。
先确认请求头有没有丢。可以在入口服务里打一条日志,看看 traceparent 还在不在。如果用了 Service Mesh,还要注意 Mesh 自己的追踪配置和 OpenTelemetry 注入会不会打架。有的环境里两个都开,结果 span 重复上报,后端看到的是两条半截链路。要么统一走 OTel,要么把 Mesh 的 tracing 采样调低,别让两边同时抢活。
坑八:出问题先看 Collector 自己的日志
我一开始排查链路问题,老盯着应用日志看,后来发现 Collector 的日志更有用。把 debug exporter 打开,或者临时加一个 logging exporter,能看到数据到底有没有进来、processor 有没有处理、export 有没有报错。
exporters:
debug:
verbosity: detailed
当然这玩意儿别长期开,数据量大的时候日志能把你磁盘刷满。临时开几分钟,确认 pipeline 通了就关掉。
最后一点:先让一条链路跑通,再谈全量
Kubernetes 链路追踪最容易犯的错,是一上来就把所有服务、所有 namespace、所有采样策略全配齐。配置越多,变量越多,出问题越难定位。
我的建议很土:先挑一个请求链路最短的服务,手动配好 endpoint、service.name 和采样率,确认 Jaeger 或 Tempo 里能看到一条完整 trace;然后再加自动注入,再加 k8sattributes,再上 tail sampling。每加一步都回头看一眼数据。链路追踪这东西,不出问题的时候你觉得它理所当然,一出问题就是“明明配了但就是没有”,一步一步来,比一次性梭哈靠谱多了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。