上期我们提到了,在Kubernetes集群中,有两类不同的负载均衡器:
- Service,实质上是一个四层负载均衡,将服务名称解析为IP地址并且转发到具体的Pod上,让Pod执行操作;
- Ingress,实质上是对外的七层负载均衡,对外接收带有域名、URL的请求,对内将七层http请求发送到具体的Pod上;
这二者是如何实现的呢?
我们知道,Kubernetes是为微服务化的应用提供的运行平台,也就是说,Kubernetes内部会有大量的微服务,应用之间各个模块的调用都需要通过service名称来进行。因此,使用传统的负载均衡器,或在1-2台服务器上部署单机/主备方式工作的软件负载均衡,会遇到性能扩展的瓶颈。
在分布式基础架构设计中,我们要遵循一条原则:对于东西向的流量处理,一定要使用分布式的处理方式!(忽悠客户使用硬件防火墙或硬件负载均衡器处理云内东西流量的错误设计,应当受到强烈谴责和抵制)
在Kubernetes中,给出的版本答案是:把东西向负载均衡器分布式地运行在每个工作节点上。Kubernetes在每个工作节点上提供了kube-proxy机制,其具体实现可以使用iptables或ipvs。iptables是Linux在1998年就引入的一个网络过滤机制,但iptables规则多了之后性能下降,这是因为iptables规则是基于链表实现,查找复杂度为O(n),当规模非常大时,查找和处理的开销就特别大。当节点到达5000个时,假设有2000个NodePort Service,每个Service有10个Pod,那么在每个Node节点中至少有20000条规则,会使得iptables成为主要的性能瓶颈。
另外,iptables主要是专门用来做主机防火墙的,而不是专长做负载均衡的。虽然通过iptables的statistic模块以及DNAT能够实现最简单的只支持概率轮询的负载均衡,但是往往我们还需要更多更灵活的算法,比如基于最少连接算法、源地址HASH算法等。ipvs虽然能部分解决这一问题,但如需要实现SNAT,依然依赖于iptables,也就是无法彻底解决iptables顺序查表带来的时间复杂度问题。
TCS的解决方案是:使用cilium,利用eBPF来解决这一问题。
如图,来自client的访问(南北向流量)到达node 172.16.112.10时,172.16.112.10的XDP层会匹配规则,当它发现访问的目的是redis对应的service,会将流量送到redis对应的Pod,而如果发现访问的目的是tomcat对应的service,会送到本机tomcat对应的pod。
对于容器平台内部的东西流量,如tomcat需要访问redis时,操作系统的sock层会在eBPF程序的操控下检测到这是对其他service的访问,并将流量送到其目的地址,而不需要经过NAT转换。
由于Cilium利用了eBPF的操纵内核网络栈中Sock及XDP模块的能力,能够实现绕过kube-proxy对数据包进行转发,也就完美地规避了kube-proxy实际调用的内核模块的限制。利用Cilium实现网络的TCS理论上可以支撑无限大的集群扩展。
在云原生的领域中,还有另一个非常重要的话题是链路追踪。由于eBPF可以在内核层面对数据包进行解析,复制,提取特定字段,利用了eBPF实现的容器平台提供链路追踪的能力,也不需要依赖istio和sidecar,而可以付出较低的性能方面的代价来实现。
可见,TCS是真正的面向云原生时代的版本答案。