跳转至

使用说明

IPPool

集群默认地址池

如果创建 LoadBalancer Service 的时候没有通过 Annotations: metallb.universe.tf/address-pool 指定地址池或通过 Annotation: metallb.universe.tf/loadBalancerIPs 指定 IP,那么会从现存的地址池中设置 autoAssign=true 的池中分配地址。

Note

创建的地址池必须跟 Metallb 组件在同一个 namespace,否则 Metallb 无法识别。

指定地址池

创建 LoadBalancer Service 的时候可以通过 Annotations: metallb.universe.tf/address-pool 指定地址池:

apiVersion: v1
kind: Service
metadata:
  name: metallb-ippool3
  labels:
    name: metallb-ippool3
  annotations:
    metallb.universe.tf/address-pool: test-pool  # test-pool 必须和 metallb 组件在同一个 namespace 下
spec:
  type: LoadBalancer
  ...

指定IP地址

创建 LoadBalancer Service 的时候可以通过 Annotations: metallb.universe.tf/loadBalancerIPs 指定 IP:

apiVersion: v1
kind: Service
metadata:
  name: metallb-ippool3
  labels:
    name: metallb-ippool3
  annotations:
   metallb.universe.tf/loadBalancerIPs: 172.16.13.210  # 该 IP 地址必须存在于现存的地址池中
spec:
  type: LoadBalancer
  ...

共享 IP 地址

k8s v1.20 之前,LoadBalancer Service 不支持配置多种协议(v1.24 支持,已成为 Beta 功能),参考 #issue 23880

Metallb 通过创建不同的 Service 并共享 Service IP,间接的支持这个功能。

创建两个 Service:

apiVersion: v1
kind: Service
metadata:
  name: dns-service-tcp
  namespace: default
  annotations:
    metallb.universe.tf/allow-shared-ip: "key-to-share-1.2.3.4"
spec:
  type: LoadBalancer
  loadBalancerIP: 1.2.3.4
  ports:
    - name: dnstcp
      protocol: TCP
      port: 53
      targetPort: 53
  selector:
    app: dns
---
apiVersion: v1
kind: Service
metadata:
  name: dns-service-udp
  namespace: default
  annotations:
    metallb.universe.tf/allow-shared-ip: "key-to-share-1.2.3.4"
spec:
  type: LoadBalancer
  loadBalancerIP: 1.2.3.4
  ports:
    - name: dnsudp
      protocol: UDP
      port: 53
      targetPort: 53
  selector:
    app: dns

Note

只要 Annotations (metallb.universe.tf/allow-shared-ip) key 和 value 相同,那么不同的 LoadBalancer Service就会具有相同的 IP 地址(ipv4/ipv6)。 当然也可以通过 Annotations (metallb.universe.tf/loadBalancerIPs) 指定 ipv4/ipv6 地址,或者通过 .spec.loadBalancerIP 指定(只支持 ipv4)。 创建后编辑 Annotation 没有效果。

共享 IP 另一个作用是 LoadBalancer IP 地址不足,需要多个 Service 共享同一个 IP,但注意不同 Service 的协议和端口应该是不同的,否则无法区分连接。

Metallb L2 模式

L2 模式下,Metallb 将会通过 ARP(for ipv4)、NDP(for ipv6)宣告 LoadBalancerIP 的地址。 在 Metallb < v0.13.2 之前,只能通过 configMap 来配置 Metallb。 在 v0.13.2 之后,通过 CRD 资源的方式配置 Metallb,另外 configMap 的方式已被弃用。

Layer2 模式下,当创建服务时,Metallbspeaker 组件)会为这个服务选举出集群中某个节点,作为这个服务对外暴露的主机。 当对 Service 的 externalIP 发出请求时,此节点会代替这个 externalIP 回复 arp 请求。 所以对 Service 发出的请求,会首先到达集群中这个节点,然后再经过这个节点上的 kube-proxy 组件,最后将流量导向这个 service 某个具体的端点 (endpoint)。

服务选举节点的逻辑主要有三点:

  1. 首先过滤掉未 ready 的节点以及 endpoint 未 ready 所在的节点
  2. 如果该服务的 endpoint 分布在同一个节点,那么筛选此节点作为该服务 IP 的 arp 响应者
  3. 如果该服务的 endpoint 分布在不同的节点,那么通过 sha256 计算 节点 + # + externalIP 后,按照字典顺序取第一个

这样,Metallb 就会为每个 Service 选择一个节点作为暴露的主机。 metallb 会将这单个 Service 的流量,全部导向某个节点,所以这个节点可能会成为限制性能的瓶颈。 Service 的带宽限制也会取决于单个节点的带宽,这也是使用 ARP 或 NDP 最主要的限制。

此外,当此节点发生故障时,Metallb 需要为服务重新选择一个新的节点。 然后 Metallb 会给客户端发送一个"免费"的 arp,告知客户端需要更新他们的 Mac 地址缓存。 在客户端更新缓存前,流量仍会转发到故障节点。因此从某种程度来看:故障转移的时间,依赖于客户端更新 Mac 地址缓存的速度。

