本地環境也能擁有雲端級負載平衡能力

每位開發者都有那麼幾篇技術文章,無論做過多少次特定操作,總是會回頭查閱。這可能是因為步驟複雜,或者文章本身寫得太好,以至於我們決定不必將步驟牢記於心—它們只是成為了我們工具箱中的一部分。

最近我需要重溫這樣一篇「老朋友」,突然意識到:技術文章往往像變質的起司,而非醇厚的紅酒。技術世界不斷演進,概念可能經得起時間考驗,但設定細節則常隨版本更迭而改變。

在我的情境中,我需要在KinD (Kubernetes in Docker) 上佈署MetalLB來支援一些測試工作。需求很簡單,包括:

  • 一個Kubernetes叢集
  • 一個CNI (容器網路介面)
  • 建立LoadBalancer類別服務的能力

當我回頭查閱之前的筆記時,發現設定不再有效。作為經常使用MetalLB的開發者,我本該預見這點。自0.13.2版本以來,MetalLB已從使用ConfigMap設定轉向採用多個自定義資源定義(CRDs)。然而,我當時仍感到困惑。

為了讓未來的自己(以及可能遇到相同問題的你)省去煩惱,我決定將完整步驟記錄下來。讓我們開始吧。

第一步:建立Kubernetes叢集

首先,我需要建立一個Kubernetes叢集。由於只是進行概念驗證測試,不需要太穩健的環境,因此KinD完全能滿足需求。為了示範,我們將建立一個雙節點叢集,首先建立以下設定檔案(config.yaml):

---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker

設定檔準備好後,執行以下命令啟動叢集:

╰─❯ kind create cluster --config config.yaml --name metallb-kind

這個命令會建立一個名為metallb-kind的叢集。當然,你可以自由選擇叢集名稱,或使用預設名稱。不過請確保你的操作是針對正確的叢集,而不是意外影響到重要的生產環境。

叢集建立完成後,你應該能看到類別似以下的節點狀態:

╰─❯ kubectl get nodes
NAME                         STATUS   ROLES           AGE     VERSION
metallb-kind-control-plane   Ready    control-plane   4m57s   v1.31.0
metallb-kind-worker          Ready    <none>          4m41s   v1.31.0

確認Kubernetes節點的網路可達性

通常,能夠存取Kubernetes叢集已經足夠。但在我們的測試場景中,需要確保能夠透過IP位址直接存取Kubernetes節點。具體來說,這是測試Docker網路是否可達的關鍵步驟。若無法存取這些位址,那麼後續設定額外IP也就毫無意義。

首先,我們需要取得KinD節點的IP位址。執行以下命令並檢視INTERNAL-IP欄位的值:

╰─❯ kubectl get nodes -o wide
NAME                         STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION     CONTAINER-RUNTIME
metallb-kind-control-plane   Ready    control-plane   10h   v1.31.0   172.18.0.4    <none>        Debian GNU/Linux 12 (bookworm)   6.5.0-15-generic   containerd://1.7.18
metallb-kind-worker          Ready    <none>          10h   v1.31.0   172.18.0.5    <none>        Debian GNU/Linux 12 (bookworm)   6.5.0-15-generic   containerd://1.7.18

取得IP位址後,我們可以透過簡單的ping測試來驗證連線性:

╰─❯ ping 172.18.0.4
PING 172.18.0.4 (172.18.0.4) 56(84) bytes of data.
64 bytes from 172.18.0.4: icmp_seq=1 ttl=64 time=0.065 ms
64 bytes from 172.18.0.4: icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from 172.18.0.4: icmp_seq=3 ttl=64 time=0.042 ms

如果收到回應,表示連線正常!否則,你可能需要排查網路連線障礙。這個步驟在Linux系統上通常沒有問題,但如果你使用的是Mac電腦,可能需要額外的設定步驟。

規劃網路位址區塊

現在,我們已經有了一個叢集並確認可以存取節點。接下來需要規劃一個IP位址池,供MetalLB用於廣播服務。由於我們依賴KinD使用的Docker網路,需要從中找出未使用的位址空間。

