作者:William Morgan (Buoyant)
译者:Anthony Han
许多gRPC的新用户会发现Kubernetes的默认负载平衡通常不能与gRPC一起使用。例如,我们来看一下将简单的gRPC Node.js微服务应用程序并部署在Kubernetes上会发生什么:
从图中可知,虽然这个voting服务有好几个pod,但是观察CPU的使用情况可知实际上只有一个pod在工作,因为只有一个pod正在接收任何流量。那么为什么会这样呢?
接下去,在本篇博文中我们会介绍为什么会这样。以及如何通过向Linkerd的任何Kubernetes应用程序添加gRPC负载平衡来轻松修复它。
为什么gRPC需要特殊的负载平衡?
首先,让我们理解为什么我们需要为gRPC做一些特别的事情。
gRPC是目前应用程序开发人员日益普遍的选择。与诸如JSON-over-HTTP之类的替代协议相比,gRPC具有一些明显的优势:大大降低了(反)序列化成本,自动类型检查,标准化API以及更小的TCP管理开销。
但是,gRPC会打破标准的连接级负载平衡,包括Kubernetes提供的部分。这是因为gRPC是基于HTTP/2构建的,HTTP/2被设计为单个长连接。所有的请求是能够被多路复用的,同一时刻的多个请求能够使用同一个连接。通常情况下,这是非常有用的,能够减少连接管理的消耗。但是,因此连接级别的负载平衡不会有效。一旦建立连接,就不会再进行平衡操作。如下所示,所有的请求会被指向同一个目的端pod。
为什么这不会影响HTTP / 1.1
这个问题在HTTP/1.1中却并没有出现,HTTP/1.1也是具有长连接。这是因为HTTP/1.1是有连接循环的特点。因此。对于HTTP/1.1应用连接的负载平衡已经足够,不需要再进行其他额外的操作。
为了搞懂为什么会这样,我们来更深入的探究下HTTP/1.1。与HTTP / 2相比,HTTP / 1.1无法复用请求。在同一时刻同个TCP连接中,只有一个HTTP能够被处理。当客户端发起类似于GET /foo的请求时,它会一直等待到服务端的返回。在发起请求-响应周期时,不能在该连接上发出其他请求。
通常情况下,请求时在并发条件下大量产生的。因此,要拥有并发HTTP / 1.1请求,我们需要建立多个HTTP / 1.1连接,并在所有这些连接中发出请求。此外,长时间的HTTP / 1.1连接通常会在一段时间后过期,并被客户端(服务端)拆除。这两个因素相结合意味着HTTP / 1.1请求通常在多个TCP连接之间循环,因此连接级别平衡起作用。
那么我们如何对gRPC进行负载均衡?
现在回到gRPC。由于我们无法进行连接基本的负载平衡,为了进行gRPC负载均衡,我们需要从连接平衡转向请求平衡。换言之,我们需要建立到每个目标的HTTP / 2连接,并在这些连接之间平衡请求,如下所示:
用网络术语来说,这意味着我们需要在L5 / L7而不是L3 / L4做出决策,即我们需要了解通过TCP连接发送的协议。
我们如何做到这一点? 有几种选择。第一种,我们的应用程序代码可以自己管理维护自己的目标负载平衡池,并且配置我们的gRPC客户端以使用此负载平衡池。这种方法给了我们最大的控制权,但在像Kubernetes这样的环境中它的实现可能非常复杂,因为这个平衡池在一直变化比如pod发生迁移时。我们的应用程序必须监控Kubernetes API来保证相关的pod数据的同步。
第二种,在Kubernetes中我们可以以headless services形式部署我们的应用。在这种情况下,Kubernetes将在服务的DNS条目中创建多个A记录。如果我们的gRPC客户端足够先进,它可以自动维护这些DNS条目的负载平衡池。但是这种方法将限制我们只能使用某些特定gRPC客户端,并且我们一般也很少只使用headless services服务。
最后,我们可以采用第三种方法:使用轻量级代理。
使用Linkerd在Kubernetes上
进行gRPC负载平衡
Linkerd是Kubernetes的CNCF托管的服务网格项目。与我们的目的最相关的是,Linkerd还可以作为service sidecar,可以应用部署于单个服务中-即使没有集群侧的相关权限。这意味着当我们将Linkerd插入到我们的服务时,它会为每个pod添加一个微小的超快代理,这些代理会监听Kubernetes API并自动执行gRPC负载平衡。我们的模型如下所示:
使用Linkerd有几个优点。首先,它适用于使用任何语言编写的服务,任何gRPC客户端和任何服务部署模型。因为Linkerd的代理是完全透明的,所以它们会自动检测HTTP / 2和HTTP / 1.x并执行L7负载平衡,并且将其他所有流量作为纯TCP流量传递。这意味着所有流量都会起作用。
其次,Linkerd的负载平衡方式是非常智能灵活的。Linkerd不仅能够监听Kubernetes API来实时更新负载平衡池当pod发生重新迁移时,而且能够根据相关请求的响应速度选择响应速度最快的pod。如果一个pod的响应速度又变慢了,基本只是暂时的,Linkerd还是会将流量从它那移除出去。这能够减少端到端尾部延迟。
最后,Linkerd的Rust-based代理非常快且小巧。他们引入导致的延迟小于1ms,每个pod需要的RSS小于10mb,这意味着对系统性能的影响可以忽略不计。
gRPC负载平衡测试
Linkerd的测试非常容易。只需按照Linkerd入门说明中的步骤操作 - 在机器上安装相关客户端,在群集上安装控制平面,然后“网格化”您的服务(在每个pod中注入了相关代理)。当在拥有Linkerd的情况下运行服务时,并且能够看到每个pod的流量比较均衡,实现了良好的gRPC负载平衡。
让我们再次看看我们的样本voting服务,这次是在安装Linkerd之后:
我们可以看到,所有pod的CPU图表都处于活动状态,表明所有pod正在占用流量,而我们无需更改一行代码。gRPC负载平衡好像是魔术一样神奇!
Linkerd还为我们提供了内置的流量级统计表,因此我们甚至不需要再通过CPU图表猜测发生了什么。这是一个Linkerd流量级统计图,显示了每个pod的成功率,请求量和延迟百分位数:
我们可以看到每个pod大约为5 RPS。我们还可以看到,虽然我们已经解决了负载平衡问题,但我们仍然需要做一些有关此服务成功率的工作(演示应用程序是故意失败的 - 作为读者的练习,看看你是否可以通过使用Linkerd仪表板来解决它!)
结束语
如果您对将gRPC负载平衡添加到Kubernetes服务的简单方法感兴趣,无论使用何种语言编写,使用的是哪种gRPC客户端,使用哪种方式部署它,都可以使用几个简单Linkerd的命令来实现gRPC负载平衡。
Linkerd还有很多其他功能,包括安全性,可靠性,调试和诊断功能,但这些都是未来博客文章的主题。
想了解更多? 我们很乐意让您加入我们快速发展的社区!Linkerd是一个CNCF项目,在GitHub上托管,在Slack,Twitter和邮件列表上拥有一个蓬勃发展的社区。
领取专属 10元无门槛券
私享最新 技术干货