使用

  • 创建 IP 池

    apiVersion: metallb.io/v1beta1
    kind: IPAddressPool
    metadata:
      name: demo-pool
      namespace: metallb-system
      labels:
        ipaddresspool: demo
    spec:
      addresses:
      - 192.168.10.0/24
      - 192.168.9.1-192.168.9.5
      - fc00:f853:0ccd:e799::/124
      autoAssign: true
      avoidBuggyIPs: false
    
  • addresses:IP 地址列表,每一个列表成员可以是一个 CIDR,可以是一个地址范围(如 192.168.9.1 - 192.168.9.5),也可以是不同 ipFamilyMetallb 会从其中分配 IP 给 LoadBalancer 服务

  • autoAssign:是否自动分配 IP 地址,默认为 true。在某些情况下(IP 地址不足或公有 IP),不希望池中的 IP 被轻易地分配,可设置为 false。 可以通过在 service 中设置 annotations: metallb.universe.tf/address-pool: pool-name。或者在 spec.LoadBalancerIP 字段设置 IP(注意这种方式已被k8s标记为遗弃)。

  • avoidBuggyIPs:是否避免使用池中以 .0.255 地址,默认为 false。

  • 配置 LoadBalancerIP 通告规则 (L2)

    通过 L2Advertisement 绑定地址池,这样告诉 Metallb 这些地址应该由 ARPNDP 通告出去。

    apiVersion: metallb.io/v1beta1
    kind: L2Advertisement
    metadata:
      name: demo
      namespace: metallb-system  
    spec:
      ipAddressPools:
      - demo-pool
      ipAddressPoolSelectors:
      - matchLabels:
          ipaddresspool: demo
      nodeSelectors:
      - matchLabels:
          kubernetes.io/hostname: kind-control-plane
    
  • ipAddressPools:可选,通过 name 筛选地址池,如 ipAddressPoolsipAddressPoolSelectors 同时未指定,则作用于所有地址池。

  • ipAddressPoolSelectors:可选,通过 labels 筛选地址池,如 ipAddressPoolsipAddressPoolSelectors 同时未指定,则作用于所有地址池。

  • nodeSelectors:可选,用于筛选哪些节点作为 loadBalancerIP 的下一跳,默认所有节点。

  • 创建LoadBalancer Service

    apiVersion: v1
    kind: Service
    metadata:
      name: metallb1-cluster
      labels:
        name: metallb
          #annotations:
          #metallb.universe.tf/address-pool: lan
    spec:
      type: LoadBalancer
      allocateLoadBalancerNodePorts: false
      ports:
      - port: 18081
        targetPort: 8080
        protocol: TCP
      selector:
        app: metallb-cluster
    

    只需要指定 spec.type=LoadBalancer,这样 Metallb 就会自然接管此 Service 的生命周期。

    Note

    如果想让 Service 从指定的地址池中分配地址,通过 annotations: metallb.universe.tf/address-pool: <pool-name> 指定。或者通过 service.spec.loadBalancerIP 字段指定 IP(需要保证存在于一个池中,不推荐这种方式)。 如果存在多种负载均衡器,可通过 service.spec.loadBalancerClass 字段指定。在部署 Metallb 时,可通过 --lb-class flag 进行配置。

负载均衡性

  • Service.spec.externalTrafficPolicy=cluster

    这种模式下,具有良好的负载均衡性,但流量可能经历多跳,这会隐藏客户端源 IP。

                                      ______________________________________________________________________________
                                    |                       -> kube-proxy(SNAT) -> pod A                          |
                                    |                      |                                                      |
    client -> loadBalancerIP:port -> | -> node A(Leader) ->                                                       |
                                    |                      |                                                      |
                                    |                       -> kube-proxy(SNAT) -> node B -> kube-proxy -> pod B  |
                                      ------------------------------------------------------------------------------
    
  • Service.spec.externalTrafficPolicy=local

    这种模式下,会保留客户端源IP,但负载均衡性较差,流量会一直到到某一个后段Pod.

                                      __________________________________________________________________________________________
                                    |                       -> kube-proxy -> pod A (后段Pod在本节点)                            |
                                    |                      |                                                                  |
    client -> loadBalancerIP:port -> | -> node A(Leader) ->                                                                   |
                                    |                      |                                                                  |
                                    |                       -> kube-proxy -> node B -> kube-proxy -> pod B (后端Pod在不同节点)  |
                                      ------------------------------------------------------------------------——————————————————
    

Metallb BGP Mode(L3)

Layer2 模式局限在一个二层网络中,流向 Service 的流量都会先转发到某一个特定的节点,这并不算真正意义上的负载均衡。 BGP 模式不局限于一个二层网络,集群中每个节点都会跟 BGP Router 建立 BGP 会话,宣告 Service 的 ExternalIP 的下一跳为集群节点本身。 这样外部流量就可以通过 BGP Router 接入到集群内部,BGP Router 每次接收到目的是 LoadBalancer IP 地址的新流量时,它都会创建一个到节点的新连接。 但选择哪一个节点,每个路由器厂商都有一个特定的算法来实现。所以从这个角度来看,这具有良好的负载均衡性。

