跳转至

09 南北向流量组件 IPVS 的落地实践

我们知道 Kubernetes 工作节点的流量管理都是由 kube-proxy 来管理的。kube-proxy 利用了 iptables 的网络流量转换能力,在整个集群的数据层面创建了一层集群虚拟网络,也就是大家在 Service 对象中会看到的术语 ClusterIP,即集群网络 IP。既然 iptables 已经很完美的支持流量的负载均衡,并能实现南北向流量的反向代理功能,为什么我们还要让用户使用另外一个系统组件 IPVS 来代替它呢?

主要原因还是 iptables 能承载的 Service 对象规模有限,超过 1000 个以上就开始出现性能瓶颈了。目前 Kubernetes 默认推荐代理就是 IPVS 模式,这个推荐方案迫使我们需要开始了解 IPVS 的机制,熟悉它的应用范围和对比 iptables 的优缺点,让我们能有更多的精力放在应用开发上。

一次大规模的 Service 性能评测引入的 IPVS

iptables 一直是 Kubernetes 集群依赖的系统组件,它同时也是 Linux 内核中的网络过滤机制。日常实践中,我们通常不会明显感知到它的性能问题。KubeCon 2018 上,社区开发者提出了一个问题:

在超大规模如 10000 个 Service 的场景下,kube-proxy 的南北向流量转发性能还能保持高效吗?

通过测试数据发现,答案是否定的。在 Pod 实例规模达到上万个实例的时候,iptables 就开始对系统性能产生影响了。我们需要知道哪些原因导致 iptables 不能稳定工作。

首先,IPVS 模式和 iptables 模式都基于 Netfilter。生成负载均衡规则时,IPVS 通过哈希表转发流量,而 iptables 需要从上到下逐条匹配规则。随着规则规模扩大,iptables 的 CPU 消耗会增加,转发效率也会下降;IPVS 的哈希查表范围更小,因此在大规模 Service 场景下通常具有更好的转发性能。

其次,这里我们要清楚的是,iptables 毕竟是为防火墙模型配置的工具,和专业的负载均衡组件 IPVS 的实现目标不同,这里并不能说 IPVS 就比 iptables 优秀,因为 IPVS 模式启用之后,仅仅只是取代南北向流量的转发,东西向流量的 NAT 转换仍然需要 iptables 来支撑。为了让大家对它们性能对比的影响有一个比较充分的理解,可以看下图:

ipvs-iptables-compare

从图中可以看到,当 Service 对象实例超过 1000 的时候,iptables 和 IPVS 对 CPU 的影响才会产生差异,规模越大影响也越明显。很明显这是因为它们的转发规则的查询方式不同导致了性能的差异。

除了规则匹配的检索优势,IPVS 相比 iptables 还提供了更灵活的负载均衡算法:

调度器 含义
rr 轮询调度
lc 最小连接数
dh 目标地址散列
sh 源地址散列
sed 最短期望延迟
nq 最少队列

而在 iptables 中我们默认只有轮询调度特性。

IPVS(IP Virtual Server)是构建在 Linux Netfilter 模块之上的内核级负载均衡组件,负责南北向流量的四层转发。它将 Service 的虚拟 IP 绑定到后端 Pod IP 集合,实现流量分发,因此符合 Kubernetes 对 Service 的实现模型。

IPVS 使用简介

安装 ipvsadm,这是 LVS 的管理工具:

sudo apt-get install -y ipvsadm

创建虚拟服务:

sudo ipvsadm -A -t 100.100.100.100:80 -s rr

使用容器创建 2 个实例:

$ docker run -d -p 8000:8000 --name first -t jwilder/whoami
cd977829ae0c76236a1506c497d5ce1628f1f701f8ed074916b21fc286f3d0d1
$ docker run -d -p 8001:8000 --name second -t jwilder/whoami
5886b1ed7bd4095cb02b32d1642866095e6f4ce1750276bd9fc07e91e2fbc668

查出容器地址:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' first
172.17.0.2
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' second
172.17.0.3
$ curl 172.17.0.2:8000
I'm cd977829ae0c

配置 IP 组绑定到虚拟服务 IP 上:

$ sudo ipvsadm -a -t 100.100.100.100:80 -r 172.17.0.2:8000 -m
$ sudo ipvsadm -a -t 100.100.100.100:80 -r 172.17.0.3:8000 -m
$ ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  100.100.100.100:http rr
  -> 172.17.0.2:8000              Masq    1      0          0
  -> 172.17.0.3:8000              Masq    1      0          0

