写在前面
对于负载均衡,我们有很多种办法。
负载均衡实际上包含了两个方面:健康探测以及流量分担。如果我们不能健康探测而将流量错误地分到宕机上,会导致流量黑洞。
首先最豪华的解决方案就是硬件负载均衡器,但是一般我们买不起。
其次是路由器 NAT Pool 地址池转换,不过这种不靠谱,因为这种就是简单地将流量分发到服务器上,万一服务器掉线了需要人工去改地址池,也就是缺少健康探测的功能。
我们也可以通过 ipvs 找一台服务器来进行负载均衡,这也是 K8s 集群内部的负载均衡办法,但是这样还是给转发的那台机器带来不必要的负担。
最后一种办法是在路由器上配置等价路由,由路由器进行负载均衡,这样其实和 NAT Pool 缺点是一样的,不过,我们有大杀器——动态路由协议可以帮我们搞定这个问题。
所以我们最后实现了这么一种迂回的办法,就是让机器发布路由信息到路由器上,然后由路由器选择机器进行转发,同时每台机器也及时监控其他机器,一旦发现宕机,则更新路由信息,避免路由器将流量转发到宕机上。
理解 MetalLB 的原理需要对 K8s 控制原理以及计算机网络有着比较深入的理解。但理解了之后,配置的东西其实相当简单。
Load Balancer 原理
如果我们使用 GKE、AWS 等公有云,那么一般来说我们给 Service 的 Type 写成 LoadBalancer,云服务厂商就会分配一个 External IP 给我们的 Service,我们只需要访问这个 External IP,其内部就能自动均衡我们的流量到各个实例上。然而作为穷鬼的我们,没有金钱享受如此高端的服务,开源版 Kubernetes 是不会给我们的 LoadBalancer Service 分配 External IP 的。难道穷鬼就不配用负载均衡了吗!!
幸好,即使没有高端的负载均衡器,我们也有开源社区的支持。metallb 就是一款开源的 K8s 负载均衡控制器,只要装上,我们的 Service 就可以拿到 IP 了。如果你对 K8s 已经比较熟悉,那么就知道,所谓 LoadBalancer Service 无非也是一种资源,只是我们缺少一个合适的控制器来为其分配 IP 而已。那么很显然,我们只要装一个分配器,就能得到一个 IP。但是,这个 IP 从哪来?显然只能我们手动分配一段 IP 池让他随便分了。然而其他电脑怎么知道这个 IP 池就是我们的服务所在?如果你熟悉计算机网络,可能会想到 BGP、OSPF 等路由协议。只要在分配好 IP 之后,把这个 IP 路由信息广播出去,那么其他电脑就能知道这个 IP 在哪里。
最简单的,我们可以在服务器的内部子网里找没用的 IP,然后等其他电脑访问这个 IP 的时候,我想办法回应一个 ARP 包,其他电脑就知道这个 IP 在哪里可以通信了,尽管这个 IP 其实没有绑定到任何网卡上,可能只是 iptables 里的一条记录。
然而,通过 ARP 广播的方式局限性非常大,分配的 IP 只能和服务器其他 IP 位于同一子网,但其实对于我们这种小型集群已经够了,但是这不够 geek!有没有高端一点的办法?
有!那就是 BGP。BGP 的原理比较复杂,简单来说就是运行 BGP 的设备之间可以交换路由信息,我们可以将自己的 IP 段通过 BGP 协议告诉其他设备,这样其他设备就能正确的路由数据包到服务器上了。BGP 需要路由器的支持,好在我们的路由器是支持的。BGP 尽管更复杂了一些,但是在 IP 段的选取上有更大的灵活性。尽管负载均衡器原理十分复杂,配置却很简单。
安装控制器
请根据自己的需要选择对应的版本。
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.3/manifests/metallb.yaml
配置路由器
在配置控制器之前,我们需要配置路由器的 BGP 功能,简单来说就是给路由自己分配一个 AS 号,然后给服务器分配一个 AS 号就可以了,然后声明邻居,使路由器能从服务器获取 BGP 信息。AS 号和 IP 地址一样,也是有私有段的,我们用的就是私有的 AS 号。(注:此处路由器型号为华为 AR-101S,其他路由器请参考配置手册配置 BGP 功能)
路由器的 AS 号是 65315,集群 AS 号是 65199。
[Huawei] bgp 65315 # 打开路由器 BGP 功能,分配 65315 AS 号
[Huawei-bgp] group servers external # 声明外部邻居组
[Huawei-bgp] peer servers as-number 65199 # 声明组 AS 号为 65199
[Huawei-bgp] peer 192.168.1.110 group servers # 声明组内邻居
[Huawei-bgp] peer 192.168.1.111 group servers
[Huawei-bgp] peer 192.168.1.113 group servers
[Huawei-bgp] peer 192.168.1.114 group servers
[Huawei-bgp] peer 192.168.1.115 group servers
[Huawei-bgp] peer 192.168.1.116 group servers
[Huawei-bgp] peer 192.168.1.117 group servers
[Huawei-bgp] peer 192.168.1.118 group servers
[Huawei-bgp] peer 192.168.1.119 group servers
[Huawei-bgp] maximum load-balancing 4 # 默认情况下,路由只会从学习到的 BGP 路由信息中选取最佳的 1 条写进路由表,而只有路由表中同一目的地址含有多个等价下一条才能实现负载均衡。所以,我们要指示 BGP 将多条路由同时写进路由表,我们的路由器型号最大只支持 4
到这里路由器就配置完了。
配置控制器
直接应用下面的 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
peers:
- peer-address: 192.168.1.1 # 路由器的 IP 地址
peer-asn: 65315 # 路由器的 AS 号
my-asn: 65199 # 集群 AS 号
address-pools:
- name: default
protocol: bgp
addresses:
- 192.168.15.0/24 # 希望分配的 IP 地址池,到时候会分配给 Service 的 ExternalIP
检查负载均衡是否生效
首先将一个 Service 改成 LoadBalancer,然后查看是否分配到了 External IP:
$ kubectl get svc traefik-load-balance-service -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik-load-balance-service LoadBalancer 10.97.104.52 192.168.15.1 80:32703/TCP,443:32237/TCP 13h
可以看到确实分配到了一个我们地址池中的 IP。
然后检查路由是否学习到了 BGP 路由表:
<Huawei>display bgp routing-table
BGP Local router ID is 192.168.1.1
Status codes: * - valid, > - best, d - damped,
h - history, i - internal, s - suppressed, S - Stale
Origin : i - IGP, e - EGP, ? - incomplete
Total Number of Routes: 8
Network NextHop MED LocPrf PrefVal Path/Ogn
*> 192.168.15.1/32 192.168.1.110 0 65199?
* 192.168.1.111 0 65199?
* 192.168.1.114 0 65199?
* 192.168.1.115 0 65199?
* 192.168.1.116 0 65199?
* 192.168.1.117 0 65199?
* 192.168.1.118 0 65199?
* 192.168.1.119 0 65199?
可以看到路由器已经收到来自集群的 BGP 信息。
然后检查路由器路由表,是否有多个下一跳:
<Huawei>display ip routing-table
Route Flags: R - relay, D - download to fib
------------------------------------------------------------------------------
Routing Tables: Public
Destinations : 12 Routes : 15
Destination/Mask Proto Pre Cost Flags NextHop Interface
0.0.0.0/0 Static 60 0 D 222.200.180.254 GigabitEthernet0/0/4
127.0.0.0/8 Direct 0 0 D 127.0.0.1 InLoopBack0
127.0.0.1/32 Direct 0 0 D 127.0.0.1 InLoopBack0
127.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0
192.168.1.0/24 Direct 0 0 D 192.168.1.1 Vlanif1
192.168.1.1/32 Direct 0 0 D 127.0.0.1 Vlanif1
192.168.1.255/32 Direct 0 0 D 127.0.0.1 Vlanif1
192.168.15.1/32 EBGP 255 0 D 192.168.1.110 Vlanif1
EBGP 255 0 D 192.168.1.111 Vlanif1
EBGP 255 0 D 192.168.1.114 Vlanif1
EBGP 255 0 D 192.168.1.115 Vlanif1
222.200.180.0/24 Direct 0 0 D 222.200.180.45 GigabitEthernet0/0/4
222.200.180.45/32 Direct 0 0 D 127.0.0.1 GigabitEthernet0/0/4
222.200.180.255/32 Direct 0 0 D 127.0.0.1 GigabitEthernet0/0/4
255.255.255.255/32 Direct 0 0 D 127.0.0.1 InLoopBack0
针对 192.168.15.1,路由表被应用了多个下一跳,因此可以负载均衡。
最后查看 FIB 转发表:
<Huawei>display fib
Route Flags: G - Gateway Route, H - Host Route, U - Up Route
S - Static Route, D - Dynamic Route, B - Black Hole Route
L - Vlink Route
--------------------------------------------------------------------------------
FIB Table:
Total number of Routes : 15
Destination/Mask Nexthop Flag TimeStamp Interface TunnelID
192.168.15.1/32 192.168.1.110 DGHU t[5340582] Vlanif1 0x0
192.168.15.1/32 192.168.1.111 DGHU t[5340582] Vlanif1 0x0
192.168.15.1/32 192.168.1.114 DGHU t[5340582] Vlanif1 0x0
192.168.15.1/32 192.168.1.115 DGHU t[5340582] Vlanif1 0x0
222.200.180.255/32 127.0.0.1 HU t[611645] InLoop0 0x0
222.200.180.45/32 127.0.0.1 HU t[611645] InLoop0 0x0
192.168.1.255/32 127.0.0.1 HU t[201] InLoop0 0x0
192.168.1.1/32 127.0.0.1 HU t[201] InLoop0 0x0
255.255.255.255/32 127.0.0.1 HU t[79] InLoop0 0x0
127.255.255.255/32 127.0.0.1 HU t[79] InLoop0 0x0
127.0.0.1/32 127.0.0.1 HU t[79] InLoop0 0x0
127.0.0.0/8 127.0.0.1 U t[79] InLoop0 0x0
192.168.1.0/24 192.168.1.1 U t[201] Vlanif1 0x0
222.200.180.0/24 222.200.180.45 U t[611645] GE0/0/4 0x0
0.0.0.0/0 222.200.180.254 GSU t[611645] GE0/0/4 0x0
可以看到路由器在路由表的指导下,为 192.168.15.1 分配了多个下一跳。