基于IPVS的集群内负载均衡深入解读
在Kubernetes官方博客之前的文章《Kubernetes1.11: In-Cluster Load Balancingand CoreDNS Plugin Graduate to General Availability中我们宣布了基于IPVS的集群内负载均衡已经实现了GA(General Availability),在这篇文章中我们将详细介绍该特性的实现细节。
什么是IPVS
IPVS (IP Virtual Server)是基于Netfilter的、作为linux内核的一部分实现传输层负载均衡的技术。
IPVS集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群前充当负载均衡器。IPVS可以将对TCP/UDP服务的请求转发给后端的真实服务器,因此IPVS天然支持Kubernetes Service。
为什么选择IPVS
随着kubernetes使用量的增长,其资源的可扩展性变得越来越重要。特别是对于使用kubernetes运行大型工作负载的开发人员或者公司来说,service的可扩展性至关重要。
kube-proxy是为service构建路由规则的模块,之前依赖iptables来实现主要service类型的支持,比如(ClusterIP和NodePort)。但是iptables很难支持上万级的service,因为iptables纯粹是为防火墙而设计的,并且底层数据结构是内核规则的列表。
kubernetes早在1.6版本就已经有能力支持5000多节点,这样基于iptables的kube-proxy就成为集群扩容到5000节点的瓶颈。举例来说,如果在一个5000节点的集群,我们创建2000个service,并且每个service有10个pod,那么我们就会在每个节点上有至少20000条iptables规则,这会导致内核非常繁忙。
基于IPVS的集群内负载均衡就可以完美的解决这个问题。IPVS是专门为负载均衡设计的,并且底层使用哈希表这种非常高效的数据结构,几乎可以允许无限扩容。
基于IPVS的kube-proxy
IPVS (IP Virtual Server)是基于Netfilter的、作为linux内核的一部分实现传输层负载均衡的技术。
IPVS集成在LVS(Linux Virtual Server)中,它在主机中运行,并在真实服务器集群前充当负载均衡器。IPVS可以将对TCP/UDP服务的请求转发给后端的真实服务器,因此IPVS天然支持Kubernetes Service。
1. 启动参数变更
启动参数:--proxy-mode。除了userspace模式和iptables模式外,现在用户可以通过--proxy-mode=ipvs使用IPVS模式。它默认使用IPVS的NAT模式,用于实现service端口映射。
启动参数:--ipvs-scheduler。引入新的kube-proxy参数以支持IPVS的负载均衡算法,用户可以通过--IPVS-scheduler配置,默认使用轮询模式(rr)。下面是另外支持的几种负载均衡算法:
• rr:轮询
• lc:最少连接数
• dh:目的地址哈希
• sh:源地址哈希
• sed:最短期望延时
• nq:从不排队
未来还可以实现通过service定义调度策略(可能是基于annotation),覆盖kube-proxy默认的调度策略。
启动参数:-cleanup-ipvs。与iptables的--cleanup-iptables参数类似,如果设置为true,清理IPVS相关配置,以及IPVS模式创建的iptables规则。
启动参数:--ipvs-sync-period。刷新IPVS规则的最大时间间隔,必须大于0。
启动参数:--ipvs-min-sync-period。刷新IPVS规则的最小时间间隔,必须大于0。
启动参数:--ipvs-exclude-cidrs。指定一个用逗号分隔的CIDR列表,这个列表规定了IPVS刷新时不能清理的规则。因为基于IPVS的kube-proxy不能区分自身创建的规则和系统中用户自带的规则,因此如果您要使用基于IPVS的kube-proxy并且系统中原来存在一些IPVS规则,那么应该加上这个启动参数,否则系统中原来的规则会被清理掉。
2. 设计细节
a) IPVS service网络拓扑
当我们创建ClusterIP类型的service时,IPVS模式的kube-proxy会做下面几件事:
• 确认节点中的虚拟网卡,默认是kube-ipvs0
• 绑定service IP地址到虚拟网卡
• 为每个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
需要注意的是,service与IPVS虚拟服务器应该是1:N的对应关系,因为service可能有多个IP地址(比如ExternalIP类型的service,就有clusterIP和ExternalIP两个地址)。而endpoint与IPVS真实服务器的对应关系应该是1:1。
删除kubernetes的service将会触发删除相应的IPVS虚拟服务器、IPVS真实服务器并且解绑虚拟网卡上的IP。
b)端口映射
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
c) 会话保持
IPVS支持会话保持,当kubernetes的service指定会话保持时,IPVS会设置超时时间,默认180分钟,下面是会话保持示例:
# 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
d) IPVS模式中的iptables和ipset
IPVS是专门为负载均衡设计的,因此IPVS自身不能实现kube-proxy的其他功能,比如包过滤、hairpin、源地址转换等。
IPVS模式会在上述场景中使用iptables,具体来说分为下面四种场景:
• kube-proxy启动参数中带有--masquerade-all=true,即所有流量作源地址转换。
• kube-proxy启动参数中指定CIDR。
• 支持LoadBalancer类型的service。
• 支持NodePort类型的service。
但是IPVS模式不会像iptables模式,创建太多iptables规则。所以我们引入了ipset来减少iptables规则。以下是IPVS模式维护的ipset表:
ipset名称 | set成员 | 功能 |
---|---|---|
KUBE-CLUSTER-IP | 所有Service IP + port | 如果启动参数中加了masquerade-all=true或clusterCIDR,用来做masquerade |
KUBE-LOOP-BACK | 所有Service IP + port + IP | 针对hairpin问题做masquerade |
KUBE-EXTERNAL-IP | Service External IP + port | 对方问外部IP的流量做masquerade |
KUBE-LOAD-BALANCER | lb型service的ingress IP + port | 对访问lb类型service的流量做masquerade |
KUBE-LOAD-BALANCER-LOCAL | 规定了externalTrafficPolicy=local的lb型的service的ingress IP + port | 接收规定了externalTrafficPolicy=local的lb型service |
KUBE-LOAD-BALANCER-FW | 规定了loadBalancerSourceRanges的lb型的service的ingress IP + port | 针对规定了loadBalancerSourceRanges的lb型service,用于过滤流量 |
KUBE-LOAD-BALANCER-SOURCE-CIDR | lb型的service的ingress IP + port + source CIDR | 针对规定了loadBalancerSourceRanges的lb型service,用于过滤流量 |
KUBE-NODE-PORT-TCP | NodePort型Service TCP port | 对访问NodePort(TCP)的流量作masquerade |
KUBE-NODE-PORT-LOCAL-TCP | 规定了externalTrafficPolicy=local的NodePort型Service TCP port | 接收规定了externalTrafficPolicy=local的NodePort型service |
KUBE-NODE-PORT-UDP | NodePort型Service UDP port | 对访问NodePort(UDP)的流量作masquerade |
KUBE-NODE-PORT-LOCAL-UDP | 规定了externalTrafficPolicy=local的NodePort型Service UDP port | 接收规定了externalTrafficPolicy=local的NodePort型service |
通常来说,对于IPVS模式的kube-proxy,无论有多少pod/service,iptables的规则数都是固定的。
e)使用基于IPVS的kube-proxy
目前,local-up-cluster脚本,GCE部署集群脚本,kubeadm都已经支持通过环境变量KUBE_PROXY_MODE=ipvs自动部署IPVS模式的集群。
另外可以通过在kube-proxy的启动参数中添加--proxy=mode=ipvs启动IPVS模式的kube-proxy,不过需要保证事先加载了IPVS依赖的内核模块。
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack_ipv4
最后,在kubernetes之前的版本中,需要通过设置特性开关SupportIPVSProxyMode来使用IPVS。在kubernetes v1.10版本中,特性开关SupportIPVSProxyMode默认开启,在1.11版本中该特性开关已经被移除。但是如果您使用kubernetes 1.10之前的版本,需要通过--feature-gates=SupportIPVSProxyMode=true开启SupportIPVSProxyMode才能正常使用IPVS