本文已发布一年以上。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否已发生变化或不再准确。
基于 IPVS 的集群内负载均衡深度解析
编者注:本文是关于 Kubernetes 1.11 新特性系列深度文章的一部分
引言
根据 Kubernetes 1.11 版本发布博客文章 ,我们宣布基于 IPVS 的集群内服务负载均衡已晋升为正式可用 (General Availability)。在本篇博客中,我们将带您深入了解此功能。
什么是 IPVS?
IPVS (IP 虚拟服务器) 构建于 Netfilter 之上,作为 Linux 内核的一部分实现了传输层负载均衡。
IPVS 被集成到 LVS (Linux 虚拟服务器) 中,在主机上运行并充当真实服务器集群前方的负载均衡器。IPVS 可以将基于 TCP 和 UDP 的服务请求导向真实服务器,并使真实服务器的服务显示为单个 IP 地址上的虚拟服务。因此,IPVS 自然支持 Kubernetes Service。
为什么在 Kubernetes 中使用 IPVS?
随着 Kubernetes 的使用越来越广泛,其资源的可扩展性变得越来越重要。特别是,服务的可扩展性对于开发者/公司采用 Kubernetes 运行大型工作负载至关重要。
Kube-proxy 作为服务路由的基础构建模块,一直依赖于经过实战考验的 iptables 来实现核心支持的服务类型,例如 ClusterIP 和 NodePort。然而,由于 iptables 纯粹是为防火墙目的而设计的,并且基于内核规则列表,因此在扩展到数万个 Service 时会遇到困难。
尽管 Kubernetes 在 v1.6 版本中已经支持 5000 个节点,但使用 iptables 的 kube-proxy 实际上是集群扩展到 5000 个节点的瓶颈。一个例子是,在 5000 节点集群中使用 NodePort Service 时,如果我们有 2000 个 Service,每个 Service 有 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
。如果未配置,则默认值为 round-robin (rr) 轮询算法。
- rr: 轮询 (round-robin)
- lc: 最少连接 (least connection)
- dh: 目标哈希 (destination hashing)
- sh: 源哈希 (source hashing)
- sed: 最短预期延迟 (shortest expected delay)
- nq: 永不排队 (never queue)
将来,我们可以实现 Service 特定调度器(可能通过 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 proxier 在清理 IPVS 规则时应排除这些 CIDR,因为 IPVS proxier 无法区分 kube-proxy 创建的 IPVS 规则和用户原始的 IPVS 规则。如果您在环境中使用 IPVS proxier 并有自己的 IPVS 规则,应指定此参数,否则您的原始规则将被清除。
设计考量
IPVS Service 网络拓扑
创建 ClusterIP 类型 Service 时,IPVS proxier 将执行以下三项操作:
- 确保节点中存在一个 dummy 接口,默认为 kube-ipvs0
- 将 Service IP 地址绑定到 dummy 接口
- 分别为每个 Service 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 类型 Service 有两个 IP 地址 - ClusterIP 和 External IP。那么 IPVS proxier 将创建 2 个 IPVS 虚拟服务器 - 一个用于 Cluster IP,另一个用于 External IP。Kubernetes Endpoint(每个 IP+Port 对)与 IPVS 虚拟服务器之间的关系是 1:1
。
删除 Kubernetes service 将触发删除相应的 IPVS 虚拟服务器、IPVS 真实服务器及其绑定到 dummy 接口的 IP 地址。
端口映射
IPVS 有三种代理模式:NAT (masq)、IPIP 和 DR。只有 NAT 模式支持端口映射。Kube-proxy 利用 NAT 模式进行端口映射。以下示例显示 IPVS 将 Service 端口 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 会话亲和性(持久连接)。当 Service 指定会话亲和性时,IPVS proxier 将在 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 Proxier 中的 Iptables 和 Ipset
IPVS 用于负载均衡,无法处理 kube-proxy 中的其他变通方法,例如包过滤、hairpin-masquerade 技巧、SNAT 等。
IPVS proxier 在上述场景中利用 iptables。具体来说,ipvs proxier 在以下 4 种场景中将回退到 iptables:
- kube-proxy 启动时带有 --masquerade-all=true
- 在 kube-proxy 启动时指定 cluster CIDR
- 支持 Loadbalancer 类型 service
- 支持 NodePort 类型 service
然而,我们不希望创建过多的 iptables 规则。因此,我们采用 ipset 来减少 iptables 规则。以下是 IPVS proxier 维护的 ipset 集合表格:
集合名称 | 成员 | 用途 |
---|---|---|
KUBE-CLUSTER-IP | 所有 Service IP + 端口 | 为 masquerade-all=true 或指定了 clusterCIDR 的情况进行伪装 |
KUBE-LOOP-BACK | 所有 Service IP + 端口 + IP | 为解决 hairpin 问题进行伪装 |
KUBE-EXTERNAL-IP | Service External IP + 端口 | 为发送到外部 IP 的数据包进行伪装 |
KUBE-LOAD-BALANCER | Load Balancer ingress IP + 端口 | 为发送到 Load Balancer 类型 service 的数据包进行伪装 |
KUBE-LOAD-BALANCER-LOCAL | 带有 externalTrafficPolicy=local 的 Load Balancer ingress IP + 端口 | 接受发送到带有 externalTrafficPolicy=local 的 Load Balancer 的数据包 |
KUBE-LOAD-BALANCER-FW | 带有 loadBalancerSourceRanges 的 Load Balancer ingress IP + 端口 | 丢弃发送到指定了 loadBalancerSourceRanges 的 Load Balancer 类型 Service 的数据包 |
KUBE-LOAD-BALANCER-SOURCE-CIDR | Load Balancer ingress IP + 端口 + 源 CIDR | 接受发送到指定了 loadBalancerSourceRanges 的 Load Balancer 类型 Service 的数据包 |
KUBE-NODE-PORT-TCP | NodePort 类型 Service TCP 端口 | 为发送到 NodePort(TCP) 的数据包进行伪装 |
KUBE-NODE-PORT-LOCAL-TCP | 带有 externalTrafficPolicy=local 的 NodePort 类型 Service TCP 端口 | 接受发送到带有 externalTrafficPolicy=local 的 NodePort Service 的数据包 |
KUBE-NODE-PORT-UDP | NodePort 类型 Service UDP 端口 | 为发送到 NodePort(UDP) 的数据包进行伪装 |
KUBE-NODE-PORT-LOCAL-UDP | 带有 externalTrafficPolicy=local 的 NodePort 类型 service UDP 端口 | 接受发送到带有 externalTrafficPolicy=local 的 NodePort Service 的数据包 |
总的来说,对于 IPVS proxier,iptables 规则的数量是静态的,无论我们有多少 Service/Pod。
在 IPVS 模式下运行 kube-proxy
目前,local-up scripts、GCE scripts 和 kubeadm 支持通过导出环境变量(KUBE_PROXY_MODE=ipvs
)或指定 flag(--proxy-mode=ipvs
)来切换 IPVS 代理模式。在运行 IPVS proxier 之前,请确保所需的 IPVS 内核模块已安装。
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4
最后,对于 Kubernetes v1.10,feature gate SupportIPVSProxyMode
默认设置为 true
。对于 Kubernetes v1.11,该 feature gate 已完全移除。但是,对于 v1.10 之前的 Kubernetes 版本,您需要显式启用 --feature-gates=SupportIPVSProxyMode=true
。
参与其中
参与 Kubernetes 的最简单方式是加入众多符合您兴趣的特别兴趣小组 (SIG)。有什么想向 Kubernetes 社区广播的吗?在我们的每周社区会议上,并通过以下渠道分享您的声音。
感谢您持续的反馈和支持。在Stack Overflow上提问(或回答问题)。在K8sPort加入贡献者社区门户。在 Twitter @Kubernetesio 上关注我们获取最新动态。在Slack上与社区聊天。分享您的 Kubernetes 故事。