雲原生負載均衡器之 OpenELB 使用指南

本文主要在 k8s 原生集羣上部署 v0.4.4 版本的 OpenELB 作爲 k8s 的 LoadBalancer,主要涉及 OpenELBLayer2 模式BGP 模式兩種部署方案。由於 BGP 的相關原理和配置比較複雜,這裏僅涉及簡單的 BGP 配置。

文中使用的 k8s 集羣是在 CentOS7 系統上基於 dockercalico 組件部署 v1.23.6 版本,此前寫的一些關於 k8s 基礎知識和集羣搭建的一些方案 [1],有需要的同學可以看一下。

工作原理

簡介

OpenELB 是一個開源的雲原生負載均衡器實現,可以在基於裸金屬服務器、邊緣以及虛擬化的 Kubernetes 環境中使用 LoadBalancer 類型的 Service 對外暴露服務。OpenELB 項目最初由 KubeSphere 社區 [2] 發起,目前已作爲 CNCF 沙箱項目 [3] 加入 CNCF 基金會,由 OpenELB 開源社區維護與支持。

與 MetalLB 類似,OpenELB 也擁有兩種主要工作模式:Layer2 模式和 BGP 模式。OpenELB 的 BGP 模式目前暫不支持 IPv6。

無論是 Layer2 模式還是 BGP 模式,核心思路都是通過某種方式將特定 VIP 的流量引到 k8s 集羣中,然後再通過 kube-proxy 將流量轉發到後面的特定服務。

Layer2 模式

Layer2 模式需要我們的 k8s 集羣基礎環境支持發送 anonymous ARP/NDP packets。因爲 OpenELB 是針對裸金屬服務器設計的,因此如果是在雲環境中部署,需要注意是否滿足條件。

下圖來自 OpenELB 官方 [4],這裏簡單闡述一下 Layer2 模式的工作原理:

主要的工作流程就如同上面描述的一般,但是還有幾個需要額外注意的點:

BGP 模式

OpenELB 的 BGP 模式使用的是 gobgp[5] 實現的 BGP 協議,通過使用 BGP 協議和路由器建立 BGP 連接並實現 ECMP 負載均衡,從而實現高可用的 LoadBalancer。

我們還是借用官網的圖 [6] 來解釋一下這個流程,注意 BGP 模式暫不支持 IPv6。

Layer2 Mode

配置 ARP 參數

部署 Layer2 模式需要把 k8s 集羣中的 ipvs 配置打開 strictARP[7],開啓之後 k8s 集羣中的 kube-proxy 會停止響應 kube-ipvs0 網卡之外的其他網卡的 arp 請求,而由 MetalLB 接手處理。

strict ARP 開啓之後相當於把 將 arp_ignore 設置爲 1 並將 arp_announce 設置爲 2 啓用嚴格的 ARP,這個原理和 LVS 中的 DR 模式對 RS 的配置一樣,可以參考之前的文章中的解釋 [8]。

strict ARP configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface

# 查看kube-proxy中的strictARP配置
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP
      strictARP: false

# 手動修改strictARP配置爲true
$ kubectl edit configmap -n kube-system kube-proxy
configmap/kube-proxy edited

# 使用命令直接修改並對比不同
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl diff -f - -n kube-system

# 確認無誤後使用命令直接修改並生效
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl apply -f - -n kube-system

# 重啓kube-proxy確保配置生效
$ kubectl rollout restart ds kube-proxy -n kube-system

# 確認配置生效
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARP
      strictARP: true

部署 openelb

這裏我們還是使用 yaml 進行部署,官方把所有部署的資源整合到了一個文件中,我們還是老規矩先下載到本地再進行部署

$ wget https://raw.githubusercontent.com/openelb/openelb/master/deploy/openelb.yaml

