借助于kubernetes优秀的弹性扩缩功能,运行其中的应用程序能够在流量突增的时候坦然应对,在流量低谷的时候无需担心成本。但于此同时,也带来了极大的挑战: 弹性扩缩导致容器IP动态变化,客户端无法直接依赖于容器IP进行访问,我们必须通过某种方式固定流量入口,将流量通过该固定入口均衡地分发到后端,在容器扩缩的过程能够随着容器启停动态更新后端地址。
在这种场景下,我们自然而然地会想到广泛使用的LoadBalancer。kubernetes中service资源其实就是一种LoadBalancer。 service可以会产生一个serviceIP,通过label selecter选定一组pod,流量会通过该serviceIp负载均衡到后端的pod。
service有很多类型: ClusterIP,NodePort,LoadBalancer。在应用于实际复杂的业务场景,以上类型各有利弊:
ClusterIP 是通过分配一个虚拟IP给每个service,通过kube-proxy实现转发,这个虚拟的IP在集群外无法被直接访问到,只适合集群内部的互相调用。
NodePort 是通过将流量转发到宿主机上,然后通过kube-proxy转发到对应的pod, 每创建一个该类型的service就会占用一个宿主机的端口用做转发。此种类型的service虽然可以实现集群外部访问, 但是无法大规模应用,因为service比较多的时候,端口容易冲突,管理起来比较麻烦。
LodaBalacner会创建一个真实的LoadBalancer, 然后将流量转发到NodePort service之上,因为此时NodePort端口对用户透明,由kubernetes自动分配并管理,所以不存在上述提到的端口冲突的问题。 但是缺点就是性能,功能和扩展性
性能不高: 需要经过nodePort的转发,LB首先将流量转发到其中某一台node上面,然后再经过kube-proxy的转发,如果pod没有在这一台机器上面,还需要再转发一次到其他的node上面,如此一来就多了一跳。
扩展性: 同时由于LoadBalancer会直接将所有的node挂载到LB之上,如果集群规模变大,到了几百几千台就会达到LB的限制,无法继续添加机器。社区虽然提供了externalTraficPolicy这种机制,只挂载pod所在的node到LB, 但是这样会导致流量转发不均衡,例如如果nodeA上面有两个pod,nodeB上面有一个pod, LB是将流量平均的转达到两个node上面, 而不是根据pod数目设置不同的权重, 参见社区。
功能: 会有源IP丢失的问题,在转发过程中需要做SNAT和NAT, 在某些业务场景下无法满足用户需求。
除了service之外,还有ingress用来实现负载均衡。 ingress本质上是一个代理,广泛用于七层协议,对于一些四层或者gRPC类型的支持不太好。同时ingress controller容器本身也会发生容器漂移等现象,也需要一个四层的负载均衡动态地转发流量到后端。
Requirement明确了上述各种类型的service的特点之后,我们需要明确我们所需要的service到底是什么样子,主要体现为: 功能,可用性,性能。
功能能够在集群外部被访问到,将流量从外部均匀地传递到集群内的多个容器。这其实就是kubernetes中LoadBalacner类型的service,对于每一个service我们使用一个真实的负载均衡器,借助于公司内部的或者公有云厂商提供的负载均衡设备即可,这些产品一般都比较成熟。
性能流量能够高效地转发到容器中,LoadBalancer作为底层基础架构,需要满足各种各样业务对网络性能的要求。流量能够高效的转发到容器内, 这点需要我们LB后端直接挂载容器,不用再经过NodePort或者iptable转发, 对于这点我们需要对底层网络有一定的要求,需要LB能够连接到podIP上,需要VPC直连的容器网络方案,而overlay方式的容器网络在容器集群外是无法直接访问的,此处就无法使用。不过一般情况下,真正在生产环境中被广泛使用的也就是VPC直连的容器网络方案,各个云厂商也有提供相应的解决方案。
VPC直连的网络方案现在被广泛采用,不光是为了解决LB连接的问题, 还具有其他优势:
首先业务需要podIP可以被直接访问到,便于架构上云时进行迁移,有些时候, 部分业务在容器里, 部分还在物理机上,他们需要能够互通。
性能需求,VPC直连的没有overlay封包解包的性能损耗
方便诊断,维护起来更加简单,可以直接看做是物理机使用
目前各个云厂商都有相关的CNI插件, 有利于多云架构的实现
对于LB直接连接容器, 其实在之前的架构下也是这么做的,已经证明了可行性。 只是老的架构是通过在富容器中启动一个agent,由agent注册自身容器IP到LB。老架构由于设计的较早,当时容器还是被当作虚拟机使用,当时还没有kubernetes,没有controller模式, 随着慢慢发展暴露出很多问题:
权限管理难以实现, 分散在各个容器之中