文章目录- kube-proxy介绍
- kube-proxy iptables模式实现原理
- iptables 存在的问题
- kube-proxy ipvs模式实现原理
- kube-proxy 工作原理
kube-proxy介绍
我们知道容器的特点是快速创建、快速销毁,Kubernetes Pod和容器一样只具有临时的生命周期,一个Pod随时有可能被终止或者漂移,随着集群的状态变化而变化,一旦Pod变化,则该Pod提供的服务也就无法访问,如果直接访问Pod则无法实现服务的连续性和高可用性,因此显然不能使用Pod地址作为服务暴露端口。
解决这个问题的办法和传统数据中心解决无状态服务高可用的思路完全一样,通过负载均衡和VIP实现后端真实服务的自动转发、故障转移。
这个负载均衡在Kubernetes中称为Service,VIP即Service ClusterIP,因此可以认为Kubernetes的Service就是一个四层负载均衡,Kubernetes对应的还有七层负载均衡Ingress,本文仅介绍Kubernetes Service。
这个Service就是由kube-proxy实现的,ClusterIP不会因为 Pod 状态改变而变,需要注意的是VIP即ClusterIP是个假的IP,这个IP在整个集群中根本不存在,当然也就无法通过IP协议栈无法路由,底层underlay设备更无法感知这个IP的存在,因此ClusterIP只能是单主机(Host Only)作用域可见,这个IP在其他节点以及集群外均无法访问。
Kubernetes为了实现在集群所有的节点都能够访问Service,kube-proxy默认会在所有的Node节点都创建这个VIP并且实现负载,所以在部署Kubernetes后发现kube-proxy是一个DaemonSet。
而Service负载之所以能够在Node节点上实现是因为无论Kubernetes使用哪个网络模型,均需要保证满足如下三个条件:
- 容器之间要求不需要任何NAT能直接通信;
- 容器与Node之间要求不需要任何NAT能直接通信;
- 容器看到自身的IP和外面看到它的IP必须是一样的,即不存在IP转化的问题。
至少第2点是必须满足的,有了如上几个假设,Kubernetes Service才能在Node上实现,否则Node不通Pod IP也就实现不了了。
有人说既然kube-proxy是四层负载均衡,那kube-proxy应该可以使用haproxy、nginx等作为负载后端啊?
事实上确实没有问题,不过唯一需要考虑的就是性能问题,如上这些负载均衡功能都强大,但毕竟还是基于用户态转发或者反向代理实现的,性能必然不如在内核态直接转发处理好。
因此kube-proxy默认会优先选择基于内核态的负载作为后端实现机制,目前kube-proxy默认是通过iptables实现负载的,在此之前还有一种称为userspace模式,其实也是基于iptables实现,可以认为当前的iptables模式是对之前userspace模式的优化。
在当前版本的k8s中,kube-proxy默认使用的是iptables模式,通过各个node节点上的iptables规则来实现service的负载均衡,但是随着service数量的增大,iptables模式由于线性查找匹配、全量更新等特点,其性能会显著下降。从k8s的1.8版本开始,kube-proxy引入了IPVS模式,IPVS模式与iptables同样基于Netfilter,但是采用的hash表,因此当service数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能。
用两张图来概括一下上面的内容
Service, Endpoints与Pod的关系:
Kube-proxy进程获取每个Service的Endpoints,实现Service的负载均衡功能:
访问Service的请求,不论是Cluster IP TargetPort的方式;还是用Node节点IP NodePort的方式,都被Node节点的Iptables规则重定向到Kube-proxy监听Service服务代理端口。kube-proxy接收到Service的访问请求后,根据负载策略,转发到后端的Pod。
kube-proxy iptables模式实现原理
该模式利用内核iptables来实现service的代理和LB:
iptables mode因为使用iptable NAT来完成转发,也存在不可忽视的性能损耗。另外,如果集群中存在上万的Service/Endpoint,那么Node上的iptables rules将会非常庞大,性能还会再打折扣。这也导致目前大部分企业用k8s上生产时,都不会直接用kube-proxy作为服务代理,而是通过自己开发或者通过Ingress Controller来集成HAProxy, Nginx来代替kube-proxy。
iptables 模式与 userspace 相同,kube-proxy 持续监听 Service 以及 Endpoints 对象的变化;但它并不在本地节点开启反向代理服务,而是把反向代理全部交给 iptables 来实现;即 iptables 直接将对 VIP 的请求转发给后端 Pod,通过 iptables 设置转发策略。其工作流程大体如下:
由此分析: 该模式相比 userspace 模式,克服了请求在用户态-内核态反复传递的问题,性能上有所提升,但使用 iptables NAT 来完成转发,存在不可忽视的性能损耗,而且在大规模场景下,iptables 规则的条目会十分巨大,性能上还要再打折扣。
iptables 存在的问题
- iptables规则复杂零乱,真要出现什么问题,排查iptables规则必然得掉层皮。LOG TRACE 大法也不好使。
- iptables规则多了之后性能下降,这是因为iptables规则是基于链表实现,查找复杂度为O(n),当规模非常大时,查找和处理的开销就特别大。据官方说法,当节点到达5000个时,假设有2000个NodePort Service,每个Service有10个Pod,那么在每个Node节点中至少有20000条规则,内核根本支撑不住,iptables将成为最主要的性能瓶颈。
- iptables主要是专门用来做主机防火墙的,而不是专长做负载均衡的。虽然通过iptables的statistic模块以及DNAT能够实现最简单的只支持概率轮询的负载均衡,但是往往我们还需要更多更灵活的算法,比如基于最少连接算法、源地址HASH算法等。而同样基于netfilter的ipvs却是专门做负载均衡的,配置简单,基于散列查找O(1)复杂度性能好,支持数十种调度算法。因此显然ipvs比iptables更适合做kube-proxy的后端,毕竟专业的人做专业的事,物尽其美。
kube-proxy ipvs模式实现原理
Kube-proxy IPVS mode
kube-proxy ipvs 是基于 NAT 实现的,通过ipvs的NAT模式,对访问k8s service的请求进行虚IP到POD IP的转发。当创建一个 service 后,kubernetes 会在每个节点上创建一个网卡,同时帮你将 Service IP(VIP) 绑定上,此时相当于每个 Node 都是一个 ds,而其他任何 Node 上的 Pod,甚至是宿主机服务(比如 kube-apiserver 的 6443)都可能成为 rs;
与iptables、userspace 模式一样,kube-proxy 依然监听Service以及Endpoints对象的变化, 不过它并不创建反向代理, 也不创建大量的 iptables 规则, 而是通过netlink 创建ipvs规则,并使用k8s Service与Endpoints信息,对所在节点的ipvs规则进行定期同步; netlink 与 iptables 底层都是基于 netfilter 钩子,但是 netlink 由于采用了 hash table 而且直接工作在内核态,在性能上比 iptables 更优。其工作流程大体如下:
kube-proxy 工作原理
kube-proxy 监听 API server 中 service 和 endpoint 的变化情况,并通过 userspace、iptables、ipvs 或 winuserspace 等 proxier 来为服务配置负载均衡(仅支持 TCP 和 UDP)。