宝格丽网站建设策划案,科技发明,短视频动漫怎么做出来的,wordpress修改后台样式在尝试将gRPC服务部署到Kubernetes集群中时#xff0c;一些用户#xff08;包括我#xff09;面临的挑战之一是实现适当的负载均衡。在深入了解如何平衡gRPC的方式之前#xff0c;我们首先需要回答一个问题#xff0c;即为什么需要平衡流量#xff0c;如果Kubernetes已经…在尝试将gRPC服务部署到Kubernetes集群中时一些用户包括我面临的挑战之一是实现适当的负载均衡。在深入了解如何平衡gRPC的方式之前我们首先需要回答一个问题即为什么需要平衡流量如果Kubernetes已经完成了这项工作。
本文关注于Kubernetes和Golang。
为什么在Kubernetes中无法适当地平衡gRPC流量
之所以难以平衡gRPC流量的主要原因是人们将gRPC视为HTTP这就是问题的根源。设计上它们是不同的虽然HTTP为每个请求创建和关闭连接但gRPC使用HTTP2协议在长时间的TCP连接上运行使得平衡更加困难因为多个请求通过同一个连接进行多路复用。然而这并不是配置gRPC服务在Kubernetes中出现平衡问题的唯一原因以下是一些常见的错误配置
错误的gRPC客户端配置错误的Kubernetes服务配置
错误的gRPC客户端配置
设置gRPC客户端时常见的情况是选择默认配置这对于1-1连接类型完全有效但对于生产环境来说并不如我们所希望的有效。这背后的原因是因为默认的gRPC客户端提供了使用简单的IP/DNS记录连接的可能性这只会创建一个与目标服务的连接。
因此需要为与多个服务器建立连接进行不同的设置将连接类型从1-1转换为1-N。
默认设置
func main(){conn, err : grpc.Dial(my-domain:50051, grpc.WithInsecure())if err ! nil {log.Fatalf(error connecting with gRPC server: %v, err)}defer conn.Close()cli : test.NewTestServiceClient(conn)rs, err : cli.DoSomething(context.Background(), ...)...
}新的设置
func main() {address : fmt.Sprintf(%s:///%s, dns, my-domain:50051)conn, err : grpc.Dial(address,grpc.WithInsecure(),grpc.WithBalancerName(roundrobin.Name))if err ! nil {log.Fatalf(did not connect: %v, err)}defer conn.Close()...
}这里有两个重要的更改需要注意
地址 最终解析的地址将类似于 dns:///my-domain:50051之所以使用这种格式是因为Dial函数允许我们使用由Scheme://Authority/Endpoint组成的目标而在我们的情况下我跳过了Authority。因此首先我添加了dns作为方案因为我希望解析一个域并持续观察其更改解析器选项有透传默认、dns和手动更多详情请参阅这里。负载均衡器选项 如果我们的客户端现在连接到多个服务器那么我们的gRPC客户端可以根据所选择的负载均衡算法平衡请求。
总结一下我们的gRPC客户端现在能够创建不同的连接前提是域名解析为多个A或AAAA记录而且不仅如此现在还能够将请求均匀地分配到不同的服务器。
现在让我们看看如何让它与Kubernetes一起工作的缺失部分。
错误的Kubernetes服务配置
在Kubernetes中创建服务非常简单我们只需要定义服务名称、端口和选择器以便服务可以动态地将Pod分组并自动平衡请求如下所示
apiVersion: v1
kind: Service
metadata:name: my-service
spec:selector:app: my-appports:- name: grpcprotocol: TCPport: 50051targetPort: 50051那么对于先前的设置问题在于默认的Kubernetes服务只创建了一个DNS记录链接到单个IP。因此当您执行类似 nslookup my-service.{namespace}.svc.cluster.local 的操作时返回的是一个单个IP这使得在常见的gRPC实现中连接图看起来像这样 例如使用默认的Kubernetes服务的连接图
绿线表示与客户端的活动连接黄色表示未活动的Pod。客户端与Kubernetes服务创建了持久连接同时服务也与其中一个Pod创建了连接但这并不意味着服务与其余的Pod没有连接。
让我们使用一个无头服务来解决这个问题
apiVersion: v1
kind: Service
metadata:name: my-service
spec:clusterIP: None **this is the key***selector:app: my-appports:- name: grpcprotocol: TCPport: 50051targetPort: 50051创建了无头服务后nslookup看起来有些不同现在它返回与之关联的记录将Pod的IP分组到服务中从而使gRPC客户端更好地了解需要连接的服务器数量。
现在您已经看到了gRPC客户端的配置您必须知道为什么Kubernetes服务返回与一组Pod关联的IP非常重要。原因是客户端可以看到所有需要建立连接的服务器。在这一点上您可能已经意识到了一个注意事项即平衡的责任现在在客户端部分而不在Kubernetes的一侧。我们现在需要从Kubernetes那里得到的主要任务是保持与服务关联的Pod列表的最新状态。 例如在具有无头Kubernetes服务的连接图中可以看到连接发生了一些变化现在我们不通过Kubernetes服务来访问Pod而是使用Kubernetes服务来检索与域名关联的Pod列表然后直接与Pod建立连接。但是不要因为直接连接到Pod而感到惊慌因为我们在客户端中设置了DNS解析器类型该解析器将持续监视与无头服务的更改并将与可用的Pod保持最新的连接。
为什么不使用服务网格
如果可以的话请使用服务网格因为在服务网格中所有这些设置都是透明的而且最重要的是它是与编程语言无关的。关键区别在于服务网格利用了Sidecar模式和控制平面来编排入站和出站流量还可以看到所有网络和流量类型HTTP、TCP等从而能够正确平衡请求。简而言之如果您不使用服务网格那么您需要直接从每个客户端连接到多个服务器或者连接到一个L7代理来帮助平衡请求。
附加信息
尽管先前的设置可以工作但我在尝试在alpine Linux映像中进行Pod轮换或扩展时重新平衡连接时遇到了问题。经过一些研究我意识到我并不是唯一遇到这种问题的人可以查看这里和这里的一些相关的GitHub问题。这就是为什么我决定创建自己的解析器的原因您可以在这里查看我创建的自定义解析器我创建的自定义解析器非常基础但现在可以正常工作gRPC客户端现在可以再次监听域名的更改我还为该库添加了一个可配置的监听器它每隔一段时间查找域名并更新提供给gRPC连接管理器的IP集合如果您想贡献欢迎加入。
另一方面因为我想深入了解所以我决定创建自己的gRPC代理我也学到了很多东西利用了gRPC的http2基础我可以创建一个代理而无需更改proto负载消息或甚至不知道proto文件的定义还使用了前面提到的自定义解析器。
最后我想说的是如果您的gRPC客户端需要与许多服务器连接我强烈建议使用代理作为平衡的机制因为将这个机制放在主应用程序中将增加复杂性和资源消耗尝试保持许多打开的连接并重新平衡它们想象一下如果最终的平衡在应用程序中您将有一个与N个服务器连接的实例1-N但是使用代理您将有一个与M个代理连接到N个服务器的实例1-M-N其中MN因为每个代理实例可以处理与不同服务器的许多连接。