kube-proxy详解

kube-proxy 的转发模式可以通过启动参数–proxy-mode进行设置,有userspace、iptables、IPVS等可选项。

usersapce这里就不做赘述了, 由于转发是在用户态。所以效率不高,且容易丢包。 所以被废弃了。

iptables 模式

1
在这种模式下缺点就是在大规模的集群中,iptables添加规则会有很大的延迟。因为使用iptables,每增加一个svc都会增加一条iptables的chain。并且iptables修改了规则后必须得全部刷新才可以生效。

iptables是用户态应用程序,通过配置NetFilter规则表构建Linux内核防火墙。

iptables模式与userspace模式最大的区别在于,kube-proxy 利用iptables的DNAT模块,实现了Service 入口到Pod实际地址的转换,免去了一次内核态到用户态的切换。

例子

以下面Service为例,分析kube-proxy创建的iptables规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
meadata:
labels:
name: tomcat
name: tomcat
space:
ports:
- port: 6080
targetPort: 6080
nodePort: 30005
type: NodePort
selector:
app: tomcat

如上所示,在本例中我们创建了一个NodePort类型名为tomcat的服务。该服务的端口为6080,NodePort为30005,对应后端Pod的端口也为6080。虽然没有显示,但是它的Cluster IP为10.254.0.40。tomcat服务有两个后端Pod,IP分别是192.168.20.1和192.168.20.2。

kube-proxy 为该服务创建的iptables规则如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# iptables -S -t nat
-A PREROUTING -m -comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m -comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m -comment --comment "kubernetes postrouting rules " -j KUBE-POSTROUTING
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/tomcat:"\
-m tcp --dport 30005 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/tomcat:"\
-m -tpc --dport 30005 -j KUBE-SVC-67RLXXX

-A KUBE-SEP-ID6YXXX -s 192.168.20.1/32 -m -comment --comment "default/tomcat:"\
-j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YXXX -p tcp -m -comment --comment "default/tomcat:"\
-m tcp -j DANT --to-destination 192.168.20.1:6080

-A KUBE-SEP-IN2YXXX -s 192.168.20.2/32 -m -comment --comment "default/tomcat:"\
-j KUBE-MARK-MASQ
-A KUBE-SEP-IN2YXXX -p tcp -m -comment --comment "default/tomcat:"\
-m tcp -j DANT --to-destination 192.168.20.2:6080

-A KUBE-SERVICES -d 10.254.0.40/32 -p tcp -m -comment --comment \
"default/tomcat: cluster ip" -m tcp --dport 6080 -j KUBE-SVC-67RLXXX
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeport; NOTE: this must be the \
last rule in this chain " -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

-A KUBE-SVC-67RLXXX -m comment --comment "default/tomcat:" -m \
statistic --mode random --probability 0.500000000 -j KUBE-SEP-ID6YXXX
-A KUBE-SVC-67RLXXX -m comment --comment "default/tomcat:" -j \
KUBE-SEP-IN2YXXX
...

逐条分析。首先,如果是通过节点的30005端口访问NodePort,则会进入以下链,kube-proxy针对NodePort流量入口创建了KUBE-NODEPORTS 链。在我们这个例子中,KUBE-NODEPORTS 链进一步跳转到KUBE-SVC-67RLXXX链。

1
2
3
4
-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/tomcat:"\
-m tcp --dport 30005 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m -comment --comment "default/tomcat:"\
-m -tpc --dport 30005 -j KUBE-SVC-67RLXXX

再看下面的规则,这里采用了iptables的random模块,使连接有50%的概率进入KUBE-SEP-ID6YXXX链,50%的概率进入KUBE-SEP-IN2YXXX链。因此,kube-proxy的iptables模式采用随机数实现了服务的负载均衡。

1
2
3
4
-A KUBE-SVC-67RLXXX  -m comment --comment "default/tomcat:" -m \
statistic --mode random --probability 0.500000000 -j KUBE-SEP-ID6YXXX
-A KUBE-SVC-67RLXXX -m comment --comment "default/tomcat:" -j \
KUBE-SEP-IN2YXXX

KUBE-SEP-ID6YXXX 链的具体作用就是将请求通过DNAT发送到192.168.20.1的6080端口。

1
2
3
4
-A KUBE-SEP-ID6YXXX -s 192.168.20.1/32 -m -comment --comment "default/tomcat:"\
-j KUBE-MARK-MASQ
-A KUBE-SEP-ID6YXXX -p tcp -m -comment --comment "default/tomcat:"\
-m tcp -j DANT --to-destination 192.168.20.1:6080

当然,做完dnat后也需要snat

