MetalLB介绍

说说我的homelab里,伊布介绍了家里的homelab,其中包括了kubernetes集群的信息。这里面伊布比较不满意的是,kubernetes集群没有L4负载均衡,对外暴漏服务时,只能使用nodePort的方式,比较麻烦:必须要记住不同的端口号。

前两天去上海kubeCon的时候,看到有人介绍他的树莓派集群,其中L4负载均衡使用的是MetalLB,瞬间眼前一亮,这不就是伊布一直想要的东西嘛!

MetalLB可谓是“穷人的LoadBalancer”。kubernetes本身并没有实现LoadBalancer;如果是云上用户,可以使用云服务商提供的provider;而对于bare metal的用户来说,则可以使用MetalLB来达到相同的目的。

MetalLB的两个核心特性:

地址分配

MetalLB会为用户的load balancer类型service分配IP地址,该IP地址不是凭空产生的,需要用户预先分配。

外部声明

地址分配后还需要通知到网络中的其他主机。MetalLB支持两种声明模式:

  • Layer 2模式:ARP/NDP
  • BGP模式

Layer 2 模式

Layer 2模式下,每个service会有集群中的一个node来负责。当服务客户端发起ARP解析的时候,对应的node会响应该ARP请求,之后,该service的流量都会指向该node(看上去该node上有多个地址)。

Layer 2模式并不是真正的负载均衡,因为流量都会先经过1个node后,再通过kube-proxy转给多个end points。如果该node故障,MetalLB会迁移 IP到另一个node,并重新发送免费ARP告知客户端迁移。现代操作系统基本都能正确处理免费ARP,因此failover不会产生太大问题。

Layer 2模式更为通用,不需要用户有额外的设备;但由于Layer 2模式使用ARP/ND,地址池分配需要跟客户端在同一子网,地址分配略为繁琐。

BGP模式

BGP模式下,集群中所有node都会跟上联路由器建立BGP连接,并且会告知路由器应该如何转发service的流量。

BGP模式是真正的LoadBalancer。

MetalLB部署

安装MetalLB比较简单。

kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml

编排文件主要部署2个组件:

  • metallb-system/controller,负责IP地址的分配,以及service和endpoint的监听
  • metallb-system/speaker,负责保证service地址可达,例如Layer 2模式下,speaker会负责ARP请求应答。

注意,部署后,还需要根据具体的地址通告方式,配置configmap metallb-system/config。controller会读取该configmap,并reload配置。

下面分别介绍两种模式的部署。

Layer 2模式部署

如下是Layer 2模式加的config,在伊布的环境下,为service分配的地址池是192.168.0.10-192.168.0.100

将下面的编排文件使用kubectl apply到集群上去。

kind: ConfigMap
apiVersion: v1
metadata:
  name: config
  namespace: metallb-system
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.0.10-192.168.0.100

至此,MetalLB Layer 2 mode的配置就结束了。来验证下。

$ kubectl get pods nginx-68995d8957-bczhf -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE       
nginx-68995d8957-bczhf   2/2     Running   0          19d   10.244.0.78   ubuntu-1  
$ kubectl get svc
NAME    TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
nginx   LoadBalancer   10.97.187.100   192.168.0.10   80:32353/TCP   179m

可以看到,svc nginx分配的的外部IP地址是192.168.0.10,这也是伊布的地址池中的第一个IP。浏览器访问192.168.0.10,可以正常显示nginx的页面。

MetalLB是怎么做的呢?

先在客户端上看看192.168.0.10的ARP地址。

❯❯❯ arp 192.168.0.10
? (192.168.0.10) at 0:c:29:f3:b2:1d on en0 ifscope [ethernet]
❯❯❯ arp ubuntu-1
ubuntu-1.lan (192.168.0.154) at 0:c:29:f3:b2:1d on en0 ifscope [ethernet]

有点意思。192.168.0.10的mac地址,和ubuntu-1的地址是一样的。显然,因为nginx pod是运行在ubuntu-1上的,因此当客户端发起ARP请求的时候,ubuntu-1上的speaker会应答该请求,将流量导向ubuntu-1。

去看ubuntu-1的speaker的日志,可以看到一些记录。

{"caller":"arp.go:102","interface":"ens160","ip":"192.168.0.10","msg":"got ARP request for service IP, sending response","responseMAC":"00:0c:29:f3:b2:1d","senderIP":"192.168.0.234","senderMAC":"00:22:6d:57:a2:1c","ts":"2019-06-30T04:31:29.524752775Z"}

那么报文到了ubuntu-1之后呢?我们来看看iptables规则。

-A KUBE-SERVICES -d 192.168.0.10/32 -p tcp -m comment --comment "default/nginx: loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-4N57TFCL4MD7ZTDA

-A KUBE-FW-4N57TFCL4MD7ZTDA -m comment --comment "default/nginx: loadbalancer IP" -j KUBE-MARK-MASQ
-A KUBE-FW-4N57TFCL4MD7ZTDA -m comment --comment "default/nginx: loadbalancer IP" -j KUBE-SVC-4N57TFCL4MD7ZTDA