預設情況下,KinD使用名為kind的Docker網路,我們可以使用jq工具來查詢相關資訊(除非你偏好直接檢視原始資料)。我們需要的值位於IPAM.Config中的subnet欄位。這裡可能有多個條目,我們需要找出IPv4子網。如果你的Docker網路設定不複雜,執行以下命令就能獲得所需資訊:

╰─❯ docker inspect kind | jq .[].IPAM.Config
[
  {
    "Subnet": "fc00:f853:ccd:e793::/64"
  },
  {
    "Subnet": "172.18.0.0/16",
    "Gateway": "172.18.0.1"
  }
]

從輸出中可以看到,我們有一個IPv4子網172.18.0.0/16,這正是我們需要的資訊。在設定MetalLB時,我們將從這個子網中分配一小部分IP位址用於負載平衡服務。

為什麼本地環境需要MetalLB?

在雲端環境中,當你建立一個LoadBalancer類別的Kubernetes服務時,雲平台會自動提供一個負載平衡器。但在本地開發環境中,如KinD或Minikube,預設情況下並沒有負載平衡的實作。

這就是MetalLB發揮作用的地方。它為本地Kubernetes環境提供了網路負載平衡功能,讓我們能夠使用LoadBalancer類別的服務,而不僅侷限於NodePort或ClusterIP。這對於更接近生產環境的本地測試非常重要。

在我的經驗中,許多開發團隊會在本地開發過程中忽略負載平衡設定,導致佈署到雲環境時出現意外問題。透過在本地環境中使用MetalLB,我們可以更早地發現並解決這些潛在問題。

網路規劃的關鍵考量

在選擇MetalLB的IP位址範圍時,有幾個重要因素需要考慮:

  1. 避免衝突:選擇的IP範圍不應與現有網路裝置或服務衝突
  2. 範圍大小:根據預期的服務數量分配足夠的IP位址
  3. 子網位置:確保選擇的範圍在Docker網路內,但不與節點IP重疊
  4. 可達性:確保從開發機器能夠存取這些IP位址

根據上面的Docker網路資訊,我們可以選擇172.18.255.200-172.18.255.250這個範圍作為MetalLB的位址池。這個範圍在Docker子網內,但遠離了通常分配給容器的低位址範圍,降低了衝突的可能性。

在下一部分中,我們將詳細介紹如何安裝和設定MetalLB,利用這個位址池為我們的服務提供負載平衡能力。

透過這些準備工作,我們已經為在本地Kubernetes環境中實作真正的負載平衡奠定了基礎。這種設定不僅能夠滿足開發和測試需求,還能讓我們更接近生產環境的體驗。

在實際工作中,我發現這種本地負載平衡環境對於開發微服務架構特別有價值,它讓團隊能夠在本地重現生產環境中的網路行為,大降低了佈署風險。

MetalLB:本地環境的負載平衡救星

多年來在為客戶設計Kubernetes基礎架構時,我總是遇到一個反覆出現的問題:本地環境缺乏負載平衡解決方案。在公有雲上,當你設定LoadBalancer類別的服務時,Kubernetes會自動呼叫雲端供應商的API來設定負載平衡器。但在私有環境或本地開發時,這些服務往往會停留在pending狀態。

MetalLB正是為解決這個痛點而生,它讓你能夠在沒有雲端供應商的環境中使用LoadBalancer服務。我曾在多個專案中實作MetalLB,從小型開發環境到大型企業內部平台,它的表現都令人滿意。今天我將分享如何從零開始在本地Kubernetes叢集上佈署MetalLB。

為何你需要MetalLB?

在深入技術細節前,讓我先解釋為何MetalLB對本地環境如此重要。想像你正在本地開發一個微服務架構,需要模擬生產環境中的負載平衡行為。傳統上,你有這些選擇:

  1. 使用NodePort服務 - 但連線埠管理麻煩與不夠優雅
  2. 使用Ingress控制器 - 但這主要針對HTTP/HTTPS流量
  3. 設定複雜的代理 - 需要額外維護成本