1
2
3
客户端C发起对一个服务S的访问,假设目的地址是(C,VIP),那么客户端期待得到的回程报文的源地址是VIP。即回程报文的源和目的地址应该是(VIP,C)。当网络报文经过网关(Linux内核的netfilter,包括iptables和IPVS director)进行一次DNAT后,报文的源和目的地址对呗修改成了(C,S)。当报文送到服务端S后,服务端一看报文源地址是C,便直接把响应报文返回给C,此时响应报文的源目的地址对是(S,C)。这与客户端期待报文源和目的地址不匹配,客户端收到后悔简单丢弃该报文。

因此,当报文不直接送达后端服务器,而是访问虚IP,经过一次中间网关(不管是虚拟网关还是实际网关)时,都需要在网关处做一次SANT,把报文的源IP改成网关IP地址,以便回程报文回到该网关。再让该网关把回程报文目的修改成客户端C的IP地址,源地址改成虚IP。

结果显示

综上所述,iptables模式最主要的链是KUBE-SERVICES、KUBE-SVC-和KUBE-SEP-

  • KUBE-SERVICES 链是访问集群内服务的数据包入口点,它会根据匹配到的目标IP:port将数据包分发到相应的KUBE-SVC-*链
  • KUBE-SVC-链相对于一个负载均衡器,它会将数据平均分发到KUBE-SEP-链,每个KUBE-SVC-链后面的KUBE-SEP-都和Service的后端Pod数量一样。
  • KUBE-SEP-*链通过DNAT将连接目的地址和端口从Service的IP:port替换为后端Pod的IP:port,从而将流量转发到相应的Pod。

iptables模式与userspace模式相比,虽然在稳定性和性能上均有不小的提升,但因为iptables使用NAT完成转发,也存在不可忽视的性能损耗。另外,当集群中存在上万服务时,Node上的iptables rules会非常庞大,对管理是个不小的负担,性能还会大打折扣。

ipvs 模式

IPVS是LVS的负载均衡模块,亦基于netfilter,但比iptables性能更高,具备更好的可扩展性。kube-proxy的IPVS模式在kubernetes1.11版本达到稳定。

先来了解一下为什么添加IPVS的原因,随着kubernetes集群规模的增长,其资源的可扩展性变得越来越重要,特别是对那些运行大型工作负载的企业,其服务的可扩展性尤其重要。要知道,iptables难以扩展到支持成千上万的服务,它纯粹是为防火墙设计的,并且底层路由表的实现是链表,对路由规则的增删改查操作都要涉及便利一次链表。

尽管kubernetes 1.6版本已经支持5000节点,但使用iptables模式的kube-proxy实际上是将集群扩展到5000节点的最大瓶颈。假设,我们有1000个服务,每个服务有10个后端Pod,将会在工作节点上至少产生10000*N(N>=4)个iptables记录,这可能使内核非常繁忙的处理每次iptables规则的刷新。

并且,使用IPVS做集群内服务的负载均衡可以解决iptables带来的性能问题。IPVS专门用于负载均衡,并使用更高效的数据结构(散列表),允许几乎无限的规模扩张。

IPVS 的工作原理

IPVS是Linux内核实现的四层负载均衡,是LVS负载均衡模块的实现。IPVS基于netfilter的散列表,相对于同样基于netfilter框架的iptables有更好的性能表现和扩展性。

IPVS支持TCP、UDP、SCTP、IPv4、IPv6等协议,也支持多种负载均衡策略,例如rr、wrr、lc、wlc、sh、dh、lblc等。IPVS通过persistent connection 调度算法原生支持会话保持功能。

LVS工作原理,简单来说。当外机的数据包首先经过netfilter的PERROUTING链,然后经过一次路由抉择到达INPUTU链,再做一次DNAT后经过FORWARD链离开本机网络路由协议栈。由于IPVS的DNAT是发生在netfilter的INPUT链,因此如何让网络报文经过INOUT链在IPVS中就变得非常重要。一般有两种解决方法,一种是把服务的虚IP写到本机的本地内核路由表中;另一种方法是在本机创建一个dunmmy网卡。然后把服务的虚IP绑定到该网卡上。kubernetes使用的是后者。

IPVS支持三种负载均衡模式:Direct Routing(简称DR),Tunneling(也称ipip模式)和NAT(也称Masq模式)

注:虽有一些版本的IPVS,例如华为和阿里自己维护的分支支持fullNAT,及同时支持SNAT和DNAT,但是Linux内核原生版本的IPVS只做DNAT,不做SNAT。因此在kubernetes service 的某些场景下,我们仍然需要iptables。

DR

