这篇文章已经超过一年了。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

基于 IPVS 的集群内负载均衡深入探讨

编者注:此文章是关于 Kubernetes 1.11 新特性的一系列深入文章的一部分

简介

根据Kubernetes 1.11 发布博客文章,我们宣布基于 IPVS 的集群内服务负载均衡已升级为正式版。在本博客中,我们将深入探讨该功能。

什么是 IPVS?

IPVS (IP Virtual Server) 构建在 Netfilter 之上,并作为 Linux 内核的一部分实现传输层负载均衡。

IPVS 被集成到 LVS (Linux Virtual Server) 中,它在主机上运行,并充当真实服务器集群前面的负载均衡器。IPVS 可以将 TCP 和 UDP 服务请求定向到真实服务器,并使真实服务器的服务在单个 IP 地址上显示为虚拟服务。因此,IPVS 天然支持 Kubernetes 服务。

为什么 Kubernetes 选择 IPVS?

随着 Kubernetes 使用量的增长,其资源的扩展性变得越来越重要。特别是,服务的可扩展性对于运行大型工作负载的开发人员/公司采用 Kubernetes 至关重要。

Kube-proxy 是服务路由的构建块,它依赖于久经考验的 iptables 来实现核心支持的服务类型,例如 ClusterIP 和 NodePort。但是,iptables 在扩展到数万个服务时会遇到困难,因为它纯粹是为防火墙目的而设计的,并且基于内核规则列表。

即使 Kubernetes 在 v1.6 版本中已经支持 5000 个节点,使用 iptables 的 kube-proxy 实际上是将集群扩展到 5000 个节点的瓶颈。一个例子是,在 5000 节点的集群中使用 NodePort 服务,如果我们有 2000 个服务,每个服务有 10 个 Pod,这将在每个工作节点上导致至少 20000 个 iptable 记录,这会使内核非常繁忙。

另一方面,使用基于 IPVS 的集群内服务负载均衡可以极大地帮助解决此类问题。IPVS 专门为负载均衡而设计,并使用更有效的数据结构(哈希表),从而在底层实现几乎无限的扩展。

基于 IPVS 的 Kube-proxy

参数更改

参数:--proxy-mode 除了现有的 userspace 和 iptables 模式外,IPVS 模式是通过 --proxy-mode=ipvs 配置的。它隐式使用 IPVS NAT 模式进行服务端口映射。

参数:--ipvs-scheduler

添加了一个新的 kube-proxy 参数来指定 IPVS 负载均衡算法,参数为 --ipvs-scheduler。如果未配置,则默认值为轮询 (rr)。

  • rr:轮询
  • lc:最少连接
  • dh:目标哈希
  • sh:源哈希
  • sed:最短预期延迟
  • nq:永不排队

将来,我们可以实现服务特定的调度程序(可能通过注释),该调度程序具有更高的优先级并覆盖该值。

参数:--cleanup-ipvs--cleanup-iptables 参数类似,如果为 true,则清除在 IPVS 模式下创建的 IPVS 配置和 IPTables 规则。

参数:--ipvs-sync-period IPVS 规则刷新的最大间隔(例如,“5 秒”、“1 分钟”)。必须大于 0。

参数:--ipvs-min-sync-period IPVS 规则刷新的最小间隔(例如,“5 秒”、“1 分钟”)。必须大于 0。

参数:--ipvs-exclude-cidrs 以逗号分隔的 CIDR 列表,当清理 IPVS 规则时,IPVS 代理不应触及这些 CIDR,因为 IPVS 代理无法区分 kube-proxy 创建的 IPVS 规则和用户原始的 IPVS 规则。 如果您在环境中使用带有自己的 IPVS 规则的 IPVS 代理,则应指定此参数,否则您的原始规则将被清除。

设计考虑

IPVS 服务网络拓扑

创建 ClusterIP 类型服务时,IPVS 代理将执行以下三项操作

  • 确保节点中存在虚拟接口,默认为 kube-ipvs0
  • 将服务 IP 地址绑定到虚拟接口
  • 分别为每个服务 IP 地址创建 IPVS 虚拟服务器

这是一个示例

# kubectl describe svc nginx-service
Name:			nginx-service
...
Type:			ClusterIP
IP:			    10.102.128.4
Port:			http	3080/TCP
Endpoints:		10.244.0.235:8080,10.244.1.237:8080
Session Affinity:	None

# ip addr
...
73: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 1a:ce:f5:5f:c1:4d brd ff:ff:ff:ff:ff:ff
    inet 10.102.128.4/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn     
TCP  10.102.128.4:3080 rr
  -> 10.244.0.235:8080            Masq    1      0          0         
  -> 10.244.1.237:8080            Masq    1      0          0   

请注意,Kubernetes 服务和 IPVS 虚拟服务器之间的关系是 1:N。例如,考虑一个具有多个 IP 地址的 Kubernetes 服务。外部 IP 类型服务具有两个 IP 地址 - ClusterIP 和外部 IP。然后,IPVS 代理将创建 2 个 IPVS 虚拟服务器 - 一个用于 Cluster IP,另一个用于外部 IP。Kubernetes 端点(每个 IP+端口对)和 IPVS 虚拟服务器之间的关系是 1:1

