IPVS
当我们部署kube-prox时都会存在一个选择, 是选择ipvs模式还是iptables哪个好呢? 也还存在其它的疑问外部流量访问到svc,然后svc跳转pod访问应用.
svc怎么跳转访问pod的呢?
kube-proxy实现svc到pod的访问和负载均衡. 让我们带着问题阅读下面的文章
1.IPVS工作原理
IPVS基本上是一种高效的Layer-4交换机,它提供负载平衡的功能。当一个TCP连接的初始SYN报文到达时,IPVS就选择一台服务器,将报文转发给它。此后通过查发报文的IP和TCP报文头地址,保证此连接的后继报文被转发到相同的服务器。这样,IPVS无法检查到请求的内容再选择服务器,这就要求后端的服务器组是提供相同的服务,不管请求被送到哪一台服务器,返回结果都应该是一样的。但是在有一些应用中后端的服务器可能功能不一,有的是提供HTML文档的Web服务器,有的是提供图片的Web服务器,有的是提供CGI的Web服务器。这时,就需要基于内容请求分发 (Content-Based Request Distribution),同时基于内容请求分发可以提高后端服务器上访问的局部性。
因为当我们指定--proxy-mode=ipvs
时IPVS使用的NAT模式做服务映射, 所以我们主要说NAT模式工作原理
IPVS/NAT模式
由于IPv4中IP地址空间的日益紧张和安全方面的原因,很多网络使用保留IP地址(10.0.0.0/255.0.0.0、 172.16.0.0/255.128.0.0和192.168.0.0/255.255.0.0)[64, 65,66]。这些地址不在Internet上使用,而是专门为内部网络预留的。当内部网络中的主机要访问Internet或被Internet访问时,就需要采用网络地址转换(Network Address Translation, 以下简称NAT),将内部地址转化为Internets上可用的外部地址。
NAT的工作原理是报文头(目标地址、源地址和端口等)被正确改写后,客户相信它们连接一个IP地址,而不同IP地址的服务器组也认为它们是与客户直接相连的。由此,可以用NAT方法将不同IP地址的并行网络服务变成在一个IP地址上的一个虚拟服务。
客户通过Virtual IPAddress(虚拟服务的IP地址)访问网络服务时,请求报文到达调度器,调度器根据连接调度算法从一组真实服务器中选出一台服务器,将报文的目标地址 Virtual IPAddress改写成选定服务器的地址,报文的目标端口改写成选定服务器的相应端口,最后将修改后的报文发送给选出的服务器。同时,调度器在连接Hash 表中记录这个连接,当这个连接的下一个报文到达时,从连接Hash表中可以得到原选定服务器的地址和端口,进行同样的改写操作,并将报文传给原选定的服务器。当来自真实服务器的响应报文经过调度器时,调度器将报文的源地址和源端口改为Virtual IPAddress和相应的端口,再把报文发给用户。我们在连接上引入一个状态机,不同的报文会使得连接处于不同的状态,不同的状态有不同的超时值
[图片上传失败...(image-d8414e-1604968259756)]
- 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间
- PREROUTING链首先会接收到用户请求,判断目标IP确定是本机IP,将数据包发往INPUT链
- IPVS是工作在INPUT链上的 ,当用户请求到达INPUT时,IPVS会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时IPVS会强行修改数据包里的目标IP地址及端口,并将新的数据包发往FORWORD链
- FORWORD链将数据将数据包发给POSTROUTING链
- POSTROUTING链接收数据包后发现目标IP地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器
2.Service提供常用的类型
- ClusterIP,也是默认方式。Service会分配一个集群内部的固定虚拟IP,实现集群内通过该IP来对POD进行访问。这个又有两类,上面说到的最普通的Service,ClusterIP还有一种是Headless Service,这种形式不会分配IP也不会通过kube-proxy做反向代理或者负载均衡,而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为POD的IP列表,这种主要是共StatefulSet类型使用。
- NodePort,这种类型的Service是除了使用ClusterIP的功能外还会映射一个宿主机随机端口到service上,这样集群外部可以通过宿主机IP+随机端口来访问。
- LoadBalancer:和nodePort类似,不过除了使用ClusterIP和NodePort之外还会向使用的公有云申请一个负载均衡器,从而实现集群外部通过LB来访问服务
- ExternalName:是Service的一种特例,此模式主要面对运行在集群外部的服务,通过它可以将外部服务映射到k8s集群,具备k8s内服务的一些特性,来为集群内部提供服务。
我们主要说的是ClusterIP和NodePort
3.kube-proxy如何处理请求
[图片上传失败...(image-674319-1604968259756)]
如上图所示Iptables的链的规则从pod发起的请求到达主机以后首先会到达PREROUTEING链, kube-proxy 中主要用到了iptables的NAT表
NAT表有三种内建链:
- PREROUTING链 – 处理刚到达本机并在路由转发前的数据包。它会转换数据包中的目标IP地址(destination ip address),通常用于DNAT(destination NAT)。
- POSTROUTING链 – 处理即将离开本机的数据包。它会转换数据包中的源IP地址(source ip address),通常用于SNAT(source NAT)。
- OUTPUT链 – 处理本机产生的数据包。
$ iptables-save
...
*nat
:PREROUTING ACCEPT [7:420]
:INPUT ACCEPT [7:420]
:OUTPUT ACCEPT [9:556]
:POSTROUTING ACCEPT [9:556]
:DOCKER - [0:0]
:KUBE-FIREWALL - [0:0]
:KUBE-KUBELET-CANARY - [0:0]
:KUBE-LOAD-BALANCER - [0:0]
:KUBE-MARK-DROP - [0:0]
:KUBE-MARK-MASQ - [0:0]
:KUBE-NODE-PORT - [0:0]
:KUBE-POSTROUTING - [0:0]
:KUBE-SERVICES - [0:0]
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A POSTROUTING -s 10.243.0.0/16 -d 10.243.0.0/16 -j RETURN
-A POSTROUTING -s 10.243.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE
-A POSTROUTING ! -s 10.243.0.0/16 -d 10.243.104.0/21 -j RETURN
-A POSTROUTING ! -s 10.243.0.0/16 -d 10.243.0.0/16 -j MASQUERADE
-A KUBE-FIREWALL -j KUBE-MARK-DROP
-A KUBE-LOAD-BALANCER -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-NODE-PORT -p tcp -m comment --comment "Kubernetes nodeport TCP port for masquerade purpose" -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
-A KUBE-SERVICES ! -s 10.243.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT
-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT
COMMIT
...
3.1集群内部流量
流程如下:
+------+ +--------------+ +--------------------+ +------+
| POD1 | --> | OUTPUT CHAIN | --> | KUBE-SERVICES RULE | --> | IPVS |
+------+ +--------------+ +--------------------+ +------+
OUTPUT CHAIN规则如下, 所有流量跳转到KUBE-SERVICES CHAIN
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
KUBE-SERVICES CHAIN规则如下
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-MARK-MASQ all -- !10.243.0.0/16 0.0.0.0/0 /* Kubernetes service cluster ip + port for masquerade purpose */ match-set KUBE-CLUSTER-IP dst,dst
KUBE-NODE-PORT all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
ClusterIP service 的访问流量会匹配ACCEPT,其匹配规则是match-set KUBE-CLUSTER-IP dst,dst,即封包的IP:port匹配内核中名为KUBE-CLUSTER-IP的 ipset。
IPVS的配置如下
$ ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.244.0.1:443 rr
-> 10.40.58.153:6443 Masq 1 0 0
-> 10.40.58.154:6443 Masq 1 0 0
-> 10.40.61.116:6443 Masq 1 1 0
3.2集群外部流量
流程如下:
+--------+ +---------+ +------------------+ +--------------------+ +----------------+ +------+
| CLIENT | --> | SERVICE | --> | PREROUTING CHAIN | --> | KUBE-SERVICES RULE | --> | KUBE-MARK-MASQ | --> | IPVS |
+--------+ +---------+ +------------------+ +--------------------+ +----------------+ +------+
PREROUTING CHAIN规则如下, 所有流量跳转到KUBE-SERVICES CHAIN
$ iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
KUBE-SERVICES CHAIN规则如下
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-MARK-MASQ all -- !10.243.0.0/16 0.0.0.0/0 /* Kubernetes service cluster ip + port for masquerade purpose */ match-set KUBE-CLUSTER-IP dst,dst
KUBE-NODE-PORT all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
ClusterIP service 的访问流量会匹配KUBE-MARK-MASQ,其匹配规则是match-set KUBE-CLUSTER-IP dst,dst 并且源地址不是pod的CIDR地址段,所有流量jump 到KUBE-MARK-MASQ链
Chain KUBE-MARK-MASQ (3 references)
target prot opt source destination
MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
为所有的流封包打上0x4000的标记, 后续交给IPVS处理
3.3 NodePort
流程如下:
+-----+ +------------------+ +--------------------+ +----------------+ +----------------+ +------+
| pod | --> | PREROUTINH CHAIN | --> | KUBE-SERVICES RULE | --> | KUBE-NODE-PORT | --> | KUBE-MARK-MASQ | --> | IPVS |
+-----+ +------------------+ +--------------------+ +----------------+ +----------------+ +------+
对于访问 NodePort service 来说,其流量第一步依然是经由PREROUTING chain,而后跳转到KUBE-SERVICES chain。在这里封包会jump到KUBE-NODE-PORT target chain,其匹配条件是ADDRTYPE match dst-type LOCAL,即目标地址类型是 Node 配置的地址(eth0),这符合 NodePort 的定义。
KUBE-NODE-PORT 的规则如下
Chain KUBE-NODE-PORT (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* Kubernetes nodeport TCP port for masquerade purpose */ match-set KUBE-NODE-PORT-TCP dst
对于匹配到KUBE-NODE-PORT的封包jump到KUBE-MARK-MASQ打上0x4000标记
KUBE-NODE-PORT-TCP ipset 规则如下:
$ ipset -L KUBE-NODE-PORT-TCP
Name: KUBE-NODE-PORT-TCP
Type: bitmap:port
Revision: 1
Header: range 0-65535
Size in memory: 524432
References: 1
Members:
32106
ipvs的配置如下
$ ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.40.61.116:32106 rr
TCP 10.243.104.0:32106 rr
TCP 10.243.104.1:32106 rr
4.summary
kube-proxy IPVS 模式依然会使用 iptables 来做封包 MASQUERADE,但是其使用 ipset match 的方式保持了 iptables 规则数量恒定(几条),随着 service 的数量增多,不会出现 iptables 规则过多以及线性匹配的性能瓶颈。 这里使用 iptables 与否不是关键,iptalbes 与 IPVS 理论上都是内核 netfilter 模块的客户端,IPVS 这里只是借用 iptables 方便地设置 netfilter 转发。
流程如下:
+--------+ +---------+ +------+ +-----+
| CLIENT | --> | SERVICE | --> | IPVS | --> | POD |
+--------+ +---------+ +------+ +-----+
架构如下:
+--------+ +------+ +-------+
| CLIENT | --> | | --> | POD01 |
+--------+ | | +-------+
| | +-------+
| NODE | --> | POD02 |
| | +-------+
| | +-------+
| | --> | POD03 |
+------+ +-------+
- 集群中的每台NODE都是Director, 因为所有的iptables和ipvs规则会配置在集群中的每个node上
- Service上的CLusterIP为VIP
使用
ip addr show
会发现存在kube-ipvs0的虚拟网卡,IP地址cluster的IP