如何用三条配置行解决我们在Kubernetes中遇到的gRPC扩展问题
2024/11/15 2:02:58
本文主要是介绍如何用三条配置行解决我们在Kubernetes中遇到的gRPC扩展问题,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
这一切都始于我向我们资深软件工程师提出的一个问题:
“嗯,不考虑通信的速度。你觉得用gRPC来开发通信真的比用REST更好吗?”
我没想到的答案立刻来了: “那还用说。”
在我提出这个问题之前,我在一个滚动更新期间监控我们服务的一种奇怪行为,尤其是在增加pod数量时。我们大多数微服务过去通过REST调用进行通信,一直都没有遇到问题。为了减少REST带来的开销,我们已经将一些集成从REST迁移到了gRPC。最近,我们注意到一些问题,这些问题都指向同一个方向:我们的gRPC通信。当然,我们遵循了在Kubernetes中运行gRPC而不需要服务网格的建议实践,比如在这篇博客文章中描述的方法,我们在服务器端使用了无头服务,并在gRPC客户端实现了基于DNS发现的“轮询”负载均衡等。
Kubernetes内部负载均衡器不均衡RPC请求,而是均衡TCP连接。你可以在我另一篇博客文章中了解更多关于Kubernetes如何平衡TCP连接的信息。
第4层负载均衡器很常见,因为它们简单且协议无关。然而,gRPC打破了Kubernetes提供的连接级负载均衡。这是因为gRPC是基于HTTP/2的,而HTTP/2设计为保持一个长时间的TCP连接,使得所有的请求可以在任何时间点都活跃在同一连接上。这减少了连接管理的开销。然而,在这种情况下,连接级均衡并不太有用,因为一旦连接建立,就不再需要进行均衡了。所有请求都会固定在原始目的Pod上,如下所示,直到新的DNS发现发生(带有无头服务)。这不会发生,除非现有的至少一个连接断开。
问题示例:
- 2个客户端(A)与2个服务器(B)通信。
- 自动缩放器介入并增加客户端数量。
- 服务器Pod过载之后,自动缩放器介入并增加服务器Pod数量,但没有进行负载均衡。我们甚至可以看到新Pod没有收到任何流量。
- 客户端数量减少。
- 客户端数量再次增加,但负载仍然未均衡。
- 一个服务器Pod因过载而崩溃——重新发现开始。
- 虽然图片中没有显示,但当Pod恢复后,情况类似于图片3,即新Pod没有收到任何流量。
gRPC负载均衡 的例子
如我之前提到的,我们使用“客户端侧的负载均衡”,通过DNS发现使用无头服务。其他可能的选项包括使用代理负载均衡或实现另一种类似的方法,这种方法会通过Kubernetes API来查询,而不是通过DNS。
除此之外,gRPC 文档还提供了服务器端连接管理提案,我们也试了试。
以下是我对于设置服务器参数的建议,附有一个用Go代码初始化gRPC的示例:
- 将
MAX_CONNECTION_AGE
设置为 30 秒。这样足够长的时间可以实现低延迟通信,同时避免频繁且昂贵的连接建立过程。此外,它还允许服务能够相对快速地响应新 pod 的存在,使流量分配更为均衡。 - 将
MAX_CONNECTION_AGE_GRACE
设置为 10 秒。定义了连接在完成 RPC 请求前可保持活跃的最大时间。
grpc.KeepaliveParams(keepalive.ServerParameters{ MaxConnectionAge: time.Second * 30, // 这一个参数奏效了 MaxConnectionAgeGrace: time.Second * 10, // 最大连接年龄宽限期 })
它在现实生活中的表现:
应用 gRPC 配置变更前后的 pod 数量
在 gRPC 配置更改后,新 pod 中观察到的网络 I/O 活动情况
接下来是第三行
缩放问题已经搞定,但另一个问题变得更加明显。重点转向了客户端进行滚动更新(即逐个更新 Pod)时出现的 gRPC code=UNAVAILABLE 问题。有趣的是,这种情况只在滚动更新期间出现,而在单个 Pod 的缩放事件期间却没有发现。
滚动更新过程中出现的 gRPC 错误次数
滚动部署的过程很简单:创建一个新的replicaset,然后创建一个新的pod,当新pod准备好后,旧pod会被终止,依此类推。每次新pod启动之间的时间间隔为15秒。我们了解到,关于gRPC DNS重新发现,它只会在旧连接中断或收到_GOAWAY_信号时启动。因此,客户端每15秒启动一次新的重新发现,但得到的是过时的DNS记录。客户端会不断尝试重新发现,直到成功。
基本上都是 DNS 的问题……除非另有他因
DNS TTL缓存几乎在每个地方都可以找到。基础设施的DNS也有它自己的缓存。Java客户端受到默认30秒TTL缓存的影响比通常不实现DNS缓存的Go客户端要大得多。Go客户端报告的问题较少,而Java客户端报告了数百甚至数千次的问题。那么,在滚动更新期间仅影响gRPC时,为什么要做这样的改变呢?
幸运的是,有一个简单易行的解决办法:在新 pod 启动时设置 30 秒延迟。
最小就绪秒数设为30
Kubernetes部署规范允许我们设置一个新的Pod必须准备好之前的一个最短时间间隔,之后才会开始终止旧Pod。过了这段时间之后,连接会被终止,gRPC客户端会收到GOAWAY信号并开始重新寻找服务。TTL已经过期,因此客户端会获取新的、更新的记录。
gRPC 在配置方面就像一把瑞士军刀,可能默认情况下并不适合你的基础设施或应用程序。多看看文档,微调一下,多试试,充分利用现有资源。我觉得可靠且有弹性的通信应该是你最想达到的目标。
我建议你也看看:
- Keepalives。对于短寿命的内部集群连接来说,这个功能没有意义,但在其他情况下可能会很有用。
- 重试。先尝试一些退避重试,而不是直接创建新连接,以避免过载基础设施。
- 代码映射。将您的 gRPC 响应代码映射到众所周知的 HTTP 状态码,以更好地理解发生了什么。
- 负载均衡。平衡至关重要。不要忘记设置退避并进行全面测试。
- 服务器访问日志(gRPC code=OK)默认情况下设置为 info 级别,可能会过于详细。考虑将它们调整为 debug 级别并进行过滤。
这篇关于如何用三条配置行解决我们在Kubernetes中遇到的gRPC扩展问题的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15为什么我在Kubernetes集群里需要一个API网关?
- 2024-11-15亚马逊EKS的未来发展趋势
- 2024-11-15使用Kubernetes简化分布式Spring Boot应用中的定时任务管理
- 2024-11-15教你轻松几步升级Hetzner上超划算的Kubernetes集群
- 2024-11-15动手排查Kubernetes网络故障之旅
- 2024-11-15Kubernetes监控:最佳实践指南
- 2024-11-15两年使用Kubernetes运行Airflow后我们学到的经验教训
- 2024-11-14在Deckhouse管理的Kubernetes集群中运行WebAssembly应用详解
- 2024-11-14我们在EKS中避免因IP耗尽导致故障的经验分享
- 2024-11-01Argo CD在多租户环境中的安全设置与应用项目管理