删除 Kubernetes 服务将触发删除相应的 IPVS 虚拟服务器、IPVS 真实服务器及其绑定到虚拟接口的 IP 地址。

端口映射

IPVS 中有三种代理模式:NAT (masq)、IPIP 和 DR。只有 NAT 模式支持端口映射。Kube-proxy 利用 NAT 模式进行端口映射。以下示例显示 IPVS 将服务端口 3080 映射到 Pod 端口 8080。

TCP  10.102.128.4:3080 rr
  -> 10.244.0.235:8080            Masq    1      0          0         
  -> 10.244.1.237:8080            Masq    1      0       

会话亲和性

IPVS 支持客户端 IP 会话亲和性(持久连接)。当服务指定会话亲和性时,IPVS 代理将在 IPVS 虚拟服务器中设置超时值(默认情况下为 180 分钟=10800 秒)。例如

# kubectl describe svc nginx-service
Name:			nginx-service
...
IP:			    10.102.128.4
Port:			http	3080/TCP
Session Affinity:	ClientIP

# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.102.128.4:3080 rr persistent 10800

IPVS 代理中的 Iptables 和 Ipset

IPVS 用于负载均衡,它无法处理 kube-proxy 中的其他解决方法,例如数据包过滤、发夹伪装技巧、SNAT 等。

IPVS 代理在上述场景中利用 iptables。具体来说,ipvs 代理将在以下 4 种场景中回退到 iptables

  • kube-proxy 启动时带有 --masquerade-all=true
  • 在 kube-proxy 启动中指定集群 CIDR
  • 支持 Loadbalancer 类型服务
  • 支持 NodePort 类型服务

但是,我们不想创建太多 iptables 规则。因此,我们采用 ipset 以减少 iptables 规则。以下是 IPVS 代理维护的 ipset 集的表

集合名称成员用法
KUBE-CLUSTER-IP所有服务 IP + 端口对于 masquerade-all=true 或指定了 clusterCIDR 的情况进行伪装
KUBE-LOOP-BACK所有服务 IP + 端口 + IP用于解决发夹问题的伪装
KUBE-EXTERNAL-IP服务外部 IP + 端口用于发送到外部 IP 的数据包的伪装
KUBE-LOAD-BALANCER负载均衡器入口 IP + 端口用于发送到 Load Balancer 类型服务的数据包的伪装
KUBE-LOAD-BALANCER-LOCAL带有 externalTrafficPolicy=local 的负载均衡器入口 IP + 端口接受带有 externalTrafficPolicy=local 的 Load Balancer 的数据包
KUBE-LOAD-BALANCER-FW带有 loadBalancerSourceRanges 的负载均衡器入口 IP + 端口丢弃指定了 loadBalancerSourceRanges 的 Load Balancer 类型服务的数据包
KUBE-LOAD-BALANCER-SOURCE-CIDR负载均衡器入口 IP + 端口 + 源 CIDR接受指定了 loadBalancerSourceRanges 的 Load Balancer 类型服务的数据包
KUBE-NODE-PORT-TCPNodePort 类型服务 TCP 端口用于发送到 NodePort(TCP) 的数据包的伪装
KUBE-NODE-PORT-LOCAL-TCP带有 externalTrafficPolicy=local 的 NodePort 类型服务 TCP 端口接受带有 externalTrafficPolicy=local 的 NodePort 服务的数据包
KUBE-NODE-PORT-UDPNodePort 类型服务 UDP 端口用于发送到 NodePort(UDP) 的数据包的伪装
KUBE-NODE-PORT-LOCAL-UDP带有 externalTrafficPolicy=local 的 NodePort 类型服务 UDP 端口接受带有 externalTrafficPolicy=local 的 NodePort 服务的数据包

通常,对于 IPVS 代理,iptables 规则的数量是静态的,无论我们有多少服务/Pod。

在 IPVS 模式下运行 kube-proxy

目前,local-up 脚本、GCE 脚本和 kubeadm 支持通过导出环境变量 (KUBE_PROXY_MODE=ipvs) 或指定标志 (--proxy-mode=ipvs) 来切换 IPVS 代理模式。在运行 IPVS 代理之前,请确保已安装 IPVS 所需的内核模块。

ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4

最后,对于 Kubernetes v1.10,功能门 SupportIPVSProxyMode 默认设置为 true。对于 Kubernetes v1.11,该功能门已完全删除。但是,在 v1.10 之前的 Kubernetes 中,您需要显式启用 --feature-gates=SupportIPVSProxyMode=true

参与进来

参与 Kubernetes 最简单的方法是加入与您的兴趣相符的众多特别兴趣小组 (SIG) 之一。您想向 Kubernetes 社区广播某些内容吗?在我们的每周社区会议上以及通过以下渠道分享您的声音。

感谢您持续的反馈和支持。在 Stack Overflow 上发布问题(或回答问题)加入 K8sPort 上的倡导者社区门户 在 Twitter 上关注我们 @Kubernetesio 以获取最新更新 在 Slack 上与社区聊天 分享您的 Kubernetes 故事