MetalLB提供了第四種選擇:在本地環境中使用與雲端相同的LoadBalancer抽象概念,無需修改應用程式設定。我在多個開發團隊中推廣這種方法,開發人員能夠更貼近生產環境進行測試,減少了環境差異帶來的問題。

MetalLB的運作原理

MetalLB主要有兩種運作模式:第二層(L2)和BGP。在這篇文章中,我們將專注於L2模式,因為它更簡單與適合大多數本地開發場景。

在L2模式下,MetalLB使用ARP(地址解析協定)來宣告IP位址。當服務請求負載平衡器IP時,MetalLB會選擇一個節點作為「長官者」來回應該IP的ARP請求。這意味著所有流量會先到達這個節點,然後再轉發到適當的Pod。雖然不是真正的負載平衡,但對於開發和測試環境已經足夠了。

實戰:佈署MetalLB

第一步:確認網路環境

在佈署MetalLB前,我們需要了解本地網路環境。特別是,我們需要找出一個可用的IP位址範圍,供MetalLB分配給服務。

對於Docker Desktop使用者,我們可以檢視Docker的網路設定:

docker network inspect bridge

在我的環境中,我發現Docker使用了172.18.0.0/16網段。為了避免衝突,我決定使用這個範圍中的一小部分作為MetalLB的IP池。

第二步:佈署MetalLB控制器

有兩種方式可以佈署MetalLB:使用YAML清單或Helm。我通常偏好使用YAML,因為它更直觀與便於理解每個元件:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

如果你偏好Helm,也可以這樣安裝:

helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb

安裝完成後,檢查pod是否正常執行:

kubectl get pods -n metallb-system

你應該會看到一個控制器和兩個speaker pod(每個節點一個):

NAME                          READY   STATUS    RESTARTS      AGE
controller-8694df9d9b-fhkp2   1/1     Running   0             1d
speaker-5xc9r                 1/1     Running   0             1d
speaker-as78d                 1/1     Running   0             1d

第三步:設定MetalLB

MetalLB的最新版本使用自定義資源定義(CRD)進行設定,而不是舊版使用的ConfigMap。我們需要建立兩個資源:IPAddressPoolL2Advertisement

首先建立一個YAML檔案(metallb-config.yaml):

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: demo-pool
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.1-172.18.255.25  
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: demo-advertisement
  namespace: metallb-system

這個設定建立了一個包含25個IP位址的池(172.18.255.1至172.18.255.25)。然後應用這個設定:

kubectl apply -f metallb-config.yaml

我在一次專案中就是因為IP位址範圍設定太小,當服務數量增加時遇到了IP耗盡的問題。所以我建議即使是測試環境,也應該預留足夠的IP空間。

第四步:建立測試服務

現在我們來建立一個簡單的服務來測試MetalLB。我選擇使用輕量級的http-echo服務:

---
apiVersion: v1
kind: Namespace
metadata:
  name: echo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  namespace: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
        - name: echo
          image: hashicorp/http-echo
          args:
            - -listen=:8080
            - -text="歡迎來到玄貓的K8s實驗室"
          ports:
            - name: http
              containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo
  namespace: echo
spec:
  ports:
    - name: http
      port: 80
      targetPort: http
      protocol: TCP
  selector:
    app: echo

應用這個設定後,我們會得到一個基本的ClusterIP服務:

kubectl apply -f echo-service.yaml
kubectl get svc -n echo

輸出應該類別似於:

NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
echo   ClusterIP   10.96.173.217   <none>        80/TCP    2m40s

第五步:將服務升級為LoadBalancer

現在,讓我們將這個服務修改為LoadBalancer類別,並新增MetalLB所需的註解:

---
apiVersion: v1
kind: Service
metadata:
  annotations:
    metallb.universe.tf/address-pool: demo-pool
  name: echo
  namespace: echo
