这篇文章已经超过一年了。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
基于 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-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 最简单的方法是加入与您的兴趣相符的众多特别兴趣小组 (SIG) 之一。您想向 Kubernetes 社区广播某些内容吗?在我们的每周社区会议上以及通过以下渠道分享您的声音。
感谢您持续的反馈和支持。在 Stack Overflow 上发布问题(或回答问题)加入 K8sPort 上的倡导者社区门户 在 Twitter 上关注我们 @Kubernetesio 以获取最新更新 在 Slack 上与社区聊天 分享您的 Kubernetes 故事