到此,IPVS 的负载配置步骤就给大家演示完了,kube-proxy 也是如此操作 IPVS 的。

kube-proxy 的 IPVS 模式配置参数

参数 作用
--proxy-mode=ipvs 启用 IPVS NAT 转发模式,实现 Service 流量负载均衡。
--ipvs-scheduler 设置负载均衡策略,默认是 rr(轮询)。
--cleanup-ipvs 启动 IPVS 前清理历史遗留规则。
--ipvs-sync-period--ipvs-min-sync-period 设置规则同步周期,周期必须大于 0。
--ipvs-exclude-cidrs 过滤自定义 IPVS 规则,保留已有的负载均衡流量。

IPVS 模式下的例外情况

IPVS 主要负责负载均衡,在包过滤、端口回流和 SNAT 等场景下仍需要依靠 iptables。此外,以下情况可能使 IPVS 模式回退到 iptables 模式:

  • kube-proxy 启用 --masquerade-all=true
  • kube-proxy 启动时定义了集群网络;
  • 使用 LoadBalancer 类型的 Service;
  • 使用 NodePort 类型的 Service。

为了减少 iptables 规则数量,kube-proxy 还会使用 ipset。下面是 IPVS 模式下维护的常见 ipset 规则:

set name members usage
KUBE-CLUSTER-IP All Service IP + port masquerade-all=true 或配置 clusterCIDR 的流量执行 masquerade。
KUBE-LOOP-BACK All Service IP + port + IP 解决 hairpin 流量问题。
KUBE-EXTERNAL-IP Service External IP + port 对发往外部 IP 的数据包执行 masquerade。
KUBE-LOAD-BALANCER Load Balancer ingress IP + port 对发往 LoadBalancer 类型 Service 的数据包执行 masquerade。
KUBE-LOAD-BALANCER-LOCAL Load Balancer ingress IP + port,且 externalTrafficPolicy=local 接受符合 externalTrafficPolicy=local 的 LoadBalancer 流量。
KUBE-LOAD-BALANCER-FW Load Balancer ingress IP + port,且配置 loadBalancerSourceRanges 丢弃不符合来源范围的 LoadBalancer 流量。
KUBE-LOAD-BALANCER-SOURCE-CIDR Load Balancer ingress IP + port + source CIDR 接受符合 loadBalancerSourceRanges 的 LoadBalancer 流量。
KUBE-NODE-PORT-TCP NodePort 类型 Service 的 TCP 端口 对 NodePort TCP 流量执行 masquerade。
KUBE-NODE-PORT-LOCAL-TCP NodePort TCP 端口,且 externalTrafficPolicy=local 接受符合 externalTrafficPolicy=local 的 NodePort TCP 流量。
KUBE-NODE-PORT-UDP NodePort 类型 Service 的 UDP 端口 对 NodePort UDP 流量执行 masquerade。
KUBE-NODE-PORT-LOCAL-UDP NodePort UDP 端口,且 externalTrafficPolicy=local 接受符合 externalTrafficPolicy=local 的 NodePort UDP 流量。

使用 IPVS 的注意事项

Kubernetes 使用 IPVS 模式可以覆盖大部分流量负载场景,但长连接 TCP 请求的水平扩展仍需要额外处理。IPVS 本身无法限制 keepalive_requests,可以临时将长连接转换为短连接:服务端在处理一定数量的请求(例如 1000 个)后,通过 HTTP Header 返回 Connection: close,通知客户端关闭当前连接并重新建立连接。

长期方案是在集群前置部署 Nginx 或 HAProxy 集群,由代理层限制长连接请求数量,从而实现流量的弹性扩容。

实践总结

IPVS 是面向大规模 Service 场景的性能优化方案,通过内核中的 IPVS 模块替代 iptables 的主要负载均衡逻辑。需要注意的是,IPVS 的行为和性能会受到 Linux 内核版本影响,部署前应验证内核、kube-proxy 和网络插件的兼容性。

与此同时,Linux 社区也在推进 eBPF 技术,Cilium 等容器网络方案已经基于 eBPF 实现流量转发和网络互连:

8-2-cilium-diagram

基于 eBPF 的方案正在逐步扩展其能力,未来可能进一步减少对 iptables 和 IPVS 的依赖。