目的
一般情況下要讓外部使用者使用 Kubernetes 的服務時,需要透過 External Load Balancers 如 Cloud Provider (GCP、AWS、Azure…),但在使用 bare metal Kubernetes clusters 時,Kubernetes 沒有提供原生的這種 Network Load Balancers,如果要讓外部使用者能連線到 bare metal cluster 內部的服務時,通常是使用 NodePort 和 ExternalIPs service,但這兩種在生產環境使用都有著缺點。
MetalLB 的目的就是與標準網路設備做整合,使用標準的路由協定使在 bare-metal clusters 內的 對外部的服務 可以正常運作。
MetalLB 提供兩種模式:BGP mode 和 Layer 2 mode,BGP mode 才能實現真正的跨多節點均衡負載,但因為我只是測試用途所以採用 Layer 2 mode。
MetalLB 在做什麼
MetalLB 會同時作到兩個功能去提供此服務:IP 位址分配、向外部宣告 IP 是存在於這個 cluster。
- IP 位址分配:你需要提供 IP Pools 給 MetalLB,其餘的事例如分配、取消分配、追蹤都由 MetalLB 處理。Private/Public 的 IPV4/IPV6 你要獨立提供或是一起提供都可以,取決於你的環境和租用的 IPs
- 向外部宣告 IP:向 cluster 所在的網路宣告,這個 IP 存在我這個 cluster,宣告方式取決於所使用的模式為何:ARP、NDP、or BGP
關於 Layer 2 模式
在 Layer 2 模式下,是一個節點負責向本地網路宣告某個 Service IP,故所有流到此 Service IP 的流量都先經過這台節點,之後才由 kube-proxy 去分散流量到此 Service 的所有 Pods。故 Layer 2 模式未實現真正的網路負載平衡,而是實現故障轉移的功能,故障轉移的偵測是使用 memberlist 實現。
那是誰負責去做宣告?
只有一個節點去宣告某個 IP 是它負責,故需要為此 IP 選出誰是其 Leader,照官網的說法此選舉是無狀態的,工作原理如下:
- 每個 speakers 收集一個可能為某 IP 的宣告者的清單,會考慮幾個因素:active 的 speakers、external traffic policy、active 的 endpoints、node selectors 等等
- 每個 speakers 做相同的運算:使用
node+VIP元素算出一個排序的哈希表,並且 Service IP 由此哈希表順序第一個的 item 所描述的節點 (speaker) 去負責宣告
Layer 2 模式的限制
主要有遇到兩個限制:單節點頻寬瓶頸、故障移轉可能延遲。
- 頻寬限制: 雖然 Layer 2 模式透過雜湊運算可將不同 Service IP 分散給不同節點,實現 Service 層級的負載均衡,但對於單一 Service (特別是聚合大流量的 Ingress Controller 和 Gateway API),其所有流入流量仍受限於單一節點的頻寬與處理能力。
- 故障移轉延遲: MetalLB 透過發送 Gratuitous ARP 來宣告 IP 的新 MAC Address。雖然現代設備多能快速更新,但部份上層交換器或路由器的 ARP 快去策略可能較為保守 (或忽略GARP),導致在 ARP Table 更新前,流量仍被送往故障節點,造成數秒至數分鐘的連線中斷。
安裝方式
準備
這邊我使用 kube-proxy 是啟用 IPVS mode,官網提到必須啟用 strict ARP mode:
# 查看此指令會更改什麼,如果有更動就會回傳非 0 值
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl diff -f - -n kube-system
# 實際去更新異動,如果錯誤會回傳非 0 值
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system
透過 Helm 安裝 metallb
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb --namespace metallb-system --create-namespace
安裝 MetalLB 到 cluster 後,包含以下 components:
metallb-system/controllerdeployment. 這是一個 cluster-wide 的 Controller,處理 IP Address 的分配。metallb-system/speakerdaemonset. 這個元件負責依照你選擇的模式去遵循 protocol(s),讓服務可以被連線。- controller 和 speaker 使用的 Service accounts,還有讓這些 components 正常運作的 RBAC 權限。
此時 MetalLB 仍然是閒置狀態,直到設定了對應的 CRs (Custom Resources)。
設定 CRs
底下為範例 manifest 檔案,命名為 metallb-l2-config.yaml。因為我的環境是透過 KubeSpray 的 Vagrantfile 建立的,所以 IPAddressPool 就選用 172.18.8.0/24 網段。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 172.18.8.201-172.18.8.205
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: first-ad
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
建立 Resource Objects:
kubectl apply -f metallb-l2-config.yaml
設定測試網頁
檔案名稱 metallb-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test-deployment
labels:
app: nginx-test
spec:
replicas: 1
selector:
matchLabels:
app: nginx-test
template:
metadata:
labels:
app: nginx-test
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-test-service
# 如果需要指定取得 IPAddressPool 範圍內的特定 IP,加上以下 annotations
# annotations:
# metallb.io/loadBalancerIPs: 172.18.8.203
spec:
selector:
app: nginx-test
ports:
- protocol: TCP
port: 80 # 外部連線用的 Port
targetPort: 80 # Pod 內部的 Port
type: LoadBalancer # 類型設定為 LoadBalancer,MetalLB 會自動為這個 Service 分配 IP
建立 Resource Objects:
kubectl apply -f metallb-test.yaml
檢查 Service 狀態和確認連線:
kubectl get svc nginx-test-service
透過上述指令取得 EXTERNAL-IP 後,打開瀏覽器輸入 http://{EXTERNAL-IP} 應該能看到 “Welcome to nginx!” 的畫面
移除測試網頁
kubectl delete -f metallb-test.yaml