k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡( 二 )


同理对于外部访问 service 的请求,不论是 Cluster IP+TargetPort 的方式;还是用 Node 节点 IP+NodePort 的方式,都被 Node 节点的 Iptables 规则重定向到 Kube-proxy 监听 Service 服务代理端口 。kube-proxy 接收到 Service 的访问请求后,根据负载策略,转发到后端的 Pod 。
kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、kernelspace。
userspace 模式userspace 模式在 k8s v1.2 后就已经被淘汰了,userspace 的作用就是在 proxy 的用户空间监听一个端口,所有的 svc 都转到这个端口,然后 proxy 内部应用层对其进行转发 。proxy 会为每一个 svc 随机监听一个端口,并增加一个 iptables 规则 。
从客户端到 ClusterIP:Port 的报文都会通过 iptables 规则被重定向到 Proxy Port,Kube-Proxy 收到报文后,然后分发给对应的 Pod 。

k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡

文章插图
userspace 模式下,流量的转发主要是在用户空间下完成的,上面提到了客户端的请求需要借助于 iptables 规则找到对应的 Proxy Port,因为 iptables 是在内核空间,这里就会请求就会有一次从用户态到内核态再返回到用户态的传递过程, 一定程度降低了服务性能 。所以就会认为这种方式会有一定的性能损耗 。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端 。
iptables首先来简单了解下 iptables:
iptables 是 Linux 中最常用的一种防火墙工具 , 除了防火墙它还可以用作 IP 转发和简单的负载均衡功能 。基于 Linux 中的 netfilter 内核模块实现 。Netfilter 在协议中添加了一些钩子,它允许内核模块通过这些钩子注册回调函数,这样经过钩子的所有数据都会被注册在响应钩子上的函数处理 , 包括修改数据包内容、给数据包打标记或者丢掉数据包等 。iptables 是运行在用户态的一个程序 , 通过 netlink 和内核的 netfilter 框架打交道,具有足够的灵活性来处理各种常见的数据包操作和过滤需求 。它允许将灵活的规则序列附加到内核的数据包处理管道中的各种钩子上 。
Netfilter 是 Linux 2.4.x 引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的 hook 函数的管理机制 , 使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能 。
在 kubernetes v1.2 之后 iptables 成为默认代理模式,这种模式下,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除 。对每个 Service,它会安装 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面 。因为流量转发都是在内核进行的,所以性能更高更加可靠 。
k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡

文章插图
可以看到该模式下 iptables 来做用户态的入口 , kube-proxy 只是持续监听 Service 以及 Endpoints 对象的变化,iptables 通过设置的转发策略 , 直接将对 VIP 的请求转发给后端 Pod,iptables 使用 DNAT 来完成转发,其采用了随机数实现负载均衡 。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应 , 则连接失败 。这与用户空间模式不同:在这种情况下 , kube-proxy 将检测到与第一个 Pod 的连接已失败,并会自动使用其他后端 Pod 重试 。
该模式相比 userspace 模式,克服了请求在用户态-内核态反复传递的问题 , 性能上有所提升 , 但使用 iptables NAT 来完成转发,存在不可忽视的性能损耗,iptables 模式最主要的问题是在 service 数量大的时候会产生太多的 iptables 规则,使用非增量式更新会引入一定的时延,大规模情况下有明显的性能问题 。
ipvs当集群的规模比较大时,iptables 规则刷新就会很慢,难以支撑大规模的集群 。因为 iptables 的底层实现是链表,对路由规则的增删查改都需要遍历一次链表 。
在 kubernetes v1.2 之后 ipvs 成为kube-proxy的默认代理模式 。ipvs 正是解决这一问题的 , ipvs 是 LVS 的负载均衡模块,与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数 , 但是它却使用哈希表作为底层的数据结构并且工作在内核态,也就是说 ipvs 在重定向流量和同步代理规则有着更好的性能,几乎允许无限的规模扩张 。
k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡

推荐阅读