KubeProxy:IPVS 模式
文章目录
序言
继上一篇博客KubeProxy:IPtables 模式之后,这篇博客介绍 kube-proxy 的第二种工作模式 ipvs。
在 iptables 工作模式下,iptables 中 KUBE-SEP-XXX 链上规则和 Pod 数量成正比,当集群规模增大(10000个Pod以上)时每个 k8s 节点上 iptables 规则会快速上升,从而影响集群 Service 的连接速度以及 CPU 资源消耗。
为了应对大集群中 iptables 模式的性能问题,kubernetes v1.8 中引入了 ipvs 模式。ipvs 模式在 v1.9 中处于 beta 阶段,在 v1.11 中已经正式可用了。
ipvs 和 iptables 都是基于 netfilter,但 ipvs 具有以下优势:
- ipvs 连接复杂度为 O(1),而 IPtables 为 O(n)
- ipvs 支持比 iptables 更复杂的复制均衡算法(最小负载、最少连接、加权等等)
- ipvs 支持服务器健康检查和连接重试等功能
ipvs 模式中参与数据转发的内核工具包括:
- ipvs:将数据包 DNAT
- iptables:将数据包 DROP、masquared(ipvs模式中,iptables 规则数量是恒定的)
- ipset:iptables 的辅助工具
IPVS
ipvs 两个部分组成,分别是用户空间的管理工具 ipvsadm 以及运行在内核中 ipvs 模块。
如果抛开细节 kube-proxy 在 ipvs 模式下的工作实际上非常简单,下面我们模仿 kube-proxy 在一个节点上创建一个 VIP。 通过这个这个VIP,我们可以负载均衡一个kubernetes 集群的 kube-apiserver。
主机A的地址为 172.24.37.57,vip 为 10.103.97.2:6789,kube-apiserver 地址包括以下三个:
- 172.28.126.39:6443
- 172.28.126.40:6443
- 172.28.126.41:6443
在主机A上通过 ipvsadm 创建 ipvs 服务,并关联 Real Server(RS):
|
|
通过上述命令,即可实现主机A上对三个kube-apisever的负载均衡访问。
kube-proxy 通过 kube-apiserver 获取集群中所有的 Service 和 Endpoint 信息,在每个节点上创建 ipvs service/real server。
ipvs 包含 tunnel(ipip)、direct、nat 三中工作模式,kube-proxy 基于 nat 模式工作。参考:https://blog.csdn.net/qq_15437667/article/details/50644594
HOOK 位置
了解了 kube-proxy 的工作原理,我们可以稍稍挖掘一些细节。
ipvs 基于 netfilter 框架工作,工作原理同样基于在 TCP/IP 协议栈中注入钩子函数,那么 ipvs 的 hook 函数在 TCP/IP 协议栈的那些位置?
为了探明真相我在网上查了非常多的资料,但是并没有发现找到非常满意的答案。
绝大多数文章中 ipvs 在 NF_INET_FORWARD、NF_INET_FORWARD、NF_INET_POST_ROUTING 中添加了 hook 函数,如下图:
参考-1:https://github.com/liexusong/linux-source-code-analyze/blob/master/lvs-principle-and-source-analysis-part2.md
参考-2:https://blog.csdn.net/raintungli/article/details/39051435
上述文章都只分析了接收数据包的场景,并没有分析发送数据包的场景(即在 ipvs 网关机器上访问 vip)。
结合访问 service 的几种情形,我们可以梳理出以下场景:
id | Src | Dst | 经过的链路 |
---|---|---|---|
1 | k8s node | ClusterIP/NodePort | OUTPUT -> POSTROUTING ??? |
2 | 集群外 node | NodePort | PREROUTING -> INPUT -> ipvs nat -> POSTROUTING |
3 | Pod | NodePort/ClusterIP | PREROUTING -> INPUT -> ipvs nat -> POSTROUTING |
kube-proxy 在 node 上创建了一张 dummy 网卡(kube-ipvs0),使所有访问 ClusterIP 数据包能够在 INPUT Chain 上被 DNAT,上述场景中 2、3 正式这种情况。
场景1中数据包从本机 OUTPUT 发出并没有经过 INPUT 发生 DNAT,但实际情况是在 k8s node 主机上直接通过 ClusterIP/本机IP:NodePort,依然是可以正常访问到Pod和实际不符。
查阅了[ Linux Master 分支上的最新内核代码](https://github.com/torvalds/linux/blob/master/net/netfilter/ipvs/ip_vs_core.c)最新的代码,发现在 2010 年 ipvs 已经移除了 NF_INET_POSTROUTING HOOK 点,并且在 NF_INET_LOCAL_OUT 添加了新 HOOK 用于支持 IPVS 的 LocalNode 功能。
因此上述场景1中,我的疑问或许可以通过这个改动得以解释,即OUTPUT 链上或许 ipvs 同样具有 dnat 能力!
提交记录:https://github.com/torvalds/linux/commit/cf356d69db0afef692cd640917bc70f708c27f14,https://github.com/torvalds/linux/commit/cb59155f21d4c0507d2034c2953f6a3f7806913d
IPtables
在 ipvs 模式下,kube-proxy 依然使用 iptables 进行 SNAT/MASQUERADE,并且借助 ipset 工具通过 hash 的方式快速识别访问 Service。在这种情况下,kube-proxy 在 iptables 中注入的规则数量是固定不变的包括以下:
数据包入口:OUTPUT/PREROUTING,流量被导入到自定义链 KUBE-SERVICES。
|
|
自定义链 KUBE-SERVICES 中数据包被进行分类,其中第一条规则和第三条规则中 match-set KUBE-CLUSTER-IP dst,dst 的含义就是数据包 dst 包含在 ipset 的 KUBE-CLUSTER-IP 字典中时命中改规则,此时流量在 KUBE-MARK-MASQ 链中被打上 MASQUERADE 标记,然后直接进入 INPUT/OUTPUT chain 中的 ipvs 进行 DNAT。
其余访问本地网卡的流量,则进入 KUBE-NODE-PORT 链。
|
|
KUBE-NODE-PORT 链主要用于处理 externalTrafficPolicy 逻辑,其中 KUBE-NODE-PORT-LOCAL-TCP 字典记录 Local 模式的 NodePort, 而 KUBE-NODE-PORT-TCP 字典记录了全量 NoderPort。可以看到 Local 模式的数据包没有经过 KUBE-MARK-MASQ 即没有标记 MASQUERADE 。
访问 NodePort 流量会经过 OUTPUT/PREROUTING 上的其他规则,最后进入 ipvs 进行 DNAT。
PS:iptables 模式中由于 DNAT 在 iptables 上完成后直接进入 next chain,因此访问 NodePort 的数据包无法走完整的 PREROUTING、OUTPUT chain 导致 firewalld 防火墙规则失效,而在 ipvs 模式下并不存在这种问题。
|
|
经过 ipvs 转发的数据包,最后由 POSTROUTING 进入 MASQUERADE 。
A 访问 B 时,如果数据包(SRC:A,DST:B)在 B 上发生了 DNAT(SRC:A,DST:C),则 C 发出的应答包为(SRC:C,DST:A),由于 C 可能和 A 不是同一个子网因此包可能无法送达。
上述场景中需要进行 SNAT,整个流程变为 (SRC:A,DST:B)-> B(DNAT and SNAT) -> (SRC:B,DST:C) -> C应答 ->(SRC:C,DST:B)-> B ->(SRC:B,DST:A) -> A
|
|
将上述流程绘制成图如下:
参考文档
文章作者 yoaz
上次更新 2022-03-10
许可协议 MIT