$ kubectl apply -f openelb.yaml
namespace/openelb-system created
customresourcedefinition.apiextensions.k8s.io/bgpconfs.network.kubesphere.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.network.kubesphere.io created
customresourcedefinition.apiextensions.k8s.io/eips.network.kubesphere.io created
serviceaccount/kube-keepalived-vip created
serviceaccount/openelb-admission created
role.rbac.authorization.k8s.io/leader-election-role created
role.rbac.authorization.k8s.io/openelb-admission created
clusterrole.rbac.authorization.k8s.io/kube-keepalived-vip created
clusterrole.rbac.authorization.k8s.io/openelb-admission created
clusterrole.rbac.authorization.k8s.io/openelb-manager-role created
rolebinding.rbac.authorization.k8s.io/leader-election-rolebinding created
rolebinding.rbac.authorization.k8s.io/openelb-admission created
clusterrolebinding.rbac.authorization.k8s.io/kube-keepalived-vip created
clusterrolebinding.rbac.authorization.k8s.io/openelb-admission created
clusterrolebinding.rbac.authorization.k8s.io/openelb-manager-rolebinding created
service/openelb-admission created
deployment.apps/openelb-manager created
job.batch/openelb-admission-create created
job.batch/openelb-admission-patch created
mutatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created
validatingwebhookconfiguration.admissionregistration.k8s.io/openelb-admission created

接下來我們看看都部署了什麼 CRD 資源,這幾個 CRD 資源主要就是方便我們管理 openelb,這也是 OpenELB 相對 MetalLB 的優勢。

$ kubectl get crd
NAME                             CREATED AT
bgpconfs.network.kubesphere.io   2022-05-19T06:37:19Z
bgppeers.network.kubesphere.io   2022-05-19T06:37:19Z
eips.network.kubesphere.io       2022-05-19T06:37:19Z

$ kubectl get ns openelb-system -o wide
NAME             STATUS   AGE
openelb-system   Active   2m27s

實際上主要工作的負載就是這兩個 jobs.batch 和這一個 deployment

$ kubectl get pods -n openelb-system
NAME                               READY   STATUS      RESTARTS   AGE
openelb-admission-create-57tzm     0/1     Completed   0          5m11s
openelb-admission-patch-j5pl4      0/1     Completed   0          5m11s
openelb-manager-5cdc8697f9-h2wd6   1/1     Running     0          5m11s

$ kubectl get deploy -n openelb-system
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
openelb-manager   1/1     1            1           5m38s

$ kubectl get jobs.batch -n openelb-system
NAME                       COMPLETIONS   DURATION   AGE
openelb-admission-create   1/1           11s        11m
openelb-admission-patch    1/1           12s        11m

創建 EIP

接下來我們需要配置 loadbalancerIP 所在的網段資源,這裏我們創建一個 Eip 對象來進行定義,後面對 IP 段的管理也是在這裏進行。

apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
    # Eip 對象的名稱。
    name: eip-layer2-pool
spec:
    # Eip 對象的地址池
    address: 10.31.88.101-10.31.88.200

    # openELB的運行模式,默認爲bgp
    protocol: layer2

    # OpenELB 在其上偵聽 ARP/NDP 請求的網卡。該字段僅在protocol設置爲時有效layer2。
    interface: eth0

    # 指定是否禁用 Eip 對象
    # false表示可以繼續分配
    # true表示不再繼續分配
    disable: false
    
status:

    # 指定 Eip 對象中的IP地址是否已用完。
    occupied: false

    # 指定 Eip 對象中有多少個 IP 地址已分配給服務。
    # 直接留空,系統會自動生成
    usage: 

    # Eip 對象中的 IP 地址總數。
    poolSize: 100

    # 指定使用的 IP 地址和使用 IP 地址的服務。服務以Namespace/Service name格式顯示(例如,default/test-svc)。
    # 直接留空,系統會自動生成
    used: 

    # Eip 對象中的第一個 IP 地址。
    firstIP: 10.31.88.101
    # Eip 對象中的最後一個 IP 地址。
    lastIP: 10.31.88.200

    ready: true
    # 指定IP協議棧是否爲 IPv4。目前,OpenELB 僅支持 IPv4,其值只能是true.
    v4: true

配置完成後我們直接部署即可

$ kubectl apply -f openelb/openelb-eip.yaml
eip.network.kubesphere.io/eip-layer2-pool created

部署完成後檢查 eip 的狀態

$ kubectl get eip
NAME              CIDR                        USAGE   TOTAL
eip-layer2-pool   10.31.88.101-10.31.88.200           100

創建測試服務

然後我們創建對應的服務

apiVersion: v1
kind: Namespace
metadata:
  name: nginx-quic

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-lb
  namespace: nginx-quic
spec:
  selector:
    matchLabels:
      app: nginx-lb
  replicas: 4
  template:
    metadata:
      labels:
        app: nginx-lb
    spec:
      containers:
      - name: nginx-lb
        image: tinychen777/nginx-quic:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb-service
  namespace: nginx-quic
