作者:姜远川,后端研发工程师,来自富途技术工程部
本文分享了富途证券引入基于 eBPF 的可观测性方案 DeepFlow,以应对传统 APM 所面临的诸如代码侵入性强和覆盖不全面等挑战的过程。在 TKE 超级节点等复杂场景的落地过程中,我们与社区密切合作,解决了多项兼容性和性能问题。通过 DeepFlow,我们快速定位了一个 DNS 解析引起的 MySQL 超时故障,验证了该方案的价值。未来,我们计划将内部观测平台和 DeepFlow 相结合,以持续拓展其应用场景并优化现有监控路径。
01|业务运维痛点
我们在内部已经建立了一套可观测平台,负责观测跨越多种编程语言的大量应用。我们通过代码插桩的方式提供了丰富的观测能力,但随着业务规模的增长和技术架构的演进,也面临了若干运维痛点:
- 代码入侵性: 不同编程语言需要独立的 SDK 维护,给业务团队带来了学习和升级的额外负担。
- 故障定位模糊: SDK 与业务代码的界限不明确,影响定位定界效率。
- 监控路径不完整: 传统埋点方法导致监控数据粒度不足,难以追踪基础服务、云组件以及网络调用的细节。
- 网络问题诊断效率低: 在复杂的云原生架构中,K8s 自身排查工具效率低,缺乏高效的工具。
因此我们希望引入一种跨语言,无侵入的观测技术来增强我们现有观测能力,于是把目光放到了 eBPF 技术上。
02|DeepFlow 引入原因
我们对基于 eBPF 技术的无侵入式可观测解决方案进行了调研,我们发现 DeepFlow 有以下几点非常吸引我们。
- 低内核版本的兼容性: 我们的一些机器运行在不支持 eBPF 的较低内核版本上。DeepFlow 通过提供 cBPF 作为后备选项,仍能采集关键应用及网络性能指标数据。
- 协议和指标的全面性: 相较于其他 eBPF 解决方案,DeepFlow 支持更多的协议,并能够采集更细致的性能指标,包括应用协议的 RED(请求、错误、延迟)指标和网络协议栈的关键指标如吞吐量、延迟、连接异常、重传次数和零窗口等。
- 数据融合与协议扩展: DeepFlow 支持与现有 APM 数据的集成,使得我们可以无缝地将其融合到富途的现有可观测平台中。此外,DeepFlow 支持自定义协议的扩展,可以适配我们自研 RPC 协议。
DeepFlow 的全面性,成为我们实现无侵入可观测性的首选平台。
03|DeepFlow 部署现状
目前,DeepFlow 已试运行于我们的一个生产集群,整体架构如下图所示。一套 deepflow-server 对应一个地区的所有 K8s 集群和 CVM 节点。
部署现状
04|DeepFlow 落地过程
由于我们的基础设施较为复杂,有些场景存在一些兼容问题,通过和社区紧密合作的方式解决了这些问题,下文重点介绍超级节点的部署以及部署后的性能优化过程。
TKE 超级节点部署
超级节点[1]是腾讯云提供的 Nodeless 解决方案,不具备传统的物理节点概念。因此,我们只能在 Pod 中部署 deepflow-agent。我们参考了 DeepFlow 官方提供的 Serverless Pod 部署方式[2],但这仍然为我们带来了一些挑战。
- agent 注册失败问题
在超级节点上部署 deepflow-agent 时,刚开始会存在注册失败的问题,deepflow-server 会报failed to register agent by querying DB table ip_ressource without finding data的错误日志。
日志报错:注册失败
deepflow-agent 报错:未指定集群名
deepflow-agent 的则报analyzer_ip not set, remote log disabled,与社区沟通排查后发现,deepflow-agent 部署在 Serverless Pod 时,需在 value 文件中指定 clusterNAME,使 agent 加入指定 k8s cluster,解决方式如下:
cat << EOF > values-custom.yaml
deployComponent:
- "daemonset"
- "watcher"
tke_sidecar: true
clusterNAME: xxxxxx # FIXME: 需要填写此值
EOF
- agent 初始化容器运行失败
Daemonset 部署的 deepflow-agent 默认会开启初始化容器,用于运行命令sysctl -w net.core.bpf_jit_enable=1来修改 Linux 内核参数。但由于超级节点缺失节点的概念,该命令无法成功运行,导致被注入的业务 Pod 状态异常。通过超级节点官方文档得知,需要通过注解来实现对内核参数的修改。
修改内核参数
为了解决以上问题,我们采取了分步骤的部署策略:
- 部署 deepflow-server,并通过 deepflow-ctl 命令获取 agent-group-id
deepflow-ctl
- 部署 deepflow-agent,在配置中指定 agent-group-id,并通过注解方式启用了内核参数bpf_jit_enable。同时,我们还需要关闭初始化容器,并添加容忍超级节点的污点(taints)
cat << EOF > values-custom.yaml
deployComponent:
- "daemonset"
- "watcher"
tke_sidecar: true
agentGroupID: $agentGroupID # 指定 agent-group-id
clusterNAME: $clusterNAME # 指定集群名称
tolerations: # 超级节点默认打了污点,需要容忍该污点
- key: "eks.tke.cloud.tencent.com/eklet"
operator: "Exists"
effect: "NoSchedule"
sysctlInitContainer: # 关闭初始化容器
enabled: false
podAnnotations: # 超级节点通过注解修改内核参数
eks.tke.cloud.tencent.com/host-sysctls: '[{"name": "net.core.bpf_jit_enable","value": "1"}]'
EOF
helm install deepflow-agent -n deepflow \
deepflow/deepflow-agent --create-namespace \
-f values-custom.yaml
采集和存储性能优化
在初次部署后,我们注意到采集到的数据有不完整的情况,以及 Request Log 中发现了大量响应状态为 Unknown 的请求。通过和社区合作分析,发现主要是因为宿主机负载过高、部分参数未优化导致,接下来我们详细分享下对于这两个问题我们是如何发现和解决的。
Unknown 请求
- 宿主机负载过高: 我们通过 DeepFlow Agent 的视图发现,一些 agent 因宿主机负载过高而出现了严重的丢包甚至停止运行的情况
负载过高
查看 agent 的日志,我们确认了过高的负载是主要的丢包原因,甚至还触发了熔断保护机制(优先保证业务正常运行)。
日志信息
对于此问题,我们通过调整机器配置和优化 Pod 的分布来减轻宿主机负载。虽然可以通过修改 deepflow-agent 的熔断阈值配置来应对问题,但注意这并不能从根本上避免丢包,因为负载高的场景下无法避免。
- 参数优化: 通过导入社区提供的 DeepFlow Alert Analysis 告警视图[3],可以看到 deepflow-server 写入时和 deepflow-agent 采集时均存在丢包的情况。
告警视图
为了应对此问题,我们通过 DeepFlow 社区提供的最佳实践案例[4],采取了以下优化措施:
- Agent 数据采集配置优化: 为了减少图中 agent 出现的丢包问题,我们在 `agent-group-config`[5] 中进行了以下配置调整[6],开启并增大了 cBPF 的缓冲块大小,并扩展了 payload 解析包长:
l7_log_packet_size: 4096 # 增大 payload 解析长度,确保 Header 字段能被充分解析
static_config:
afpacket-blocks-enabled: true # 允许设置 cBPF AF_PACKET 缓冲区大小
afpacket-blocks: 256 # 增大 cBPF AF_PACKET 缓冲区为 256MB
- Server端配置优化: 通过kubectl edit cm -n deepflow deepflow在 server.yaml[7] 中增加如下配置,提高向 clickhouse 写入队列的大小,减少 server 端写入时的丢包问题:
server.yaml:
ingester:
flow-log-decoder-queue-size:
eBPF 性能优化
我们发现 deepflow-agent 开启 eBPF 后在内核版是 4.19 的宿主机带来了明显的资源开销增长,以下是 eBPF 开启和关闭对 CPU 的性能消耗对比图。和社区反馈后,社区对此问题快速响应,以下分享对此问题的排查的整个过程。
性能消耗
社区反馈根据内部测试数据来说,不可能存在 eBPF 开启有这么大的资源开销,猜测可能是有其他 eBPF hook 点冲突导致。于是,需要先分析性能瓶颈在哪里,我们先通过 Linux 的 perf 工具进行性能分析,绘制宿主机 CPU 的火焰图,看到 python 进程有大量的 close 操作,而 DeepFlow 也 hook 了 close 的函数,因此扩大了影响。
erf record -o perf.ebpf-on.data -F 99 -a -g -- sleep 120
perf script -i perf.ebpf-on.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > perf.ebpf-on.svg
性能分析
为了更明确,目前的 close 操作的数据,社区提供了统计工具。检测到 10s 内,宿主机上的 close 操作就高达到 200w 次。与内部团队沟通 python 脚本是公共组件的基础脚本,改造动作很大,我们暂时决定先关闭 eBPF 功能,目前仅用 cBPF 的能力获取的可观测性数据也基本满足我们的述求,后续计划待后续宿主机内核版本升级到 5.x 后再开启 eBPF 功能。
close 调用统计
对于此问题,我们关注到社区也进行了优化,包括:
- [eBPF] Remove 'sys_exit_close' tracepoint #6984[8]:去掉 sys_exit_close hook 点,仅保留 sys_enter_close。
- [eBPF] Optimize the handling of eBPF close probe #6969[9]、[eBPF] Adjust the handling of close tracepoint #6983[10]:尽量减少读取内核结构内存的次数,通过查询 hash 标判断是否是有效的 socket,并尽快退出 close 的追踪。
05|实战:快速定位 MySQL 超时
业务收到了数据库超时的告警。影响到了多个的内部服务。通过内部观测平台分析,我们发现了多个应用请求数据库时偶现超过 5 秒的延迟。这是困扰业务许久的一个问题,为了协助业务得到更细粒度的监控细节,我们上了DeepFlow 进行定位。
服务定位
利用 DeepFlow 的应用调用日志,我们能看到原始调用被分解为 DNS 解析和 MySQL 调用两个过程。我们发现 DNS解析出现了 Unknown异常,未收到返回的情况。
调用日志
通过深入分析我们得出结论,DNS 解析异常并耗时 5s 是因为触发了 glibc 的 resolver 超时。同时我们在 GitHub上发现这是 K8s 的一个已知 bug,并在 Linux 内核 5.x 的版本中得到修复。
相关技术文档:
https://tencentcloudcontainerteam.github.io/2018/10/26/DNS-5-seconds-delay/[11]
github issue:
https://github.com/kubernetes/kubernetes/issues/56903[12]
结论: MySQL 调用时延超 5s 是因为 DNS 解析出现异常,且这是 K8s 在低内核版本的一个已知 bug。这是一个传统监控手段难以直接揭示的问题。我们将受影响的 Pod 迁移至运行 Linux 内核 5.x 版本的宿主机上解决了该问题。
06|未来规划
目前,我们对 DeepFlow 的应用尚处于初级阶段,在某些特定场景上看到了其潜力,目前仍在探索其更大的应用价值。为此,我们期望将 DeepFlow 集成到内部观测平台上,完善已有的监控路径和数据,主要包括以下几点:
- 采样控制: eBPF 技术会采集大量数据,必然会带来存储和计算成本的压力,也可能对业务运行产生性能影响。因此我们期望能够实现精确的采样控制。例如,只采集带有 traceId 的数据,用于过滤无法关联内部观测平台的数据,或提供百分比采样功能,在不缩小采集范围的前提下控制数据量。
- 调用链追踪的完善: 期望将内部的 traceId 注入 DeepFlow 收集的数据中,利用代码插桩和 eBPF 技术相结合的方式,细化 Span 粒度,提供更全面的调用链覆盖。
- 网络问题解决方案的完善: 我们计划将 DeepFlow 采集的指标数据导出至内部观测平台,以加强对云原生场景中网络协议栈相关指标的监控。同时,我们希望通过实践找到一些典型应用场景,形成一套基于 DeepFlow 定位网络问题的解决方案。
参考资料
[1]超级节点:https://cloud.tencent.com/document/product/457/74014
[2]DeepFlow 官方提供的 Serverless Pod 部署方式:https://deepflow.io/docs/zh/ce-install/serverless-pod/
[3]DeepFlow Alert Analysis 告警视图:https://ce-demo.deepflow.yunshan.net/d/DeepFlow_Alert_Analysis/deepflow-alert-analysis?orgId=1
[4]最佳实践案例:https://www.deepflow.io/docs/zh/best-practice/reduce-storage-overhead/
[5]agent-group-config: https://github.com/deepflowio/deepflow/blob/main/server/agent_config/example.yaml
[6]配置调整:https://www.deepflow.io/docs/zh/best-practice/agent-advanced-config/
[7]server.yaml: https://github.com/deepflowio/deepflow/blob/main/server/server.yaml
[8][eBPF] Remove 'sys_exit_close' tracepoint #6984: https://github.com/deepflowio/deepflow/pull/6984
[9][eBPF] Optimize the handling of eBPF close probe #6969: https://github.com/deepflowio/deepflow/pull/6969
[10][eBPF] Adjust the handling of close tracepoint #6983: https://github.com/deepflowio/deepflow/pull/6983
[11]https://tencentcloudcontainerteam.github.io/2018/10/26/DNS-5-seconds-delay/: https://tencentcloudcontainerteam.github.io/2018/10/26/DNS-5-seconds-delay/
[12]https://github.com/kubernetes/kubernetes/issues/56903: https://github.com/kubernetes/kubernetes/issues/56903