使用

  • 创建 ippool

    apiVersion: metallb.io/v1beta1
    kind: IPAddressPool
    metadata:
      name: bgp-pool
      namespace: metallb-system
      labels:
        ipaddresspool: demo
    spec:
      addresses:
      - 192.168.10.0/24
      autoAssign: true
      avoidBuggyIPs: false
    
  • 配置 LoadBalancerIP 通告规则 (L3)

    Note

    BGP 模式需要硬件支持运行 BGP 协议。若无,可使用如 frrbird 等软件代替。

    推荐使用 frr 进行安装:

    # ubuntu
    apt install frr
    # centos
    yum install frr
    

    frr 配置 BGP

    router bgp 7675  # bgp as number
    bgp router-id 172.16.1.1  # route-id 常常是接口IP
    no bgp ebgp-requires-policy # 关闭 ebpf filter !!!
    neighbor 172.16.1.11 remote-as 7776  # 配置 ebgp -> neighbor 1, 172.16.1.11 为集群一节点
    neighbor 172.16.1.11 description master1 # description
    neighbor 172.16.2.21 remote-as 7776  # 节点 2
    neighbor 172.16.2.21 description woker1 
    

    Metallb 配置:

  • 配置 BGPAdvertisement

    此 CRD 主要用于指定需要通过 BGP 宣告的地址池,同 L2 模式,可通过池名称或者 labelSelector筛选。同时可配置BGP一些属性:

    apiVersion: metallb.io/v1beta1
    kind: BGPAdvertisement
    metadata:
      name: local
      namespace: metallb-system
    spec:
      ipAddressPools:
      - bgp-pool
      aggregationLength: 32
    
    • aggregationLength:路由后缀聚合长度,默认为 32,意味这 BGP 通告的路由的掩码为 32,值调小可聚合路由条数
    • aggregationLengthV6:同上,用于 ipv6,默认为 128
    • ipAddressPools:[]string,选择需要 BGP 通告的地址池
    • ipAddressPoolSelectors:通过 label 筛选地址池
    • nodeSelectors:通过 node label 筛选 loadBalancerIP 的下一跳节点,默认为全部节点
    • peers:[]string,BGPPeer 对象的名称,用于声明此 BGPAdvertisement 作用于哪些 BGP 会话
    • communities:参考 BGP communities,可以直接配置,也可以指定 communities CRD 的名称
  • 配置 BGP Peer

    BGP Peer 用于配置 BGP 会话的配置,包括对端 BGP AS 及 IP 等。

    apiVersion: metallb.io/v1beta2
    kind: BGPPeer
    metadata:
      name: test
      namespace: metallb-system
    spec:
      myASN: 7776
      peerASN: 7675
      peerAddress: 172.16.1.1
      routerID: 172.16.1.11
    
    • myASN:本端 ASN,范围为 1-64511(public AS)64512-65535(private AS)
    • peerASN:对端 ASN,范围同上。如果二者相等,则为 iBGP;否则为 eBGP
    • peerAddress:对端路由器 IP 地址
    • sourceAddress:指定本段建立 BGP 会话的地址,默认从本节点网卡自动选择
    • nodeSelectors:根据 node label 指定哪些节点需要跟 BGP Router 建立会话
  • 创建 LoadBalancer 类型的 Service

    $ kubectl get svc | grep LoadBalancer
    metallb-demo   LoadBalancer   172.31.63.207   10.254.254.1   18081:30531/TCP   3h38m
    

验证

在 BGP Router 上可以看到通过 BGP 学习到的路由:

$ vtysh

Hello, this is FRRouting (version 8.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.

router# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

K>* 0.0.0.0/0 [0/100] via 10.0.2.2, eth0, src 10.0.2.15, 03:52:17
C>* 10.0.2.0/24 [0/100] is directly connected, eth0, 03:52:17
K>* 10.0.2.2/32 [0/100] is directly connected, eth0, 03:52:17
B>* 10.254.254.1/32 [20/0] via 172.16.1.11, eth1, weight 1, 03:32:16
  *                        via 172.16.2.21, eth2, weight 1, 03:32:16
C>* 172.16.1.0/24 is directly connected, eth1, 03:52:17

可以看到通往 LoadBalancerIP 的下一跳分别是集群节点 1 和节点 2,在 BGP Router 执行连通性测试:

root@router:~# curl 10.254.254.1:18081
{"pod_name":"metallb-demo","pod_ip":"172.20.166.20","host_name":"worker1","client_ip":"172.20.161.0"}

FRR Mode

目前 Metallb BGP模式有两种 Backend 实现:Native BGPFRR BGP

FRR BGP 目前是实验阶段,对比 Native BGPFRR BGP 主要有以下几个优点:

  • BFD 协议支持(提高故障反应能力,缩短故障时间)
  • 支持 IPV6 BGP
  • 支持 ECMP

评论