Grafana 可观测性 grpc 长链接处理
问题描述:
Grafana Agent 和 Tempo Distributor 都是使用 Grpc 协议来传递 Tracing Data,同时新版的 OTEL 协议默认也是 GRPC
GRPC 的传输效率会比 HTTP 高一些,同时可以降低短连接带来的系统压力。所以我们也优先推荐使用 grpc 协议来搜集 OTEL data
但是由于 grpc 默认不会中断链接,只要 client 和 server 不发生异常的情况下,不会重新创建新的链接,也导致 Grafana Agent 和 Distributor 容器负载严重的不均衡
同时由于我们经常会对这些基础组件进行滚动升级或者 k8s 迁移 pod,会导致所有的 grpc 都集中到了一个容器中,容易导致容器 OOM 和负载不均衡。也无法达到高可用的效果,同时我们也无法实现动态扩容的能力
TLDR;
Grafana 官方对这些 grpc 的配置都没有提及,需要自己看源码查找,虽然 Grafana Agent 在 Flow mode 提过,不过我们使用的是 static mode
主要是对 receiver 增加一些参数重启服务即可
具体的参数说明请参考文档:
https://github.com/grpc/grpc-go/blob/v1.57.0/keepalive/keepalive.go#L50
https://pkg.go.dev/google.golang.org/grpc/keepalive#ServerParameters
调研过程
Server + GRPC
从官方 Golang 的 grpc 库可以看到 其实针对任何的 grpc 协议都是有一些 client 和 server 的 keepalive 的配置信息的 https://pkg.go.dev/google.golang.org/grpc/keepalive
但是这些配置在 Grafana 官网文档中都没有提及,都是只有简单的 grpc 的监听端口的说明
https://grafana.com/docs/agent/latest/static/configuration/traces-config/
https://grafana.com/docs/tempo/latest/configuration/#distributor
通过查询 Grafana tempo 的源码 https://github.com/weaveworks/common/blob/dd9e68f319d5ddfb68002a238d481265af23fa3b/server/server.go#L106C58-L106C94 可以看到有关于 server 的一些 grpc 参数配置,但是这些参数都没有体现到文档,于是进行了一次尝试通过在 server 配置增加 grpc keepalive 的配置进行测试
重新部署 Distributor 后观察两个容器的日志,依然是只有一个容器有日志,另外一个容器没有任何输出
通过查询 Distributor 的 3200 的 server 端口的 status , 可以看到配置已经生效,但是这个配置仅限于 3200 的 server 端口,并不是 Distributor 的 receiver 的 grpc 的端口,这是两个不同的配置模块
Receiver + grpc
只能重新阅读源码,看一下 receiver 的启动逻辑 https://github.com/grafana/tempo/blob/ef29986dc4444befaf11ee61c001652de8d948d7/modules/distributor/config.go#L35C61-L35C61
Receiver 本身是一个 map[string]string 的结构,在这个位置会被 OTEL 库使用 https://github.com/grafana/tempo/blob/ef29986dc4444befaf11ee61c001652de8d948d7/modules/distributor/receiver/shim.go#L146
同时在 https://github.com/grafana/tempo/blob/ef29986dc4444befaf11ee61c001652de8d948d7/modules/distributor/receiver/shim.go#L196 会把这些信息强制转成 otlpreceiver.Config
继续深究这个 struct,可以看到 最终这个 struct 和 KeepaliveServerParameters 已经集成在一起了,而这里的参数和上面提到的 golang grpc 的库是一模一样的。
也就是说其实 otlpreceiver.config 其实是支持 grpc 的 keepalive 的参数的。这是这个类型转换是通过一个 map[string]string 的形式来强制转换的。
至此,我们已经找到了 Distributor 配置 keepalive 参数的方法,剩下的就是实验,通过对原来的 receiver 添加 keepalive 等参数然后重新部署
从结果中可以看到我新扩容一个节点之后,直接查看新创建的 POD 的日志,可以从里面看到已经有新的 traceid 显示了说明 Grpc server 配置的 keepalive 参数已经生效了,现在可以实现负载了。
继续看 Grafana Agent 如何配置,惊喜的发现他们的配置模式是类似的,都是复用了 otlpreceiver.Config, 所以直接也添加类似的配置
因为 Grafana Agent 没有详细的日志输出,所以只能通过 tcpdump 方案确认是否生效
可以看到在 11:26:51 的时候有一个容器向 Grafana Agent 14250 发起了 TCP 握手 SYN,建立成功后就开始 PUSH 数据了在
11:27:02 的时候,可以看到 Grafana agent 的 14250 端口发起了 Finish TCP 结束包请求,完成 TCP 终止同
时查看另外一个容器是否有 grpc 请求,可以看到,另外一个 POD 也有类似的现象
所以整体都是符合预期的
评论