之前的文章<<Kubernetes Service网络通信路径>>介绍了kubernetes
的几种Service
。如果要暴露服务给kubernetes
集群外使用,可以选择NodePort
和LoadBalancer
。但LoadBalancer
现在主要在各大公有云厂商上能够原生支持。而使用NodePort
暴露服务,将使用一个非常大的端口,无法使用原始的端口号来暴露服务,比如mysql
的3306
端口。
Service
的官方文档中介绍了一种辅助方式, 叫External-IP
, 可以在worker
节点上会通过该IP
来暴露服务,而且可以使用在任意类型的service
上。集群外的用户就可以通过该IP
来访问服务。但如果这个IP
只存在于一个worker
节点上,那么就不具备高可用的能力了,我们需要在多个worker
节点上配置这个VIP:Virtual IP
。我们可以使用LVS
(也叫做IPVS
)的DR(Director Routing)
模式作为外部负载均衡器将流量分发到多个worker
节点上,同时保持数据包的目的地址为该VIP
。
DR
模式只会修改数据包的目的MAC
地址为后端RealServer
的MAC
地址,因而要求负载均衡器Director
和RealServer
在同一个二层网络,而且响应包不会经过Director
。
下面我们来实验如何使用LVS
的DR
模式实现service
负载均衡。
我们在之前的实验集群中创建一个类型为ClusterIP
(默认类型)的service
, 指定一个外部IP
:
1 | apiVersion: v1 |
创建服务:
1 | kubectl apply -f whoami.yaml |
查看服务,可以看到whoami
的EXTERNAL-IP
为10.240.0.201
:
1 | [root@master1 ~]# kubectl get svc -o wide |
在worker
节点上检查iptables
规则,可以看到在KUBE-SERVICES
链中添加了EXTERNAL-IP
相关的规则:
1 | -A KUBE-SERVICES ! -s 10.230.0.0/16 -d 10.32.0.60/32 -p tcp -m comment --comment "default/whoami:web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ |
当数据包目的地址为10.240.0.201:80
时,跳转到KUBE-SVC-*
链,从而分发到相应的pod
中。
我们在节点上添加上这个VIP
:
1 | [root@node1 ~]# ip addr add 10.240.0.201/32 dev lo |
因为这个VIP
需要在多个worker
节点上存在,因而把它配置在lo
上,并抑制相应网卡上对该VIP
的ARP
响应:
1 | sysctl -w net.ipv4.conf.eth1.arp_ignore = 1 |
在节点上尝试访问VIP
, 可以成功访问:
1 | [root@node1 ~]# curl http://10.240.0.201 |
接着我们在worker
节点所在二层网络再启动一台虚拟机作为LVS
的Director
。在该机器上给与worker
节点二层互通的网卡添加VIP
:
1 | ip addr add 10.240.0.201/32 dev eth1 |
使用ipvsadm
创建负载均衡服务, 并使用DR
模式添加两个worker
节点做为后端的RealServer
:
1 | ipvsadm -A -t 10.240.0.201:80 -s rr |
查看负载均衡服务:
1 | [root@lb1 ~]# ipvsadm -L -n |
环境配置完成。我们找一台客户端访问VIP:10.240.0.201
, 同时在Director
机器上抓包,可以看到:
1 | [root@lb1 ~]# tcpdump -ieth1 -nn -e tcp port 80 |
数据包的目的MAC
地址被修改为node2
上eth1
的MAC
地址, 而且响应包并不经过Director
:
1 | [root@node2 ~]# ip link show dev eth1 |
根据之前网上的这篇文章, worker
节点可以不设置VIP
,因为VIP
并不需要由用户态程序来接收流量,而是直接由iptables
来进行数据包转换。
在大多数场景下这是正确的。但是如果需要直接从worker
节点上通过VIP
访问该服务时就需要在worker
节点上配置VIP
了。数据包在从worker
节点发送出去时,会经由nat:OUTPUT
和nat:POSTROUTING
来处理。iptables
的NAT
实现是基于conntrack
来实现的,发出时系统会建立conntrack
条目。iptables
的nat:PREROUTING
和nat:POSTROUTING
的处理都会调用nf_nat_ipv4_fn
函数。当数据包由LVS Director
把数据包返回到自身这台RealServer
时, nat:PREROUTING
阶段调用nf_nat_ipv4_fn
:
1 | switch (ctinfo) { |
这时nf_nat_initialized
会返回0
, 因而跳过do_chain
的调用,也就不再会执行nat:PREROUTING
所设置的链和规则,放行通过进入到路由决策阶段。但由于数据包的源IP
是本机地址,默认情况下Linux路由实现不允许非loopback
设备之外的设备所进入的数据包源地址为本机地址, 因而该数据包会被丢弃。
但内核提供了参数accept_local
可以允许这种包通过:
1 | accept_local - BOOLEAN |
修改相应参数放行数据包:
1 | sysctl -w net.ipv4.conf.eth1.rp_filter=1 |
再次从worker
节点访问VIP
, 同时开启tcpdump
抓包分析:
1 | [root@node1 ~]# curl http://10.240.0.201 |
可以看到返回路由错误,而从抓包结果看,我们放行数据包后,根据目的地址,数据包会再被发送出去,从而形成环路。直到IP
包的ttl
减为0
返回了路由错误。
1 | [root@node1 ~]# tcpdump -ieth1 tcp port 80 -nn -e -v |
本文只是简单实验可行性。如果用于生产环境,需要额外的方案考虑,比如:
LVS
本身可以配合keepalived
使用主备模式保证Director
的HA- 使用
OSPF
的ECMP
来配置多主的Director
集群(可以参考之前的文章<<基于Cumulus VX实验ECMP+OSPF负载均衡>>) - 省略
LVS
的Director
层,直接使用OSPF
的ECMP
将流量分发到worker
节点的VIP
参考: