在kubernets集群的每一个节点上都运行着kube-proxy进程,负责实现Kubernetes中service组件的虚拟IP服务。目前kube-proxy有以下三种工做模式:node
User space模式git
在这种模式下,kube-proxy经过观察Kubernetes中service和endpoint对象的变化,当有新的service建立时,全部节点的kube-proxy在node节点上随机选择一个端口,在iptables中追加一条把访问service的请求重定向到这个端口的记录,并开始监听这个端口的链接请求。github
好比说建立一个service,对应的IP:1.2.3.4,port:8888,kube-proxy随机选择的端口是32890,则会在iptables中追加算法
-A PREROUTING -j KUBE-PORTALS-CONTAINER -A KUBE-PORTALS-CONTAINER -d 1.2.3.4/32 -p tcp --dport 8888 -j REDIRECT --to-ports 32890
KUBE-PORTALS-CONTAINER 是kube-proxy在iptable中建立的规则链,在PREROUTING阶段执行后端
执行过程:缓存
这种模式的缺点就是,请求数据须要到kube-proxy中,就是用户空间中,才能决定真正要转发的实际服务地址,性能会有损耗。而且在应用执行过程当中,kube-proxy的可用性也会影响系统的稳定服务器
iptables 模式(目前kube-proxy默认的工做模式)网络
在这种模式下,kube-proxy经过观察Kubernetes中service和endpoint对象的变化,当有servcie建立时,kube-proxy在iptables中追加新的规则。对于service的每个endpoint,会在iptables中追加一条规则,设定动做为DNAT,将目的地址设置成真正提供服务的pod地址;再为servcie追加规则,设定动做为跳转到对应的endpoint的规则上,session
默认状况下,kube-proxy随机选择一个后端的服务,能够经过iptables中的 -m recent 模块实现session affinity功能,经过 -m statistic 模块实现负载均衡时的权重功能数据结构
好比说建立了一个service,对应的IP:1.2.3.4,port:8888,对应一个后端地址:10.1.0.8:8080,则会在iptables中追加(主要规则):
-A PREROUTING -j KUBE-SERVICES -A KUBE-SERVICES -d 1.2.3.4/32 -p tcp –dport 8888 -j KUBE-SVC-XXXXXXXXXXXXXXXX -A KUBE-SVC-XXXXXXXXXXXXXXXX -j KUBE-SEP-XXXXXXXXXXXXXXXX -A KUBE-SEP-XXXXXXXXXXXXXXXX -p tcp -j DNAT –to-destination 10.1.0.8:8080
执行过程:KUBE-SERVICES 是kube-proxy在iptable中建立的规则链,在PREROUTING阶段执行
在这种逻辑下,数据转发都在系统内核层面作,提高了性能,而且即使kube-proxy不工做了,已经建立好的服务还能正常工做 。可是在这种模式下,若是选中的第一个pod不能响应,请求就会失败,不能像userspace模式,请求失败后kube-proxy还能对其余endpoint进行重试。
这就要求咱们的应用(pod)要提供readiness probes功能,来验证后端服务是否能正常提供服务,kube-proxy只会将readiness probes测试经过的pod写入到iptables规则中,以此来避免将请求转发到不正常的后端服务中。
介绍
因为在iptables模式中,kube-proxy须要为每个服务,每个endpoint都生成相应的iptables规则,当服务规模很大时,性能也将显著降低,所以kubernetes在1.8引入了IPVS模式,1.9版本中变成beta,在1.11版本中成为GA。
在IPVS模式下,kube-proxy观察Kubernetes中service和endpoint对象的变化,经过调用netlink接口建立相应的IPVS规则,并周期性的对Kubernetes的service、endpoint和IPVS规则进行同步,当访问一个service时,IPVS负责选择一个真实的后端提供服务。
IPVS模式也是基于netfilter的hook功能(INPUT阶段),这点和iptables模式是同样的,可是用的是内核工做空间的hash表做为存储的数据结构,在这种模式下,iptables可经过ipset来验证具体请求是否知足某条规则。在service变成时,只用更新ipset中的记录,不用改变iptables的规则链,所以能够保证iptables中的规则不会随着服务的增长变多,在超大规模服务集群的状况下提供一致的性能效果。
在对规则进行同步时的性能也要高于iptables(只用对特定的一个hash表进行更新,而不是像iptables模式下对整个规则表进行操做),因此能提供更高的网络流量。
IPVS在对后端服务的选择上也提供了更多灵活的算法:因为IPVS的优点,因此尽量的采用IPVS做为kube-proxy的工做模式,经过如下步骤在建立集群时启用IPVS
安装依赖工具包
apt install -y ipvsadm ipset
kube-proxy启用IPVS时依赖模块:
ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack
可经过以下命令检查系统是否启用了这些模块
$ lsmod | grep -e ip_vs -e nf_conntrack
若是没有启用,经过以下命令启用
$ modprobe -- ip_vs $ modprobe -- ip_vs_rr $ modprobe -- ip_vs_wrr $ modprobe -- ip_vs_sh $ modprobe -- nf_conntrack
kube-proxy配置文件中追加IPVS相关配置
proxy-mode: "ipvs" ipvs-min-sync-period: ipvs 规则刷新的最小时间间隔 ipvs-scheduler: ipvs的负载算法(rr、lc等) ipvs-sync-period: ipvs规则刷新的最大时间间隔(30s)
采用用IPVS模式时,在启动kube-proxy前必需要确保IPVS模块在服务上存在,若是kube-proxy启动时,通过验证发现IPVS模块不可用,kube-proxy自动采用iptables模式工做,参考代码:server_others.go
在介绍kube-proxy如何使用IPVS实现对service的请求前,先看下IPVS的简单介绍及工做原理
介绍
IPVS是Linux服务器中用来实现LVS(Linux virtual service)的一个模块,工做在内核空间是一种基于虚拟IP的负载均衡技术,经过用户空间的ipvsadm来执行规则制定,常见术语
名称 | 解释 |
---|---|
RS | Real Server 后端请求处理服务器 |
CIP | Client IP,客户端IP |
VIP | Director Virtual IP,负载均衡器虚拟IP |
DIP | Director IP,负载均衡器IP |
RIP | Real Server IP,后端处理请求的真实服务器IP |
工做原理
由于IPVS是hook到netfilter的input链中执行的,因此须要先了解下数据在netfilter中的流转过程
正常流程:
加入IPVS后的简化逻辑图
能够看到,当IPVS模块工做后,在input链上会再次对请求作匹配,匹配到的直接作postrouting,再也不进入用户空间处理,而是把请求发送到IPVS选中的提供真实服务的服务器
IPVS有三种工做模式NAT(Masq)、DR、TUN,由于kube-proxy采用的是NAT模式,因此下面只对NAT模式进行分析
NAT:Network Address Transition(网络地址转换),工做在此模式下的IPVS,首先根据scheduler策略选择一个真实的后端服务,接着将请求头中的目的地址IP、端口转换成真实服务的IP和端口,以后进入POSTROUTING,向真实的服务器发起请求。当响应数据返回时,再经过masqreade,将返回数据的源地址修改为虚拟服务器的地址,具备有以下特性:
Real Server应该使用私有ip地址,和client IP不在一个网段中(若是在一个网段中,real Server能够将数据直接返回客户端,不会再通过Director Server返回)
通常Real Server的网关应该指向DIP,否则的话没法保证响应报文通过Director Server(IP 协议,数据发送给不在同一个网段的服务器时,会把数据发送给网关)
RIP要和DIP应该在同一网段内
进出的报文,不管请求仍是响应都要通过Directory server(知足上面三点,这个是天然而然的)
支持端口映射
Real Server可使用任意系统,只要端口对应便可
其流程以下:
当用户请求到达DirectorServer,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP 。
PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链。
IPVS比对数据包请求的服务是否为集群服务,如果,修改数据包的目标IP地址为后端服务器IP,而后将数据包发至POSTROUTING链。 此时报文的源IP为CIP,目标IP为RIP ,在这个过程完成了目标IP的转换。
POSTROUTING链经过选路,将数据包发送给Real Server。
由于Real Server的网关指向DIP,而且一般状况下CIP和RIP不在同一个网段内,在这种状况下,Real Server会先将数据发送给Director Server(网关),这样数据就能够沿原路返回。
Director Server在响应客户端前,此时会将源IP地址修改成本身的VIP地址,而后响应给客户端。 此时报文的源IP为VIP,目标IP为CIP。
在此过程当中CIP一直是不发生变化的
从IPVS的工做原理上能够知道,kube-proxy想使用IPVS,须要完成如下几个工做
为了让node节点识别对应的VIP,kube-proxy启动时建立了一个虚拟网络设备kube-ipvs0,类型是dummy,并把service的VIP都设置到这个设备下面。建立这个设备,以及向设备中追加VIP的逻辑都在代码proxier.go中
$ ip addr 27: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default link/ether ba:6b:cb:b9:61:89 brd ff:ff:ff:ff:ff:ff inet 10.254.0.1/32 brd 10.254.0.1 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.254.0.2/32 brd 10.254.0.2 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.254.199.160/32 brd 10.254.199.160 scope global kube-ipvs0 valid_lft forever preferred_lft forever
对应的service
$ kubectl get svc -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default clientip ClusterIP 10.254.199.160 <none> 8080/TCP 23h default kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 25d kube-system kube-dns ClusterIP 10.254.0.2 <none> 53/UDP,53/TCP,9153/TCP 23d kube-system kubelet ClusterIP None <none> 10250/TCP 18d
能够看到全部的VIP都在kube-ipvs0这个设备下面
为了让iptables中的规则容许对Servcie的请求经过,kube-proxy在iptables中追加了以下几条主要规则(iptables.masqueradeAll=false;clusterCIDR=172.30.0.0/16,clusterCIDR就是集群中的pod能分配的IP地址段):
NAT表中:
$ iptables -t nat -nL Chain PREROUTING (policy ACCEPT) target prot opt source destination KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */ 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 */ Chain POSTROUTING (policy ACCEPT) target prot opt source destination KUBE-POSTROUTING all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */ 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 Chain KUBE-POSTROUTING (1 references) target prot opt source destination MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-LOOP-BACK dst,dst,src Chain KUBE-SERVICES (2 references) target prot opt source destination KUBE-MARK-MASQ all -- !172.30.0.0/16 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
有两点须要解释下
关于规则
KUBE-MARK-MASQ all -- !172.30.0.0/16 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
意思是对于非集群内的pod访问集群的cluster IP时会执行masquerade,这是由于在kube-proxy的启动条件设置成了iptables.masqueradeAll=false;clusterCIDR=172.30.0.0/16才这样
若是设置成iptables.masqueradeAll=false;clusterCIDR=,即不设置CIDR规则会变成
KUBE-MARK-MASQ all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP src,dst
效果应该是对本身访问本身的请求作masquerade,其余请求都不作
若是iptables.masqueradeAll=true,规则变成
KUBE-MARK-MASQ all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst效果对全部请求都作masquerade
关于匹配语法 -m set --match-set 。。。 表明用set模块的对请求进行匹配
语法以下:
-m set --match-set name flags
具体传入的flags要依据 name指定的set的类型来传入,具体ipset的用法可经过以下命令查看
ipset --help
这里以KUBE-CLUSTER-IP为例
$ ipset --list KUBE-CLUSTER-IP Name: KUBE-CLUSTER-IP Type: hash:ip,port Revision: 5 Header: family inet hashsize 1024 maxelem 65536 Size in memory: 408 References: 2 Number of entries: 5 Members: 10.254.0.2,udp:53 10.254.0.1,tcp:443 10.254.0.2,tcp:9153 10.254.199.160,tcp:8080 10.254.0.2,tcp:53
结合前面的规则
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
表述的意思是请求的目的地址IP、目的端口和KUBE-CLUSTER-IP中的Members有匹配时,就接收请求
filter表中
$ iptables -nL -t filter Chain FORWARD (policy ACCEPT) target prot opt source destination KUBE-FORWARD all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ Chain OUTPUT (policy ACCEPT) target prot opt source destination KUBE-FIREWALL all -- 0.0.0.0/0 0.0.0.0/0 Chain KUBE-FIREWALL (2 references) target prot opt source destination DROP all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000 Chain KUBE-FORWARD (1 references) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000 ACCEPT all -- 172.30.0.0/16 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED ACCEPT all -- 0.0.0.0/0 172.30.0.0/16 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED
经过分析kube-proxy追加的这些规则,能够看到访问service提供的服务时,iptables中的规则最终都会容许请求经过,而且经过 -m set --match-set 的规则匹配机制,kube-proxy不用随着servcie的建立和销毁来修改iptables中的规则,只用修改对应的ipset中相应的Members就可以达到效果,保证iptables的规则表固定大小,实现大规模服务集群中保持性能的稳定
请求经过了iptables中的规则后,在INPUT阶段,须要IPVS来对请求进行判断,看请求的服务是否属于集群服务,是的话还要能找出正确的真实服务地址,这个kube-proxy如何实现呢
kube-proxy经过监听API server,当有service建立时,经过调用系统的netlink 接口,将service和对应的endpoint插入到IPVS对应的hash表中,对应代码在proxier.go中,用户能够经过ipvsadm工具查看ipvs hash表中的数据
$ ipvsadm --list IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP promote.cache-dns.local:http rr -> master:6443 Masq 1 1 0 TCP promote.cache-dns.local:doma rr -> 172.30.78.2:domain Masq 1 0 0 TCP promote.cache-dns.local:9153 rr -> 172.30.78.2:9153 Masq 1 0 0 TCP promote.cache-dns.local:http rr -> 172.30.22.4:http-alt Masq 1 0 0 -> 172.30.78.4:http-alt Masq 1 0 0 UDP promote.cache-dns.local:doma rr -> 172.30.78.2:domain Masq 1 0 0
由于启用了DNS的cache功能,因此这个地方看到的service信息是DNS缓存信息
经过上述几个步骤后,kube-proxy就把须要使用IPVS的依赖条件都实现,就能够在请求到来时利用IPVS机制对请求进行转发了。