-A KUBE-SVC-4N57TFCL4MD7ZTDA -j KUBE-SEP-HQ2WPKJSGAOND4TJ

-A KUBE-SEP-HQ2WPKJSGAOND4TJ -s 10.244.0.78/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-HQ2WPKJSGAOND4TJ -p tcp -m tcp -j DNAT --to-destination 10.244.0.78:80

是不是似曾相识?对,这些规则,和nodePort类型的service的iptables规则,是类似的。

至此,Layer 2模式的MetalLB就可以正常工作了。

BGP模式部署

Layer 2模式通用,不过作为geeker,不应该满足于毕竟,将系统的可靠性依赖于免费ARP,不是一种正经的做法。

BGP模式部署,对环境有一定的要求:

得有一个可以运行BGP协议的上联交换机。

在伊布的homelab中,kubernetes的node是hp gen8上运行的虚拟机,从逻辑上来看,可以认为,各个vm是连接到WNDR4300的。因此,只要WNDR4300上可以运行bgp协议,就可以支持BGP模式部署。

伊布的WNDR4300上运行的是openwrt,这是一个完全自主可控的路由器操作系统,生态比较完善,十分推荐。

openwrt上可以运行quaggaquagga是一个unix平台下的经典路由软件套件,实现了以下路由协议:OSPFv2, OSPFv3, RIP v1 and v2, RIPng and BGP-4。在这里,伊布将使用quagga的bgp功能,来完成MetalLB的BGP模式部署。

当然,如果您不想使用openwrt,也可以使用商业路由器,如UBNT ER系列,H3C/HW等厂家的路由器。

openwrt安装quagga

openwrt使用opkg来管理软件包,默认的源在国内使用比较慢,您可以使用ustc的镜像源。

修改/etc/opkg/distfeeds.conf

src/gz openwrt_core http://mirrors.ustc.edu.cn/lede/releases/18.06.2/targets/ar71xx/nand/packages
src/gz openwrt_base http://mirrors.ustc.edu.cn/lede/releases/18.06.2/packages/mips_24kc/base
src/gz openwrt_luci http://mirrors.ustc.edu.cn/lede/releases/18.06.2/packages/mips_24kc/luci
src/gz openwrt_packages http://mirrors.ustc.edu.cn/lede/releases/18.06.2/packages/mips_24kc/packages
src/gz openwrt_routing http://mirrors.ustc.edu.cn/lede/releases/18.06.2/packages/mips_24kc/routing
src/gz openwrt_telephony http://mirrors.ustc.edu.cn/lede/releases/18.06.2/packages/mips_24kc/telephony

需要安装 quagga、quagga-zebra、quagga-bgpd,以及用于管理的quagga-watchquagga、quagga-vtysh。

root@OpenWrt:~# opkg install quagga quagga-zebra quagga-bgpd quagga-watchquagga quagga-vtysh

安装好以后,启动quagga,启动后会监听2601、2605、179端口号。

root@OpenWrt:~# /etc/init.d/quagga start
root@OpenWrt:~# netstat -antp|grep LISTEN
tcp        0      0 0.0.0.0:2601            0.0.0.0:*               LISTEN      23173/zebra
tcp        0      0 0.0.0.0:2605            0.0.0.0:*               LISTEN      23178/bgpd
tcp        0      0 0.0.0.0:179             0.0.0.0:*               LISTEN      23178/bgpd

openwrt配置BGP协议

从拓扑上来说,WNDR4300会跟kubernetes上的各个node建立BGP peer对,在这里伊布设置WNDR4300的AS为65000,kubernetes上各个node的AS为65009。

ssh登录到OpenWrt后,使用vtysh命令进入Quagga的命令行。配置如下。

OpenWrt# configure terminal
OpenWrt(config)# router bgp 65000
OpenWrt(config-router)# neighbor 192.168.0.154 remote-as 65009
OpenWrt(config-router)# neighbor 192.168.0.154 description "ubuntu-1"
OpenWrt(config-router)# neighbor 192.168.0.153 remote-as 65009
OpenWrt(config-router)# neighbor 192.168.0.153 description "ubuntu-2"
OpenWrt(config-router)# neighbor 192.168.0.171 remote-as 65009
OpenWrt(config-router)# neighbor 192.168.0.171 description "ubuntu-3"
OpenWrt(config-router)# neighbor 192.168.0.163 remote-as 65009
OpenWrt(config-router)# neighbor 192.168.0.163 description "x1"
OpenWrt(config-router)# neighbor 192.168.0.127 remote-as 65009
OpenWrt(config-router)# neighbor 192.168.0.127 description "x201"

此时,只是配置了WNDR4300的BGP,还需要配置kubernetes上各个node的路由信息。

kubernetes上配置BGP协议