spec:
  allocateLoadBalancerNodePorts: false
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer
 

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb2-service
  namespace: nginx-quic
spec:
  allocateLoadBalancerNodePorts: false
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer
  
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb3-service
  namespace: nginx-quic
spec:
  allocateLoadBalancerNodePorts: false
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer

然後我們檢查部署狀態:

$ kubectl apply -f nginx-quic-lb.yaml
namespace/nginx-quic unchanged
deployment.apps/nginx-lb created
service/nginx-lb-service created
service/nginx-lb2-service created
service/nginx-lb3-service created

$ kubectl get svc -n nginx-quic
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
nginx-lb-service     LoadBalancer   10.88.17.248   10.31.88.101   80:30643/TCP   58m
nginx-lb2-service    LoadBalancer   10.88.13.220   10.31.88.102   80:31114/TCP   58m
nginx-lb3-service    LoadBalancer   10.88.18.110   10.31.88.103   80:30485/TCP   58m

此時我們再查看 eip 的狀態可以看到新部署的三個 LoadBalancer 服務:

$ kubectl get eip
NAME              CIDR                        USAGE   TOTAL
eip-layer2-pool   10.31.88.101-10.31.88.200   3       100

[root@tiny-calico-master-88-1 tiny-calico]# kubectl get eip -o yaml
apiVersion: v1
items:
- apiVersion: network.kubesphere.io/v1alpha2
  kind: Eip
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"network.kubesphere.io/v1alpha2","kind":"Eip","metadata":{"annotations":{},"name":"eip-layer2-pool"},"spec":{"address":"10.31.88.101-10.31.88.200","disable":false,"interface":"eth0","protocol":"layer2"},"status":{"firstIP":"10.31.88.101","lastIP":"10.31.88.200","occupied":false,"poolSize":100,"ready":true,"usage":1,"used":{"10.31.88.101":"nginx-quic/nginx-lb-service"},"v4":true}}
    creationTimestamp: "2022-05-19T08:21:58Z"
    finalizers:
    - finalizer.ipam.kubesphere.io/v1alpha1
    generation: 2
    name: eip-layer2-pool
    resourceVersion: "1623927"
    uid: 9b091518-7b64-43ae-9fd4-abaf50563160
  spec:
    address: 10.31.88.101-10.31.88.200
    interface: eth0
    protocol: layer2
  status:
    firstIP: 10.31.88.101
    lastIP: 10.31.88.200
    poolSize: 100
    ready: true
    usage: 3
    used:
      10.31.88.101: nginx-quic/nginx-lb-service
      10.31.88.102: nginx-quic/nginx-lb2-service
      10.31.88.103: nginx-quic/nginx-lb3-service
    v4: true
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

關於 nodeport

openELB 似乎並不支持 allocateLoadBalancerNodePorts 字段,在指定了 allocateLoadBalancerNodePortsfalse 的情況下還是爲服務創建了 nodeport,查看部署後的配置可以發現參數被修改回了 true,並且無法修改爲 false

[root@tiny-calico-master-88-1 tiny-calico]# kubectl get svc -n nginx-quic
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
nginx-lb-service     LoadBalancer   10.88.17.248   10.31.88.101   80:30643/TCP   106m
nginx-lb2-service    LoadBalancer   10.88.13.220   10.31.88.102   80:31114/TCP   106m
nginx-lb3-service    LoadBalancer   10.88.18.110   10.31.88.113   80:30485/TCP   106m

[root@tiny-calico-master-88-1 tiny-calico]# kubectl get svc -n nginx-quic nginx-lb-service -o yaml | grep "allocateLoadBalancerNodePorts:"
  allocateLoadBalancerNodePorts: true

關於 VIP

如何查找 VIP

Layer2 模式下,所有的 k8s 節點的 kube-ipvs0 接口上都會看到所有的 VIP,因此確定 VIP 的節點還是和 MetalLB 一樣,需要通過查看 pod 中的日誌,或者是看 arp 表來確定