IPVS的DR模式是最广泛的IPVS模式,它工作在L2,即通过Mac地址做LB,而非IP地址。在DR模式下,回程报文不会经过IPVS director 而是直接返回给客户端。因此,DR在带来高性能的同时,对网络也有一定的限制,及要求IPVS的director 和客户端在同一个局域网。另外,比较遗憾的是,DR不支持端口映射,无法支持kubernetes service的所有场景。

Tunneling

IPVS的Tunneling模式就是用IP包封装IP包,因此也称ipip模式。Tunneling模式下的报文不经过IPVS director,而是直接回复给客户端。Tunneling模式统一不支持端口映射,因此很难被用在kubernetes的service场景中。

NAT

IPVS的NAT模式支持端口映射,回程报文需要经过IPVS director,因此也称Masq(伪装)模式。kubernetes在用IPVS实现Service时用的正式NAT模式。当使用NAT模式时,需要注意对报文进行一次SNAT,这也是kubernetes使用IPVS实现Service的微妙之处。

kube-proxy IPVS模式参数

在运行基于IPVS的kube-proxy时,需要注意以下参数:

  • –proxy-mode:除了现有的userspace和iptables模式,IPVS模式通过–proxymode=ipvs进行配置。
  • –ipvs-scheduler:用来指定ipvs负载均衡算法,如果不配置则默认使用round-robin(rr)算法。
1
2
3
4
5
6
7
如果不配置则默认使用round-robin(rr)算法。支持配置的负载均衡算法有:
— rr:轮询,这种算法是最简单的,就是按依次循环的方式将请求调度到不同的服务器上,该算法最大的特点就是简单。轮询算法假设所有的服务器处理请求的能力都是一样的,调度器会将所有的请求平均分配给每个真实服务器,不管后端 RS 配置和处理能力,非常均衡地分发下去。
— lc:最小连接,这个算法会根据后端 RS 的连接数来决定把请求分发给谁,比如 RS1 连接数比 RS2 连接数少,那么请求就优先发给 RS1
— dh:目的地址哈希,该算法是根据目标 IP 地址通过散列函数将目标 IP 与服务器建立映射关系,出现服务器不可用或负载过高的情况下,发往该目标 IP 的请求会固定发给该服务器。
— sh:原地址哈希,与目标地址散列调度算法类似,但它是根据源地址散列算法进行静态分配固定的服务器资源。
— sed:最短时延
(未来。kube-proxy可能实现在service的annotations 配置负载均衡策略,这个功能应该只能在IPVS模式下才支持)
  • –cleanup-ipvs:类似于–cleanup-iptables参数。如果设置为true,则清除在IPVS模式下创建的IPVS规则;
  • –ipvs-sync-period:表示kube-proxy刷新IPVS规则的最大间隔时间,例如5秒。1分钟等,要求大于0;
  • –ipvs-min-sync-period:表示kube-proxy刷新IPVS规则最小时间间隔,例如5秒,1分钟等,要求大于0
  • –ipvs-exclude-cidrs:用于清除IPVS规则时告知kube-proxy不要清理该参数配置的网段的IPVS规则。因为我们无法区别某条IPVS规则到底是kube-proxy创建的,还是其他用户进程的,配置该参数是为了避免删除用户自己的IPVS规则。

1
2
3
4
5
6
7
一旦创建一个Service和Endpoint,IPVS模式的kube-proxy会做以下三件事:

1)确保一块dummy网卡(kube-ipvs0)存在,为什么要创建dummy网卡?因为IPVS的netfilter钩子挂载INPUT链,我们需要把Service的访问绑定在dummy网卡上让内核“觉得”虚IP就是本机IP,进而进入INPUT链。

2)把Service的访问IP绑定在dummy网卡上

3)通过socket调用,创建IPVS的virtual server和real server,分别对应kubernetes的Service和Endpoint。

IPVS模式中的iptables和ipset

IPVS用于流量转发,它无法处理kube-proxy中的其他问题,例如包过滤、SNAT等。具体来说,IPVS模式的kube-proxy将在以下4中情况依赖iptables

  • kube-proxy 配置启动参数masquerade-all=true,即集群中所有经过Kube-proxy的包都将做一次SNAT
  • kube-proxy 启动参数指定集群IP地址范围
  • 支持Load Balance 类型的服务,用于配置白名单
  • 支持NodePort类型的服务,用于在包跨节点前配置MASQUERADE,类似于上文提到的iptables模式

我们不想创建太多的iptables规则,因此使用了ipset减少iptables规则,使得不管集群内有多少服务,IPVS模式iptables规则的总数在5条以内。

Donate