前面伊布已经安装了MetalLB的controller和speaker,只是使用的是Layer 2模式。要改为BGP模式,只要修改configmap config就可以了。

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    peers:
    - peer-address: 192.168.0.1
      peer-asn: 65000
      my-asn: 65009
    address-pools:
    - name: default
      protocol: bgp
      addresses:
      #- 192.168.0.10-192.168.0.100
      - 192.168.9.0/24

需要注意的是,伊布重新分配了一个新的网段192.168.9.0/24,而没有使用Layer 2模式的192.168.0.10-192.168.0.100。伊布理解这是BGP模式的一个优势,完全解决了Layer 2模式ARP造成的依赖,可以使用更多更灵活的网段。

地址池修改后,svc的地址也必然需要发生变化,不过伊布在实践中发现,controller此时并不会重新分配地址,而只是报了错误,提示用户当前svc的地址不在地址池中。anyway,只要重启controller,就可以重新分配地址了。

看看当前的BGP状态。

OpenWrt# show ip bgp summary
BGP router identifier 192.168.1.4, local AS number 65000
RIB entries 3, using 216 bytes of memory
Peers 5, using 24 KiB of memory

Neighbor        V         AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.168.0.127   4 65009     651     654        0    0    0 00:25:58        2
192.168.0.153   4 65009    1111    1139        0    0    0 00:26:19        2
192.168.0.154   4 65009     652     664        0    0    0 00:26:17        2
192.168.0.163   4 65009     632     647        0    0    0 00:26:19        2
192.168.0.171   4 65009     648     670        0    0    0 00:26:19        2

可以看到,WNDR4300和kubernetes上的各个node建立了BGP邻居;查看speaker的日志,会看到如下的日志,表示BGP会话已经established了。

struct { Version uint8; ASN16 uint16; HoldTime uint16; RouterID uint32; OptsLen uint8 }{Version:0x4, ASN16:0xfde8, HoldTime:0xb4, RouterID:0xc0a80104, OptsLen:0x1e}
{"caller":"bgp.go:63","event":"sessionUp","localASN":65009,"msg":"BGP session established","peer":"192.168.0.1:179","peerASN":65000,"ts":"2019-06-30T12:54:07.707648249Z"}

controller重启后,为前面svc nginx重新分配的地址为192.168.9.0。来看看WNDR4300上的路由表项。

OpenWrt# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, P - PIM, A - Babel,
       > - selected route, * - FIB route

K>* 0.0.0.0/0 via 192.168.1.1, eth0.2, src 192.168.1.4
K>* 119.28.4.177/32 via 192.168.1.1, eth0.2
C>* 127.0.0.0/8 is directly connected, lo
C>* 192.0.2.0/24 is directly connected, wg0
C>* 192.168.0.0/24 is directly connected, br-lan
C>* 192.168.1.0/24 is directly connected, eth0.2
B>* 192.168.9.0/32 [20/0] via 192.168.0.153, br-lan, 00:36:46
B>* 192.168.9.1/32 [20/0] via 192.168.0.153, br-lan, 00:36:46

可以看到,最后两条为BGP路由,会将192.168.9.0的报文转发到192.168.0.153。此时在客户端浏览器上访问192.168.9.0,会显示nginx的页面(客户端的默认路由为WNDR4300的IP地址 192.168.0.1)。

BGP模式解析

下面来解析下BGP模式。

由于客户端的默认路由是WNDR4300的IP 192.168.0.1,因此浏览器访问192.168.9.0时,报文会发送给WNDR4300。

在WNDR4300上查BGP路由,Next Hop命中192.168.0.153,WNDR4300将报文转发给192.168.0.153

报文到192.168.0.153后,会命中iptables规则,将报文转发给实际Pod的EndPoint,之后的流程就跟一般访问svc类似了。

-A KUBE-SERVICES -d 192.168.9.0/32 -p tcp -m comment --comment "default/nginx: loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-4N57TFCL4MD7ZTDA

可见,报文经路由器转发后,实际仍然是由kubernetes上的一个node进行集群内部的转发的,这一点与Layer 2模式类似。

failover

那么,如果192.168.0.153故障了呢?来详细查看下WNDR4300上的bgp路由。

OpenWrt# show ip bgp
BGP table version is 0, local router ID is 192.168.1.4
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
              i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*  192.168.9.0/32   192.168.0.127                          0 65009 ?
*                   192.168.0.154                          0 65009 ?
*>                  192.168.0.153                          0 65009 ?
*                   192.168.0.163                          0 65009 ?
*                   192.168.0.171                          0 65009 ?
*  192.168.9.1/32   192.168.0.127                          0 65009 ?
*                   192.168.0.154                          0 65009 ?
*>                  192.168.0.153                          0 65009 ?
*                   192.168.0.163                          0 65009 ?
*                   192.168.0.171                          0 65009 ?

可见,实际Next Hop包含了kubernetes所有的node,但当前只使用了一个(即>指向的条目);当192.168.0.153故障时,BGP会快速切换到另一个Hop,完成故障转移。

总结

本文介绍了MetalLB,以及MetalLB的两种部署模式:Layer 2模式和BGP模式。在实际应用中,如果条件满足,推荐使用BGP模式。

Ref: