背景
下面我将以这个思维导图来总结一下kube-proxy。
简介
- 每台机器上都运行一个 kube-proxy 服务,它监听 API server 中 service 和 endpoint 的变化情况,并通过内核的iptables等来为服务配置负载均衡(仅支持TCP 和 UDP)。因为内核有一定的nat能力,内核只解析tcp或者udp的包头,不会解析用户数据,这是用户态去干的事情。
- kube-proxy 可以直接运行在物理机上,也可以以 static pod 或者 DaemonSet 的方式运行。
实现方式
kube-proxy 当前支持一下几种实现:
- userspace:最早的负载均衡方案,它在用户空间监听一个端口,所有服务通过 iptables 转发到这个端口,然后在其内部负载均衡到实际的 Pod。该方式最主要的问题是效率低,有明显的性能瓶颈。
- iptables:目前推荐的方案,完全以iptables 规则的方式来实现 service 负载均衡。该方式最主要的问题是在服务多的时候产生太多的 iptables 规则,非增量式更新会引入一定的时延,大规模情况下有明显的性能问题。
- ipvs:为解决 iptables 模式的性能问题,v1.8 新增了 ipvs 模式,采用增量式更新,并可以保证 service更
新期间连接保持不断开。 - winuserspace:同 userspace,但仅工作在 windows上。
kube-proxy工作原理
- 首先kube-proxy会watch apiserver。监听到svc后,如果工作在iptables模式,就可以通过iptables配置dnat规则。
- 用户访问serviceip也就是clusterip,数据包会转发到目标pod。
Netfilter
当一个数据包被操作系统接收到后,内核在处理这个数据包会经过哪些流转,这个流转的框架叫做Netfilter。
- 网卡在链路层接收到数据包后,如果是本机的有效数据包,它会把包发送到Netfilter框架。也就是SKBuffer,在linux中代表数据包的数据结构。
- SKBuffer会被网络层处理,在处理中,Netfilter提供了很多hook点。即在用户态配置规则,Netfilter解析这些规则,来实现对应的行为。比如转换地址,防火墙规则等。图中绿色框就是hook点。
- 网络层首先是routing decison。它会看数据包的五元组(源IP地址,源端口,目的IP地址,目的端口和传输层协议),如果目标地址是本机地址,它就会把包转发到local process进行处理。
- 如果不是本机地址,那么就会把包转发forward。
- 经过nat转换地址后把数据包发送出去output。
Netfilter 和 iptables
通过配置用户态规则,实现访问service的clusterip。用户访问clusterip,就是通过kube-proxy配置的nat规则转发到了后端pod中。
- 内核态中网卡接收到数据包,会发起一个硬件中断,告诉cpu我这边接收到数据包了。cpu响应硬件中断后,会发起一个软中断。ksoftirqd就是用来处理软中断的。
- cpu调用SoftIRQ Handler处理数据包。网卡驱动会把数据包存到内存中,SoftIRQ从内存中读取数据包,构造SKB,SKBuffer包含了用户数据和header,header就是数据包的五元组信息。
- 然后SoftIRQ会把SKBuffer交给Netfilter来处理。
- Netfilter会去用户态读取配置的iptables规则,如果数据包匹配,那么执行对应的action。一般是修改目标地址。
iptables
- PREROUTING:数据包在经过路由判断之前的操作。支持raw,magle,dnat表。
- LOCAL_IN:经过路由判断,如果是本机,就会转发到LOCAL_IN的hook点,接着被本地进程处理。支持mangle,filter,snat表。
- FORWARD:如果不是本机,就会转发到FORWARD。支持mangle,filter表。
- LOCAL OUT:本地进程处理完后,转发到LOCAL OUT。支持raw,mangle,dnat,filter,snat表。
- POSTROUTING:再经过路由判断后,转发到POSTROUTING链,数据包就转发出去了。支持mangle,snat表。
针对kube-proxy,要实现的是修改目标地址,也就是dnat,在PREROUTING和LOCAL OUT可以实现。
hook点
raw:优先级最高,可以对数据包在路由前进行处理。决定数据包是否被状态跟踪机制处理。
mangle:修改数据包的服务类型、TTL。
nat:负载均衡规则。转换数据包地址。包括sant和dnat。
filter:防火墙规则。过滤数据包。
iptables实践
我这里有3个nginx的pod。pod,svc,ep的ip如下:
iptables-save -t nat
或者 iptables -L -t nat
找nginx svc的ip 10.96.180.103的iptables规则。
-A KUBE-SVC-P4Q3KNUAWJVP4ILH ! -s 10.100.0.0/16 -d 10.96.180.103/32 -p tcp -m comment --comment "default/nginx:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
# KUBE-SERVICES链,如果数据包目标地址是10.96.180.103/32,协议是tcp,目标端口是80,那么执行KUBE-SVC-P4Q3KNUAWJVP4ILH这个动作。
-A KUBE-SERVICES -d 10.96.180.103/32 -p tcp -m comment --comment "default/nginx:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-P4Q3KNUAWJVP4ILH
看KUBE-SVC-P4Q3KNUAWJVP4ILH具体实现。
# 是一个chain。
:KUBE-SVC-P4Q3KNUAWJVP4ILH - [0:0]
# 标记
-A KUBE-SVC-P4Q3KNUAWJVP4ILH ! -s 10.100.0.0/16 -d 10.96.180.103/32 -p tcp -m comment --comment "default/nginx:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
# 以33%的几率执行KUBE-SEP-646EWZKVGRBBVGL3动作。
-A KUBE-SVC-P4Q3KNUAWJVP4ILH -m comment --comment "default/nginx:http" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-646EWZKVGRBBVGL3
# 如果上面的规则不符合,则以50%的几率执行KUBE-SEP-CMLKHPEE3WJCPCDF动作。
-A KUBE-SVC-P4Q3KNUAWJVP4ILH -m comment --comment "default/nginx:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-CMLKHPEE3WJCPCDF
# 如果上面的规则不符合,则以100%的几率执行KUBE-SEP-WDHV5LTXWGDX4S67动作。
-A KUBE-SVC-P4Q3KNUAWJVP4ILH -m comment --comment "default/nginx:http" -j KUBE-SEP-WDHV5LTXWGDX4S67
分别查看具体动作。
# KUBE-SEP-646EWZKVGRBBVGL3
-A KUBE-SEP-646EWZKVGRBBVGL3 -s 10.100.104.49/32 -m comment --comment "default/nginx:http" -j KUBE-MARK-MASQ
# 执行dnat动作,修改目标地址为10.100.104.49,即node2。
-A KUBE-SEP-646EWZKVGRBBVGL3 -p tcp -m comment --comment "default/nginx:http" -m tcp -j DNAT --to-destination 10.100.104.49:8080
# KUBE-SEP-CMLKHPEE3WJCPCDF
-A KUBE-SEP-CMLKHPEE3WJCPCDF -s 10.100.166.189/32 -m comment --comment "default/nginx:http" -j KUBE-MARK-MASQ
# 如果node2不符合,修改目标地址为10.100.166.189,即node1。
-A KUBE-SEP-CMLKHPEE3WJCPCDF -p tcp -m comment --comment "default/nginx:http" -m tcp -j DNAT --to-destination 10.100.166.189:8080
# KUBE-SEP-WDHV5LTXWGDX4S67
-A KUBE-SEP-WDHV5LTXWGDX4S67 -s 10.100.219.102/32 -m comment --comment "default/nginx:http" -j KUBE-MARK-MASQ
# 如果node2也不符合,修改目标地址为10.100.219.102,即master。
-A KUBE-SEP-WDHV5LTXWGDX4S67 -p tcp -m comment --comment "default/nginx:http" -m tcp -j DNAT --to-destination 10.100.219.102:8080
由于iptables规则是从上到下执行的,所以到nginx的请求,会依次从33%,50%,100%的几率去转发到目标pod。
查看KUBE-SERVICES链
:KUBE-SERVICES - [0:0]
# 把KUBE-SERVICES链加到了PREROUTING和OUTPUT链的hook点里。也就是说所有进来的和出去的数据包都要经过KUBE-SERVICES处理。
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
k8s iptables数据转发
- 外部请求或者本地pod访问nginx服务,首先都要经过KUBE-SERVICES链。
- -d:目标clusterip地址,–dport:目标端口。
-A KUBE-SERVICES -d 10.96.180.103/32 -p tcp -m comment --comment "default/nginx:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-P4Q3KNUAWJVP4ILH # KUBE-SVC-P4Q3KNUAWJVP4ILH,SVC后面的P4Q3KNUAWJVP4ILH是由port,protocol经过hash所得。
- 然后先经过KUBE-MARK-MASQ链标记。
-A KUBE-SVC-P4Q3KNUAWJVP4ILH ! -s 10.100.0.0/16 -d 10.96.180.103/32 -p tcp -m comment --comment "default/nginx:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ # 给数据包打上0x4000/0x4000的标记,如果数据包标记是0x8000/0x8000则丢弃。 -A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000 -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
- 再根据后端pod的数量,根据负载均衡算法选择pod。(–mode random –probability )
-A KUBE-SVC-P4Q3KNUAWJVP4ILH -m comment --comment "default/nginx:http" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-646EWZKVGRBBVGL3
- 根据DNAT规则,转发到目标pod。(DNAT –to-destination)
-A KUBE-SEP-646EWZKVGRBBVGL3 -p tcp -m comment --comment "default/nginx:http" -m tcp -j DNAT --to-destination 10.100.104.49:8080
- 数据转发出去,经过POSTROUTING,再到KUBE-POSTROUTING。
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
- 最后经过MASQUERADE链。是用发送数据包的网卡IP来替换源IP。
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
- 再由CNI转发出去。
# 源地址类型是local,伪装源地址为vxlan.calico的地址 -A cali-POSTROUTING -o vxlan.calico -m comment --comment "cali:B61PPpOLVc6B1IiX" -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE -A cali-nat-outgoing -m comment --comment "cali:Dw4T8UWPnCLxRJiI" -m set --match-set cali40masq-ipam-pools src -m set ! --match-set cali40all-ipam-pools dst -j MASQUERADE
svc的ip只是在iptables判断用,只是一条规则。
ipvs
iptables的缺点
- iptables对于任何负载均衡配置,都需要很多规则去堆叠。
- iptables转发数据包时根据rr,weight等,在后端有非常多pod时效率不高。
- 当更新iptables规则时,kube-proxy会把原来的iptables删除,再重新刷,开销非常大,可能导致有的进程饿死。
ipvs的特点
- 不支持PREROUTING的hook点。
- 在经过路由判断后,如果是本机的话,访问的地址还是clusterip地址,这就需要把clusterip地址绑定到dummy设备上,这样才会把这个ip当成一个本地有效的ip。
iptables转换到ipvs
ks edit cm kube-proxy
mode: "ipvs"
ks delete po kube-proxy-7qr4k
yum -y install ipvsadm
ip a
ipvsadm -L -n