在 Kubernetes 叢集中使用 MetalLB 作為 Load Balancer(上)
TL;DR
網路方面的知識又多又雜,很多又是系統核心的部分。原本自己不是做網路方面的,系統核心知識也薄弱。但恰恰是這些陌生的內容滿滿的誘惑,加上現在的工作跟網路關聯更多了,逮住機會就學習下。
這篇以 Kubernetes LoadBalancer 為起點,使用 MetalLB 去實現叢集的負載均衡器,在探究其工作原理的同時瞭解一些網路的知識。
由於 MetalLB 的內容有點多,一步步來,今天這篇僅介紹其中簡單又容易理解的部分,不出意外還會有下篇(太複雜,等我搞明白先 :D)。
LoadBalancer 型別 Service
由於 Kubernets 中 Pod 的 IP 地址不固定,重啟後 IP 會發生變化,無法作為通訊的地址。Kubernets 提供了 Service 來解決這個問題,對外暴露。
Kubernetes 為一組 Pod 提供相同的 DNS 名和虛擬 IP,同時還提供了負載均衡的能力。這裡 Pod 的分組通過給 Pod 打標籤( Label )來完成,定義 Service 時會宣告標籤選擇器( selector )將 Service 與 這組 Pod 關聯起來。
根據使用場景的不同,Service 又分為 4 種類型: ClusterIP 、 NodePort 、 LoadBalancer 和 ExternalName ,預設是 ClusterIP 。這裡不一一詳細介紹,有興趣的檢視 Service 官方文件 [1] 。
除了今天的主角 LoadBalancer 外,其他 3 種都是比較常用的型別。 LoadBalancer 官方的解釋是:
使用雲提供商的負載均衡器向外部暴露服務。外部負載均衡器可以將流量路由到自動建立的 NodePort 服務和 ClusterIP 服務上。
看到“雲提供商提供”幾個字時往往望而卻步,有時又需要 LoadBalancer 對外暴露服務做些驗證工作(雖然除了 7 層的 Ingress 以外,還可以使用 NodePort 型別的 Service),而 Kubernetes 官方並沒有提供實現。比如下面要介紹的 MetalLB [2] 就是個不錯的選擇。
MetalLB 介紹
MetalLB 是裸機 Kubernetes 叢集的負載均衡器實現,使用標準路由協議。
注意:MetalLB 目前還是 beta 階段。
前文提到 Kubernetes 官方並沒有提供 LoadBalancer 的實現。各家雲廠商有提供實現,但假如不是執行在這些雲環境上,建立的 LoadBalancer Service 會一直處於 Pending 狀態(見下文 Demo 部分)。
MetalLB 提供了兩個功能:
-
地址分配:當建立 LoadBalancer Service 時,MetalLB 會為其分配 IP 地址。這個 IP 地址是從 預先配置的 IP 地址庫 獲取的。同樣,當 Service 刪除後,已分配的 IP 地址會重新回到地址庫。
-
對外廣播:分配了 IP 地址之後,需要讓叢集外的網路知道這個地址的存在。MetalLB 使用了標準路由協議實現:ARP、NDP 或者 BGP。
廣播的方式有兩種,第一種是 Layer 2 模式,使用 ARP(ipv4)/NDP(ipv6) 協議;第二種是 BPG。
今天主要介紹簡單的 Layer 2 模式 ,顧名思義是 OSI 二層的實現。
具體實現原理,看完 Demo 再做分析,等不及的同學請直接跳到最後。
執行時
MetalLB 執行時有兩種工作負載:
-
Controler: Deployment ,用於監聽 Service 的變更,分配/回收 IP 地址。
-
Speaker: DaemonSet ,對外廣播 Service 的 IP 地址。
Demo
安裝之前介紹下網路環境,Kubernetes 使用 K8s 安裝在 Proxmox 的虛擬機器 [3] 上。
安裝 K3s
安裝 K3s,這裡需要通過 --disable servicelb
禁用 k3s 預設的 servicelb。
參考 K3s 文件 [4] ,預設情況下 K3s 使用 Traefik [5] ingress 控制器 和 Klipper [6] Service 負載均衡器來對外暴露服務。
curl -sfL http://get.k3s.io | sh -s - --disable traefik --disable servicelb --write-kubeconfig-mode 644 --write-kubeconfig ~/.kube/config
建立工作負載
使用 nginx 映象,建立兩個工作負載:
kubectl create deploy nginx --image nginx:latest --port 80 -n default
kubectl create deploy nginx2 --image nginx:latest --port 80 -n default
同時為兩個 Deployment 建立 Service,這裡型別選擇 LoadBalancer :
kubectl expose deployment nginx --name nginx-lb --port 8080 --target-port 80 --type LoadBalancer -n default
kubectl expose deployment nginx2 --name nginx2-lb --port 8080 --target-port 80 --type LoadBalancer -n default
檢查 Service 發現狀態都是 Pending 的,這是因為安裝 K3s 的時候我們禁用了 LoadBalancer 的實現:
kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 14m
nginx-lb LoadBalancer 10.43.108.233 <pending> 8080:31655/TCP 35s
nginx2-lb LoadBalancer 10.43.26.30 <pending> 8080:31274/TCP 16s
這時就需要 MetalLB 登場了。
安裝 MetalLB
使用官方提供 manifest 來安裝,目前最新的版本是 0.12.1
。此外,還可以其他安裝方式供選擇,比如 Helm
[7]
、 Kustomize
[8]
或者 MetalLB Operator
[9]
。
kubectl apply -f http://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
kubectl apply -f http://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml
kubectl get po -n metallb-system
NAME READY STATUS RESTARTS AGE
speaker-98t5t 1/1 Running 0 22s
controller-66445f859d-gt9tn 1/1 Running 0 22s
此時再檢查 LoadBalancer Service 的狀態仍然是 Pending 的,嗯?因為,MetalLB 要為 Service 分配 IP 地址,但 IP 地址不是憑空來的,而是需要預先提供一個地址庫。
這裡我們使用 Layer 2 模式,通過 Configmap 為其提供一個 IP 段:
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.1.30-192.168.1.49
此時再檢視 Service 的狀態,可以看到 MetalLB 為兩個 Service 分配了 IP 地址 192.168.1.30
、 192.168.1.31
:
kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 28m
nginx-lb LoadBalancer 10.43.201.249 192.168.1.30 8080:30089/TCP 14m
nginx2-lb LoadBalancer 10.43.152.236 192.168.1.31 8080:31878/TCP 14m
可以請求測試下:
curl -I 192.168.1.30:8080
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 02 Mar 2022 15:31:15 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: keep-alive
ETag: "61f01158-267"
Accept-Ranges: bytes
curl -I 192.168.1.31:8080
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Wed, 02 Mar 2022 15:31:18 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: keep-alive
ETag: "61f01158-267"
Accept-Ranges: bytes
macOS 本地使用 arp -a
檢視 ARP 表可以找到這兩個 IP 及 mac 地址,可以看出兩個 IP 都繫結在同一個網絡卡上,此外還有虛擬機器的 IP 地址。也就是說 3 個 IP 繫結在該虛擬機器的 en0
上:
而去虛擬機器(節點)檢視網絡卡(這裡只能看到系統繫結的 IP):
Layer 2 工作原理
Layer 2 中的 Speaker 工作負載是 DeamonSet 型別,在每臺節點上都排程一個 Pod。首先,幾個 Pod 會先進行選舉,選舉出 Leader 。 Leader 獲取所有 LoadBalancer 型別的 Service,將已分配的 IP 地址繫結到當前主機到網絡卡上。 也就是說,所有 LoadBalancer 型別的 Service 的 IP 同一時間都是繫結在同一臺節點的網絡卡上。
當外部主機有請求要發往叢集內的某個 Service,需要先確定目標主機網絡卡的 mac 地址(至於為什麼,參考 維基百科 [10] )。這是通過傳送 ARP 請求, Leader 節點的會以其 mac 地址作為響應。外部主機會在本地 ARP 表中快取下來,下次會直接從 ARP 表中獲取。
請求到達節點後,節點再通過 kube-proxy 將請求負載均衡目標 Pod。所以說,假如Service 是多 Pod 這裡有可能會再跳去另一臺主機。
優缺點
優點很明顯,實現起來簡單(相對於另一種 BGP 模式下路由器要支援 BPG)。就像筆者的環境一樣,只要保證 IP 地址庫與叢集是同一個網段即可。
當然缺點更加明顯了, Leader 節點的頻寬會成為瓶頸;與此同時,可用性欠佳,故障轉移需要 10 秒鐘的時間( 每個 speaker 程序有個 10s 的迴圈 [11] )。
參考
Service 官方文件: http://kubernetes.io/zh/docs/concepts/services-networking/service/#publishing-services-service-types
MetalLB: http://metallb.universe.tf
Proxmox 的虛擬機器: http://atbug.com/deploy-vm-on-proxmox-with-terraform/
K3s 文件: http://rancher.com/docs/k3s/latest/en/networking/
Traefik: http://rancher.com/docs/k3s/latest/en/networking/
Klipper: http://metallb.universe.tf/configuration/k3s/
Helm: http://metallb.universe.tf/installation/#installation-with-helm
Kustomize: http://metallb.universe.tf/installation/#installation-with-kustomize
MetalLB Operator: http://metallb.universe.tf/installation/#using-the-metallb-operator
維基百科: http://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE
每個 speaker 程序有個 10s 的迴圈: http://github.com/metallb/metallb/blob/main/internal/layer2/announcer.go#L51
地址解析協議: http://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE
MetalLB 概念: http://metallb.universe.tf/concepts/
- 零信任安全:SPIFFE 和 SPIRE 通用身份驗證的標準和實現
- 資料庫/SQL 版本管理工具選型指北
- 譯:Kubernetes 最佳實踐
- 開放服務網格 Open Service Mesh 如何開放?
- 開放服務網格 Open Service Mesh 如何開放?
- 如何在 Kubernetes Pod 內進行網路抓包
- 在 Kubernetes 叢集中使用 MetalLB 作為 LoadBalancer(下)- BGP
- 在 Kubernetes 叢集中使用 MetalLB 作為 LoadBalancer(下)- BGP
- 在 Kubernetes 叢集中使用 MetalLB 作為 LoadBalancer(上)
- 在 Kubernetes 叢集中使用 MetalLB 作為 Load Balancer(上)
- 使用 Cilium 增強 Kubernetes 網路安全
- Linux 防火牆 iptables 之概念篇(圖文並茂)
- Kubernetes HPA 基於 Prometheus 自定義指標的可控彈性伸縮
- eBPF 和 Wasm:探索服務網格資料平面的未來
- Colima:MacOS 上的極簡容器執行時和 Kubernetes(支援 m1)
- OpenFaaS - 以自己的方式執行容器化函式
- OpenFaaS - 以自己的方式執行容器化函式
- 沙盒化容器:是容器還是虛擬機器
- 沙盒化容器:是容器還是虛擬機器
- 可觀測性|小小 Pipy,大有作為