$ kubectl logs -f -n  openelb-system openelb-manager-5cdc8697f9-h2wd6
...省略一堆日誌輸出...
{"level":"info","ts":1652949142.6426723,"logger":"IPAM","msg":"assignIP","args":{"Key":"nginx-quic/nginx-lb3-service","Addr":"10.31.88.113","Eip":"eip-layer2-pool","Protocol":"layer2","Unalloc":false},"result":{"Addr":"10.31.88.113","Eip":"eip-layer2-pool","Protocol":"layer2","Sp":{}},"err":null}
{"level":"info","ts":1652949142.65095,"logger":"arpSpeaker","msg":"map ingress ip","ingress":"10.31.88.113","nodeIP":"10.31.88.1","nodeMac":"52:54:00:74:eb:11"}
{"level":"info","ts":1652949142.6509972,"logger":"arpSpeaker","msg":"send gratuitous arp packet","eip":"10.31.88.113","nodeIP":"10.31.88.1","hwAddr":"52:54:00:74:eb:11"}
{"level":"info","ts":1652949142.6510363,"logger":"arpSpeaker","msg":"send gratuitous arp packet","eip":"10.31.88.113","nodeIP":"10.31.88.1","hwAddr":"52:54:00:74:eb:11"}
{"level":"info","ts":1652949142.6849823,"msg":"setup openelb service","service":"nginx-quic/nginx-lb3-service"}

查看 arp 表來確定 MAC 地址從而確定 VIP 所在的節點

$ ip neigh  | grep 10.31.88.113
10.31.88.113 dev eth0 lladdr 52:54:00:74:eb:11 STALE
$ arp -a | grep 10.31.88.113
? (10.31.88.113) at 52:54:00:74:eb:11 [ether] on eth0

如何指定 VIP

如果需要指定 VIP,我們還是在 service 中修改 loadBalancerIP 字段從而指定 VIP:

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb3-service
  namespace: nginx-quic
  annotations:
    lb.kubesphere.io/v1alpha1: openelb
    protocol.openelb.kubesphere.io/v1alpha1: layer2
    eip.openelb.kubesphere.io/v1alpha2: eip-layer2-pool
spec:
  allocateLoadBalancerNodePorts: true
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer
  # 使用 loadBalancerIP 字段從而指定VIP
  loadBalancerIP: 10.31.88.113

修改完成之後我們重新部署,openelb 會自動生效並且將服務的 EXTERNAL-IP 變更爲新 IP。

$ kubectl get svc -n nginx-quic -o wide
NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE     SELECTOR
nginx-lb-service     LoadBalancer   10.88.17.248   10.31.88.101   80:30643/TCP   18h     app=nginx-lb
nginx-lb2-service    LoadBalancer   10.88.13.220   10.31.88.102   80:31114/TCP   18h     app=nginx-lb
nginx-lb3-service    LoadBalancer   10.88.18.110   10.31.88.113   80:30485/TCP   18h     app=nginx-lb

Layer2 工作原理

我們查看 pod 的日誌,可以看到更多的詳細信息:

$ kubectl logs -f -n  openelb-system openelb-manager-5cdc8697f9-h2wd6
...省略一堆日誌輸出...
{"level":"info","ts":1652950197.1421876,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.101","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:56:df:ae"}
{"level":"info","ts":1652950496.9334905,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.102","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:74:eb:11"}
{"level":"info","ts":1652950497.1327593,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.113","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:74:eb:11"}
{"level":"info","ts":1652950497.1528928,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.101","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:56:df:ae"}
{"level":"info","ts":1652950796.9339302,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.102","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:74:eb:11"}
{"level":"info","ts":1652950797.1334393,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.113","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:74:eb:11"}
{"level":"info","ts":1652950797.1572392,"logger":"arpSpeaker","msg":"got ARP request, sending response","interface":"eth0","ip":"10.31.88.101","senderIP":"10.31.254.250","senderMAC":"6c:b1:58:56:e9:d4","responseMAC":"52:54:00:56:df:ae"}

可以看到 openelb-manager 會持續的監聽局域網中的 ARP request 請求,當遇到請求的 IP 是自己 IP 池裏面已經使用的 VIP 時會主動響應。如果 openelb-manager 存在多個副本的時候,它們會先使用 k8s 的選主算法來進行選主,然後再由選舉出來的主節點進行 ARP 報文的響應。

  • In Layer 2 mode, OpenELB uses the leader election feature of  Kubernetes to ensure that only one replica responds to ARP/NDP requests.

OpenELB-manager 高可用

默認情況下,openelb-manager 只會部署一個副本,對於可用性要求較高的生產環境可能無法滿足需求,官方也給出了部署多個副本 [9] 的教程。

官方教程的方式是推薦通過給節點添加 label 的方式來控制副本的部署數量和位置,這裏我們將其配置爲每個節點都運行一個服務(類似於 daemonset)。首先我們給需要部署的節點打上 labels。

# 我們給集羣內的三個節點都打上label
$ kubectl label --overwrite nodes tiny-calico-master-88-1.k8s.tcinternal tiny-calico-worker-88-11.k8s.tcinternal tiny-calico-worker-88-12.k8s.tcinternal lb.kubesphere.io/v1alpha1=openelb


# 查看當前節點的labels
$ kubectl get nodes -o wide --show-labels=true | grep openelb
tiny-calico-master-88-1.k8s.tcinternal    Ready    control-plane,master   16d   v1.23.6   10.31.88.1    <none>        CentOS Linux 7 (Core)   3.10.0-1160.62.1.el7.x86_64   docker://20.10.14   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-master-88-1.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
tiny-calico-worker-88-11.k8s.tcinternal   Ready    <none>                 16d   v1.23.6   10.31.88.11   <none>        CentOS Linux 7 (Core)   3.10.0-1160.62.1.el7.x86_64   docker://20.10.14   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-11.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb
tiny-calico-worker-88-12.k8s.tcinternal   Ready    <none>                 16d   v1.23.6   10.31.88.12   <none>        CentOS Linux 7 (Core)   3.10.0-1160.62.1.el7.x86_64   docker://20.10.14   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=tiny-calico-worker-88-12.k8s.tcinternal,kubernetes.io/os=linux,lb.kubesphere.io/v1alpha1=openelb

然後我們先把副本的數量縮容到 0。

$ kubectl scale deployment openelb-manager --replicas=0 -n openelb-system

接着修改配置,在部署節點的 nodeSelector 字段中增加我們前面新加的 labels

$ kubectl get deployment openelb-manager -n openelb-system -o yaml
...略去一堆輸出...
      nodeSelector:
        kubernetes.io/os: linux
        lb.kubesphere.io/v1alpha1: openelb
...略去一堆輸出...

擴容副本數量到 3。

$ kubectl scale deployment openelb-manager --replicas=3 -n openelb-system

檢查 deployment 狀態

$ kubectl get po -n openelb-system -o wide
NAME                               READY   STATUS      RESTARTS   AGE   IP            NODE                                      NOMINATED NODE   READINESS GATES
openelb-admission-create-fvfzk     0/1     Completed   0          78m   10.88.84.89   tiny-calico-worker-88-11.k8s.tcinternal   <none>           <none>
openelb-admission-patch-tlmns      0/1     Completed   1          78m   10.88.84.90   tiny-calico-worker-88-11.k8s.tcinternal   <none>           <none>
openelb-manager-6457bdd569-6n9zr   1/1     Running     0          62m   10.31.88.1    tiny-calico-master-88-1.k8s.tcinternal    <none>           <none>
openelb-manager-6457bdd569-c6qfd   1/1     Running     0          62m   10.31.88.12   tiny-calico-worker-88-12.k8s.tcinternal   <none>           <none>
openelb-manager-6457bdd569-gh995   1/1     Running     0          62m   10.31.88.11   tiny-calico-worker-88-11.k8s.tcinternal   <none>           <none>

至此就完成了 openelb-manager 的高可用部署改造。

BGP Mode

| IP | Hostname | | --- | --- | | 10.31.88.1 | tiny-calico-master-88-1.k8s.tcinternal | | 10.31.88.11 | tiny-calico-worker-88-11.k8s.tcinternal | | 10.31.88.12 | tiny-calico-worker-88-12.k8s.tcinternal | | 10.88.64.0/18 | podSubnet | | 10.88.0.0/18 | serviceSubnet | | 10.89.0.0/16 | OpenELB-BGP-IPpool | | 10.31.254.251 | BGP Router |

這裏路由器的 AS 號爲 64512,OpenELB 的 AS 號爲 64154。

創建 BGP 配置

這裏我們需要創建兩個 CRD,分別爲 BgpConf[10] 和 BgpPeer[11],用來配置對端 BGP 路由器的信息和自己 OpenELB 的 BGP 配置。

apiVersion: network.kubesphere.io/v1alpha2

kind: BgpConf

metadata:
  # BgpConf 對象的名稱,無需設置,因此openelb只認default,設置成別的會被忽略
  name: default

spec:
  # openelb 的ASN,必須與BgpPeer配置中的spec:conf:peerAS值不同。
  as: 64514

  # OpenELB 監聽的端口。默認值爲179(默認 BGP 端口號)。
  # 如果 Kubernetes 集羣中的其他組件(例如 Calico)也使用 BGP 和 179 端口,則必須設置不同的值以避免衝突。
  listenPort: 17900

  # 本地路由器ID,通常設置爲Kubernetes主節點的主網卡IP地址。
  # 如果不指定該字段,則使用openelb-manager所在節點的第一個IP地址。
  routerId: 10.31.88.1

---

apiVersion: network.kubesphere.io/v1alpha2

kind: BgpPeer

metadata:

  # BgpPeer 對象的名稱
  name: bgppeer-openwrt-plus
spec:
  conf:
    # 對端BGP路由器的ASN,必須與BgpConf配置中的spec:as值不同。
    peerAs: 64512
    # 對端BGP路由器的IP
    neighborAddress: 10.31.254.251

  # 指定IP協議棧(IPv4/IPv6)。
  # 不用修改,因此目前,OpenELB 僅支持 IPv4。
  afiSafis:
    - config:
        family:
          afi: AFI_IP
          safi: SAFI_UNICAST
        enabled: true
      addPaths:
        config:
          # OpenELB 可以發送到對等 BGP 路由器以進行等價多路徑 (ECMP) 路由的最大等效路由數。
          sendMax: 10
  nodeSelector:
    matchLabels:
      # 如果 Kubernetes 集羣節點部署在不同的路由器下,並且每個節點都有一個 OpenELB 副本,則需要配置此字段,以便正確節點上的 OpenELB 副本與對端 BGP 路由器建立 BGP 連接。
      # 默認情況下,所有 openelb-manager 副本都會響應 BgpPeer 配置並嘗試與對等 BGP 路由器建立 BGP 連接。
      openelb.kubesphere.io/rack: leaf1

然後我們部署並查看信息

$ kubectl apply -f openelb/openelb-bgp.yaml
bgpconf.network.kubesphere.io/default created
bgppeer.network.kubesphere.io/bgppeer-openwrt-plus created
$ kubectl get bgpconf
NAME      AGE
default   16s
$ kubectl get bgppeer
NAME                   AGE
bgppeer-openwrt-plus   20s

修改 EIP

和之前的 Layer2 模式類似,我們也需要創建一個 BGP 模式使用的 Eip。

apiVersion: network.kubesphere.io/v1alpha2
kind: Eip
metadata:
    # Eip 對象的名稱。
    name: eip-bgp-pool
spec:
    # Eip 對象的地址池
    address: 10.89.0.1-10.89.255.254

    # openELB的運行模式,默認爲bgp
    protocol: bgp

    # OpenELB 在其上偵聽 ARP/NDP 請求的網卡。該字段僅在protocol設置爲時有效layer2。
    interface: eth0

    # 指定是否禁用 Eip 對象
    # false表示可以繼續分配
    # true表示不再繼續分配
    disable: false
status:

    # 指定 Eip 對象中的IP地址是否已用完。
    occupied: false

    # 指定 Eip 對象中有多少個 IP 地址已分配給服務。
    # 直接留空,系統會自動生成
    usage: 

    # Eip 對象中的 IP 地址總數。
    poolSize: 65534

    # 指定使用的 IP 地址和使用 IP 地址的服務。服務以Namespace/Service name格式顯示(例如,default/test-svc)。
    # 直接留空,系統會自動生成
    used: 

    # Eip 對象中的第一個 IP 地址。
    firstIP: 10.89.0.1
    # Eip 對象中的最後一個 IP 地址。
    lastIP: 10.89.255.254

    ready: true
    # 指定IP協議棧是否爲 IPv4。目前,OpenELB 僅支持 IPv4,其值只能是true.
    v4: true

然後我們部署並查看信息,可以看到兩個 eip 資源都存在系統中。

$ kubectl apply -f openelb/openelb-eip-bgp.yaml
eip.network.kubesphere.io/eip-bgp-pool created
[root@tiny-calico-master-88-1 tiny-calico]# kubectl get eip
NAME              CIDR                        USAGE   TOTAL
eip-bgp-pool      10.89.0.1-10.89.255.254             65534
eip-layer2-pool   10.31.88.101-10.31.88.200   3       100

配置路由器

以家裏常見的 openwrt 路由器爲例,我們先在上面安裝 quagga 組件,當然要是使用的 openwrt 版本編譯了 frr 模塊 [12] 的話推薦使用 frr 來進行配置。

如果使用的是別的發行版 Linux(如 CentOS 或者 Debian)推薦直接使用 frr[13] 進行配置。

我們先在 openwrt 上面直接使用 opkg 安裝 quagga

$ opkg update 
$ opkg install quagga quagga-zebra quagga-bgpd quagga-vtysh

如果使用的 openwrt 版本足夠新,是可以直接使用 opkg 安裝 frr 組件的

$ opkg update 
$ opkg install frr frr-babeld frr-bfdd frr-bgpd frr-eigrpd frr-fabricd frr-isisd frr-ldpd frr-libfrr frr-nhrpd frr-ospf6d frr-ospfd frr-pbrd frr-pimd frr-ripd frr-ripngd frr-staticd frr-vrrpd frr-vtysh frr-watchfrr frr-zebra

如果是使用 frr 記得在配置中開啓 bgpd 參數再重啓 frr

$ sed -i 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons
$ /etc/init.d/frr restart

路由器這邊我們使用 frr 進行 BGP 協議的配置,需要注意的是因爲前面我們把端口修改爲 17900,因此這裏也需要進行同步配置節點端口爲 17900。

root@tiny-openwrt-plus:~# cat /etc/frr/frr.conf
frr version 8.2.2
frr defaults traditional
hostname tiny-openwrt-plus
log file /home/frr/frr.log
log syslog
!
password zebra
!
router bgp 64512
 bgp router-id 10.31.254.251
 no bgp ebgp-requires-policy
 !
 !
 neighbor 10.31.88.1 remote-as 64514
 neighbor 10.31.88.1 port 17900
 neighbor 10.31.88.1 description 10-31-88-1

 neighbor 10.31.88.11 remote-as 64514
 neighbor 10.31.88.11 port 17900
 neighbor 10.31.88.11 description 10-31-88-11

 neighbor 10.31.88.12 remote-as 64514
 neighbor 10.31.88.12 port 17900
 neighbor 10.31.88.12 description 10-31-88-12
 !
 !
 address-family ipv4 unicast
 !maximum-paths 3
 exit-address-family
exit
!
access-list vty seq 5 permit 127.0.0.0/8
access-list vty seq 10 deny any
!
line vty
 access-class vty
exit
!

部署測試服務

這裏我們創建兩個服務進行測試,需要注意和上面的 layer2 模式不同,這裏的註解要同步修改爲使用 bgp 和對應的 eip。

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb2-service
  namespace: nginx-quic
  annotations:
    lb.kubesphere.io/v1alpha1: openelb
    protocol.openelb.kubesphere.io/v1alpha1: bgp
    eip.openelb.kubesphere.io/v1alpha2: eip-bgp-pool
spec:
  allocateLoadBalancerNodePorts: false
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer
  
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb3-service
  namespace: nginx-quic
  annotations:
    lb.kubesphere.io/v1alpha1: openelb
    protocol.openelb.kubesphere.io/v1alpha1: bgp
    eip.openelb.kubesphere.io/v1alpha2: eip-bgp-pool
spec:
  allocateLoadBalancerNodePorts: false
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  selector:
    app: nginx-lb
  ports:
  - protocol: TCP
    port: 80 # match for service access port
    targetPort: 80 # match for pod access port
  type: LoadBalancer
  loadBalancerIP: 10.89.100.100

部署完成之後我們再查看服務的狀態,可以看到沒有指定 loadBalancerIP 的服務會自動按順序分配一個可用 IP,而指定了 IP 的會分配爲我們手動指定的 IP,同時 layer2 模式的也可以共存。

$ kubectl get eip -A
NAME              CIDR                        USAGE   TOTAL
eip-bgp-pool      10.89.0.1-10.89.255.254     2       65534
eip-layer2-pool   10.31.88.101-10.31.88.200   1       100
$ kubectl get svc -n nginx-quic
NAME                TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
nginx-lb-service    LoadBalancer   10.88.15.40    10.31.88.101    80:30972/TCP   2m48s
nginx-lb2-service   LoadBalancer   10.88.60.227   10.89.0.1       80:30425/TCP   2m48s
nginx-lb3-service   LoadBalancer   10.88.11.160   10.89.100.100   80:30597/TCP   2m48s

再查看路由器上面的路由表,可以看到兩個 BGP 模式的 VIP 有多個下一條路由,則說明 ECMP 開啓成功

tiny-openwrt-plus# 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/0] via 10.31.254.254, eth0, 12:56:32
C>* 10.31.0.0/16 is directly connected, eth0, 12:56:32
B>* 10.89.0.1/32 [20/0] via 10.31.88.1, eth0, weight 1, 00:00:28
  *                     via 10.31.88.11, eth0, weight 1, 00:00:28
  *                     via 10.31.88.12, eth0, weight 1, 00:00:28
B>* 10.89.100.100/32 [20/0] via 10.31.88.1, eth0, weight 1, 00:00:28
  *                         via 10.31.88.11, eth0, weight 1, 00:00:28
  *                         via 10.31.88.12, eth0, weight 1, 00:00:28

最後再找一臺機器進行 curl 測試

[tinychen /root]# curl 10.89.0.1
10.31.88.1:22432
[tinychen /root]# curl 10.89.100.100
10.31.88.11:59470

總結

OpenELB 的兩種模式的優缺點和 MetalLB[14] 幾乎一模一樣,兩者在這方面的優缺點概況文檔可以結合起來一起看:

Layer2 mode 優缺點

優點:

缺點:

改進方案:

BGP mode 優缺點

BGP 模式的優缺點幾乎和 Layer2 模式相反

優點:

缺點:

路由器中使用的哈希值通常 不穩定,因此每當後端集的大小發生變化時(例如,當一個節點的 BGP 會話關閉時),現有的連接將被有效地隨機重新哈希,這意味着大多數現有的連接最終會突然被轉發到不同的後端,而這個後端可能和此前的後端毫不相干且不清楚上下文狀態信息。

改進方案:

OpenELB 官方並沒有給出 BGP 模式的優缺點分析和改進方案,但是我們可以參考 MetalLB 官方給出的資料:

MetalLB 給出了一些改進方案 [15],下面列出來給大家參考一下

OpenELB 優缺點

這裏儘量客觀的總結概況一些客觀事實,是否爲優缺點可能會因人而異:

總的來說,OpenELB 是一款不錯的負載均衡器,在前人 MetalLB 的基礎上做了一些改進,有一定的社區熱度、有一定的專業團隊進行維護;但是目前感覺還處於比較初級的階段,有較多的功能還沒有開發完善,使用起來偶爾會有些不大不小的問題。青雲科技官方表示會在後面的 KubeSphere v3.3.0 版本內置 OpenELB 用於服務暴露,屆時應該會有更多的用戶參與進來。

引用鏈接

[1]

方案: https://tinychen.com/tags/k8s/

[2]

KubeSphere 社區: https://kubesphere.io/

[3]

沙箱項目: https://www.cncf.io/sandbox-projects/

[4]

OpenELB 官方: https://openelb.github.io/docs/concepts/layer-2-mode/

[5]

gobgp: https://github.com/osrg/gobgp

[6]

官網的圖: https://openelb.github.io/docs/concepts/bgp-mode/

[7]

strictARP: https://kubernetes.io/docs/reference/config-api/kube-proxy-config.v1alpha1/

[8]

文章中的解釋: https://tinychen.com/20200427-lvs-principle-introduction/#6、ARP-in-LVS

[9]

部署多個副本: https://openelb.github.io/docs/getting-started/configuration/configure-multiple-openelb-replicas/

[10]

BgpConf: https://openelb.github.io/docs/getting-started/configuration/configure-openelb-in-bgp-mode/#configure-local-bgp-properties-using-bgpconf

[11]

BgpPeer: https://openelb.github.io/docs/getting-started/configuration/configure-openelb-in-bgp-mode/#configure-peer-bgp-properties-using-bgppeer

[12]

編譯了 frr 模塊: http://docs.frrouting.org/projects/dev-guide/en/latest/building-frr-for-openwrt.html

[13]

frr: https://frrouting.org/

[14]

MetalLB: https://tinychen.com/20220519-k8s-06-loadbalancer-metallb/

[15]

改進方案: https://metallb.universe.tf/concepts/bgp/#limitations

[16]

企業: https://github.com/openelb/openelb/blob/master/ADOPTERS.md

雲原生實驗室 戰略上藐視雲原生,戰術上重視雲原生

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/9uxSmtnL2WadYybCQzl_Fg