本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

基于 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 Service。

为什么 Kubernetes 要使用 IPVS?

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

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

尽管 Kubernetes 在 v1.6 版本中已经支持 5000 个节点,但使用 iptables 的 kube-proxy 实际上是集群扩展到 5000 个节点的瓶颈。一个例子是,在 5000 个节点的集群中,如果 PVS 是专为负载平衡而设计的,并且使用更高效的数据结构(哈希表),从而实现几乎无限的底层扩展。

另一方面,使用基于 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:永不排队

将来,我们可以实现特定于服务的调度器(可能通过 annotation),它具有更高的优先级并会覆盖该值。

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

参数:--ipvs-sync-period IPVS 规则刷新频率的最大间隔(例如 '5s','1m')。必须大于 0。

参数:--ipvs-min-sync-period IPVS 规则刷新频率的最小间隔(例如 '5s','1m')。必须大于 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 Service 和 IPVS 虚拟服务器之间的关系是 1:N。例如,考虑一个具有多个 IP 地址的 Kubernetes Service。External IP 类型服务有两个 IP 地址 - ClusterIP 和 External IP。那么 IPVS 代理将创建 2 个 IPVS 虚拟服务器 - 一个用于 Cluster IP,另一个用于 External IP。Kubernetes Endpoint(每个 IP+Port 对)和 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=trueclusterCIDR 指定时进行伪装
KUBE-LOOP-BACK所有服务 IP + 端口 + IP用于解决发夹问题的伪装
KUBE-EXTERNAL-IP服务外部 IP + 端口用于伪装到外部 IP 的数据包
KUBE-LOAD-BALANCER负载均衡器入口 IP + 端口用于伪装到负载均衡器类型服务的数据包
KUBE-LOAD-BALANCER-LOCAL具有 externalTrafficPolicy=local 的负载均衡器入口 IP + 端口接受到具有 externalTrafficPolicy=local 的负载均衡器的数据包
KUBE-LOAD-BALANCER-FW具有 loadBalancerSourceRanges 的负载均衡器入口 IP + 端口丢弃指定了 loadBalancerSourceRanges 的负载均衡器类型服务的数据包
KUBE-LOAD-BALANCER-SOURCE-CIDR负载均衡器入口 IP + 端口 + 源 CIDR接受指定了 loadBalancerSourceRanges 的负载均衡器类型服务的数据包
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 最简单的方法是加入众多与其兴趣相符的特别兴趣小组 (SIGs) 之一。有什么想向 Kubernetes 社区广播的吗?在我们的每周社区会议上以及通过以下渠道分享您的声音。

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