写点什么

Grafana 可观测性 grpc 长链接处理

作者:盈米基金
  • 2023-08-24
    广东
  • 本文字数:4061 字

    阅读完需:约 13 分钟

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 增加一些参数重启服务即可

protocols:  grpc:    endpoint: 0.0.0.0:14250    keepalive:      server_parameters:        max_connection_idle: 60s        max_connection_age: 300s        max_connection_age_grace: 30s        time: 10s        timeout: 10s
复制代码
  • 具体的参数说明请参考文档:

  • 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:  log_received_traces: true # 开启日志后可以在日志中输出traceid,方便debug  #log_received_spans:  #  enabled: true  #  include_all_attributes: true  receivers:    jaeger:      protocols:        thrift_http:        grpc:          endpoint: '0.0.0.0:14251'    otlp:      protocols:        grpc:          endpoint: '0.0.0.0:4317' server:  http_listen_port: 3200  grpc_server_max_connection_age_grace: 10s  grpc_server_max_connection_age: 10s  grpc_server_max_connection_idle: 10s  grpc_server_keepalive_time: 10s  grpc_server_keepalive_timeout: 10s
复制代码
  • 通过查询 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 的形式来强制转换的。


// Config defines configuration for OTLP receiver.type Config struct {    // Protocols is the configuration for the supported protocols, currently gRPC and HTTP (Proto and JSON).    Protocols `mapstructure:"protocols"`}
// Protocols is the configuration for the supported protocols.type Protocols struct { GRPC *configgrpc.GRPCServerSettings `mapstructure:"grpc"` HTTP *confighttp.HTTPServerSettings `mapstructure:"http"`} // GRPCServerSettings defines common settings for a gRPC server configuration.type GRPCServerSettings struct { // Server net.Addr config. For transport only "tcp" and "unix" are valid options. NetAddr confignet.NetAddr `mapstructure:",squash"` // Configures the protocol to use TLS. // The default value is nil, which will cause the protocol to not use TLS. TLSSetting *configtls.TLSServerSetting `mapstructure:"tls"` // MaxRecvMsgSizeMiB sets the maximum size (in MiB) of messages accepted by the server. MaxRecvMsgSizeMiB uint64 `mapstructure:"max_recv_msg_size_mib"` // MaxConcurrentStreams sets the limit on the number of concurrent streams to each ServerTransport. // It has effect only for streaming RPCs. MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"` // ReadBufferSize for gRPC server. See grpc.ReadBufferSize. // (https://godoc.org/google.golang.org/grpc#ReadBufferSize). ReadBufferSize int `mapstructure:"read_buffer_size"` // WriteBufferSize for gRPC server. See grpc.WriteBufferSize. // (https://godoc.org/google.golang.org/grpc#WriteBufferSize). WriteBufferSize int `mapstructure:"write_buffer_size"` // Keepalive anchor for all the settings related to keepalive. Keepalive *KeepaliveServerConfig `mapstructure:"keepalive"` // Auth for this receiver Auth *configauth.Authentication `mapstructure:"auth"` // Include propagates the incoming connection's metadata to downstream consumers. // Experimental: *NOTE* this option is subject to change or removal in the future. IncludeMetadata bool `mapstructure:"include_metadata"`} // KeepaliveServerConfig is the configuration for keepalive.type KeepaliveServerConfig struct { ServerParameters *KeepaliveServerParameters `mapstructure:"server_parameters"` EnforcementPolicy *KeepaliveEnforcementPolicy `mapstructure:"enforcement_policy"`} // KeepaliveServerParameters allow configuration of the keepalive.ServerParameters.// The same default values as keepalive.ServerParameters are applicable and get applied by the server.// See https://godoc.org/google.golang.org/grpc/keepalive#ServerParameters for details.type KeepaliveServerParameters struct { MaxConnectionIdle time.Duration `mapstructure:"max_connection_idle"` MaxConnectionAge time.Duration `mapstructure:"max_connection_age"` MaxConnectionAgeGrace time.Duration `mapstructure:"max_connection_age_grace"` Time time.Duration `mapstructure:"time"` Timeout time.Duration `mapstructure:"timeout"`}
复制代码



至此,我们已经找到了 Distributor 配置 keepalive 参数的方法,剩下的就是实验,通过对原来的 receiver 添加 keepalive 等参数然后重新部署


  • 从结果中可以看到我新扩容一个节点之后,直接查看新创建的 POD 的日志,可以从里面看到已经有新的 traceid 显示了说明 Grpc server 配置的 keepalive 参数已经生效了,现在可以实现负载了。

  • 继续看 Grafana Agent 如何配置,惊喜的发现他们的配置模式是类似的,都是复用了 otlpreceiver.Config, 所以直接也添加类似的配置

traces:  configs:  - name: default    receivers:      jaeger:        protocols:          grpc:            endpoint: 0.0.0.0:14250            keepalive:              server_parameters:                max_connection_idle: 60s                max_connection_age: 300s                max_connection_age_grace: 30s                time: 10s                timeout: 10s           thrift_http:      otlp:        protocols:          grpc:            keepalive:              server_parameters:                max_connection_idle: 60s                max_connection_age: 300s                max_connection_age_grace: 30s                time: 10s                timeout: 10s           http:    remote_write:      - endpoint: distributor.infra:4317        insecure: true  # only add this if TLS is not required    batch:      timeout: 5s      send_batch_size: 100
复制代码
  • 因为 Grafana Agent 没有详细的日志输出,所以只能通过 tcpdump 方案确认是否生效​




  • 可以看到在 11:26:51 的时候有一个容器向 Grafana Agent 14250 发起了 TCP 握手 SYN,建立成功后就开始 PUSH 数据了在

  • 11:27:02 的时候,可以看到 Grafana agent 的 14250 端口发起了 Finish TCP 结束包请求,完成 TCP 终止同

  • 时查看另外一个容器是否有 grpc 请求,可以看到,另外一个 POD 也有类似的现象

  • 所以整体都是符合预期的


用户头像

盈米基金

关注

还未添加个人签名 2018-12-16 加入

还未添加个人简介

评论

发布
暂无评论
Grafana可观测性grpc长链接处理_gRPC_盈米基金_InfoQ写作社区