spec:
  ports:
    - name: http
      port: 80
      targetPort: http
      protocol: TCP
  selector:
    app: echo
  type: LoadBalancer

這裡我們做了兩處關鍵修改:

  1. 將服務類別從ClusterIP改為LoadBalancer
  2. 新增了metallb.universe.tf/address-pool: demo-pool註解,指定使用我們之前建立的IP池

應用這個變更:

kubectl apply -f echo-loadbalancer.yaml

檢查服務狀態:

kubectl get svc -n echo

現在你應該能看到服務已獲得外部IP:

NAME   TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
echo   LoadBalancer   10.96.190.254   172.18.255.200   80:30216/TCP   2m21s

第六步:測試負載平衡器

最後,讓我們用curl來測試這個服務是否正常工作:

curl http://172.18.255.200

如果一切正常,你應該會看到回應:

"歡迎來到玄貓的K8s實驗室"

MetalLB進階設定技巧

在實際使用MetalLB時,我發現以下幾點設定技巧特別有用:

指定固定IP位址

有時候你可能需要為特定服務分配固定IP。這可以透過新增不同的註解實作:

metadata:
  annotations:
    metallb.universe.tf/loadBalancerIPs: 172.18.255.10

這在開發環境中特別有用,可以確保服務每次重啟後都使用相同的IP,避免修改客戶端設定。

設定多個IP池

在較大的環境中,你可能需要為不同類別的服務設定不同的IP池:

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: internal-services
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.1-172.18.255.50
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: public-services
  namespace: metallb-system
spec:
  addresses:
  - 172.18.255.100-172.18.255.150

然後,你可以在服務的註解中指定使用哪個池。

避免的常見錯誤

在我佈署MetalLB的經驗中,遇到過一些常見問題:

  1. IP範圍衝突:確保MetalLB使用的IP範圍不與DHCP伺服器或其他固定IP裝置衝突
  2. 網路隔離問題:某些Kubernetes網路外掛可能需要額外設定才能與MetalLB正常工作
  3. 忘記設定註解:如果服務沒有獲得外部IP,首先檢查是否正確設定了池註解

透過MetalLB,我們成功地在本地Kubernetes環境中實作了負載平衡功能,這對於開發和測試環境來說是一個強大的工具。雖然它不能完全替代雲端提供商的負載平衡器(特別是在高用性和效能方面),但對於大多數開發需求來說已經足夠了。

MetalLB專案持續活躍開發中,未來版本可能會帶來更多功能和改進。我特別期待看到BGP模式的增強,這將使其在大型生產環境中更加實用。

在我的實際專案中,MetalLB已經成為標準工具箱的一部分,無論是在開發筆記型電腦上的微型叢集,還是在企業內部的測試環境中。它解決了Kubernetes本地環境中的一個重要缺口,讓開發體驗更加一致和順暢。

如果你正在尋找一種方法來在本地環境中模擬雲端負載平衡器的行為,我強烈推薦嘗試MetalLB。它的設定簡單,設定靈活,足以滿足大多數開發和測試需求。 透過 MetalLB 提升 KinD 叢集的實用性,讓我的本地開發環境更貼近生產環境,這對加速開發迴圈和減少佈署風險有著決定性的幫助。在實際使用過程中,我發現雖然 KinD 本身能夠透過連線埠對映暴露服務,但這種方式與真實的 Kubernetes 環境存在差異。而整合 MetalLB 後,我能夠使用與生產環境相同的服務暴露方式,大幅減少了環境差異帶來的問題。

這種設定不僅讓我能夠更有效地測試 LoadBalancer 類別的服務,還能模擬更複雜的網路情境,確保我的應用在佈署到實際環境時能夠順利執行。對於任何需要在本地進行 Kubernetes 開發的工程師來說,這個設定都能顯著提高開發效率和信心。

希望這個設定能為你的本地開發工作帶來類別似的好處,讓你在實際佈署前就能夠更全面地驗證你的應用和基礎設施程式碼。