本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
基于 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=true 或 clusterCIDR 指定时进行伪装 |
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-TCP | NodePort 类型服务的 TCP 端口 | 用于伪装到 NodePort(TCP) 的数据包 |
KUBE-NODE-PORT-LOCAL-TCP | 具有 externalTrafficPolicy=local 的 NodePort 类型服务的 TCP 端口 | 接受到具有 externalTrafficPolicy=local 的 NodePort 服务的数据包 |
KUBE-NODE-PORT-UDP | NodePort 类型服务的 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 故事