# Run Kube-vip as Cloud Provider on HA K3s ## 1. 先備知識 ### 1.1. kube-vip 專案主要提供兩大功能領域: * 透過 control-plane 的 VIP 實現高可用(HA)的 Kubernetes 叢集 * 提供 Kubernetes 中的 Service 類型: `LoadBalancer` ### 1.2. HA Kubernetes Control Plane * **重新實作負載平衡功能**:由於先前的請求,HTTP 負載平衡功能被移除,只保留了控制平面的高可用(HA)功能。此功能將重新實作,可能會採用原本的 round-robin HTTP 請求方式,或是改用 IPVS。 * **利用 Kubernetes API 判斷其他控制平面節點**:一旦單節點叢集運行後,kube-vip 可以透過 Kubernetes API 來判斷其他控制平面成員。目前的做法是每個控制平面節點都必須由 Cluster-API provider 投放一份 static manifest。 * **重新評估 Raft**:kube-vip 最初的設計是作為獨立於 Kubernetes 的 raft 叢集,但因為像 CAPV(Cluster API Provider vSphere)這類元件在升級過程中存在的一些限制,使得改為在 Kubernetes 內部使用 leaderElection 成為更好的選擇。 ### 1.3. Kubernetes service type:LoadBalancer * **每個 LoadBalancer 使用 ARP 的 LeaderElection**:目前僅有被選為 leader 的單一 Pod 會處理某個 VIP 的所有流量。若能為每個 Service 產生一個獨立的 leaderElection token,將可讓服務在整個叢集中的所有 Pod 上分散部署與運作。 ## 2. Install kube-vip as static pods ### 2.0. 本篇文章測試環境 ``` NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k3s-kubevip-a1 Ready control-plane,etcd,master,worker 13h v1.31.3+k3s1 172.20.6.61 <none> SUSE Linux Micro 6.0 6.4.0-18-default containerd://1.7.23-k3s2 k3s-kubevip-a2 Ready control-plane,etcd,master,worker 13h v1.31.3+k3s1 172.20.6.62 <none> SUSE Linux Micro 6.0 6.4.0-18-default containerd://1.7.23-k3s2 k3s-kubevip-a3 Ready control-plane,etcd,master,worker 13h v1.31.3+k3s1 172.20.6.63 <none> SUSE Linux Micro 6.0 6.4.0-18-default containerd://1.7.23-k3s2 ``` > - 底層是一台 VM 分別在各自的實體機上 > - 實體網路速度 10 G > - kubeproxy mode 為 `ipvs` > - 可透過在 K3s 任一 node 執行以下指令檢查 : > `curl http://localhost:10249/proxyMode` ### 2.1. Set configuration details 設定用於 control plane node 的 VIP 位址: ``` export VIP=172.20.6.60 ``` 將 INTERFACE 名稱設定為將宣布 VIP 的 control plane node 上的介面名稱。在許多 Linux 發行版中,可以使用 `ip a` 指令找到它。 ``` export INTERFACE=eth0 ``` 透過解析 GitHub API 取得 kube-vip 版本的最新版本。此步驟需要安裝 `jq` 和 `curl`。 ```! KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name") ``` ### 2.2. Creating the manifest 現在已經設定好輸入值,我們可以拉取並執行 kube-vip image,並提供所需的 flags 與參數。一旦依照你選擇的方法(ARP 或 BGP)產生出靜態 Pod 的 manifest,如果你是運行多個 control plane 節點,請確保將該 manifest 放入每個 control plane 節點的 static manifest 目錄中(預設為 `/etc/kubernetes/manifests`)。 根據使用的 container runtime,請使用兩個別名命令中的其中一個來建立 kube-vip 命令,以 app container 的方式執行 kube-vip 映像檔。 若使用的是 containerd,請執行以下命令: ```! alias kube-vip="ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION; ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip" ``` #### 2.2.1. ARP 在設定好輸入參數與別名命令後,我們可以執行 kube-vip container 產生一個 Static Pod 的 manifest,並將其寫入 `/var/lib/rancher/k3s/agent/pod-manifests/kube-vip.yaml` 檔案中。因此,這個動作預期是在第一個 control plane 節點上執行。 這個設定會建立一個 manifest,啟動 kube-vip,以使用 leaderElection 方法與 ARP 提供 control plane 的虛擬 IP(VIP)以及 Kubernetes Service 的管理功能。當該實例被選為 leader 時,它會將 VIP 綁定到指定的網路介面上。這個行為與 Kubernetes 中的 LoadBalancer 類型 Service 相同。 ``` kube-vip manifest pod \ --interface $INTERFACE \ --address $VIP \ --controlplane \ --services \ --arp \ --leaderElection \ --vipSubnet 16 \ --servicesElection \ --enableLoadBalancer \ --k8sConfigPath /etc/rancher/k3s/k3s.yaml | tee /var/lib/rancher/k3s/agent/pod-manifests/kube-vip.yaml ``` #### 🔍 參數解析 * `--interface $INTERFACE`:指定 kube-vip 綁定的網路介面,例如 `eth0`。 * `--address $VIP`:設定虛擬 IP(VIP),作為控制平面和服務的入口點。 * `--controlplane`:啟用控制平面 VIP 功能,提供高可用的 Kubernetes 控制平面。 * `--services`:啟用對 Kubernetes 中 `LoadBalancer` 類型服務的支援。 * `--arp`:使用 ARP 模式廣播 VIP,適用於 Layer 2 網路環境。 * `--leaderElection`:啟用領導者選舉機制,確保在多個節點中只有一個節點持有 VIP。 * `--vipSubnet 16`:設定 VIP 的子網掩碼為 `/16`。 * `--servicesElection`:為每個服務啟用獨立的領導者選舉,允許服務在不同節點上分佈(也就是說這允許 kube-vip 在多個節點之間分配 loadBalancer 位址)。 * `--enableLoadBalancer`:啟用 IPVS 負載平衡器,將流量分發到後端 Pod。 * `--k8sConfigPath /etc/rancher/k3s/k3s.yaml`:指定 kube-vip 使用的 Kubernetes 配置檔案路徑。 #### 📌 注意事項 * 確保 `$INTERFACE` 和 `$VIP` 變數已正確設定,並符合您的網路環境。 * `--vipSubnet` 的設定應根據您的網路規劃進行調整,常見的值有 `/32`、`/24` 等。 * 在使用 ARP 模式時,確保網路設備允許 ARP 廣播。 * `--servicesElection` 功能需要 kube-vip 版本 ≥ 0.5.0。 * `--serviceInterface` 可以讓 loadbalancer service 的 ip 跑在這參數指定的網卡上。 :::spoiler 適合 kubeproxy mode 為 `iptables` 的 YAML ``` kube-vip manifest pod \ --interface $INTERFACE \ --address $VIP \ --controlplane \ --services \ --arp \ --leaderElection \ --vipSubnet 16 \ --k8sConfigPath /etc/rancher/k3s/k3s.yaml | tee /var/lib/rancher/k3s/agent/pod-manifests/kube-vip.yaml ``` ::: ### 2.3. 檢視 kube-vip pod 狀態 ``` kubectl -n kube-system get pods -o wide ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-6cb54596b5-fp52d 1/1 Running 2 (116m ago) 13h 10.42.0.4 k3s-kubevip-a1 <none> <none> kube-vip-cloud-provider-6ffc794f6-x9zr8 1/1 Running 0 36m 10.42.1.5 k3s-kubevip-a3 <none> <none> kube-vip-k3s-kubevip-a1 1/1 Running 0 5m42s 172.20.6.61 k3s-kubevip-a1 <none> <none> local-path-provisioner-598c97774d-dbfhd 1/1 Running 2 (116m ago) 13h 10.42.0.5 k3s-kubevip-a1 <none> <none> metrics-server-66f4d95f59-r9kxn 1/1 Running 2 (116m ago) 13h 10.42.0.3 k3s-kubevip-a1 <none> <none> ``` ### 2.4. 檢視網卡 連線到 `kube-vip` pod 所在的 node ``` ssh <user>@<node ip> ``` 檢視 eth0 網卡資訊 ``` ip a s eth0 ``` 執行結果 : ``` 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether bc:24:11:0b:f6:e5 brd ff:ff:ff:ff:ff:ff altname enp0s18 altname ens18 inet 172.20.6.61/16 brd 172.20.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 172.20.6.60/32 scope global eth0 valid_lft forever preferred_lft forever ``` > 可以看到這張網卡多了一個 IP 位址 ### 2.5. 到其他 Control-plane node 重複執行 2.1 ~ 2.3 的操作步驟 ### 2.6. 再次檢視 kube-vip pod 狀態 ``` kubectl -n kube-system get pods -o wide ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-6cb54596b5-fp52d 1/1 Running 2 (116m ago) 13h 10.42.0.4 k3s-kubevip-a1 <none> <none> kube-vip-cloud-provider-6ffc794f6-x9zr8 1/1 Running 0 36m 10.42.1.5 k3s-kubevip-a3 <none> <none> kube-vip-k3s-kubevip-a1 1/1 Running 0 5m42s 172.20.6.61 k3s-kubevip-a1 <none> <none> kube-vip-k3s-kubevip-a2 1/1 Running 0 2m8s 172.20.6.62 k3s-kubevip-a2 <none> <none> kube-vip-k3s-kubevip-a3 1/1 Running 0 35s 172.20.6.63 k3s-kubevip-a3 <none> <none> local-path-provisioner-598c97774d-dbfhd 1/1 Running 2 (116m ago) 13h 10.42.0.5 k3s-kubevip-a1 <none> <none> metrics-server-66f4d95f59-r9kxn 1/1 Running 2 (116m ago) 13h 10.42.0.3 k3s-kubevip-a1 <none> <none> ``` ## 3. Install the kube-vip Cloud Provider ```! kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml ``` 檢查 ```! kubectl -n kube-system get pods -l "app=kube-vip" ``` 執行結果如下 : ``` NAME READY STATUS RESTARTS AGE kube-vip-cloud-provider-88689889-62msw 1/1 Running 0 8m40s ``` ## 4. Create a global CIDR or IP Range 為了讓 kube-vip 能夠為類型為 `LoadBalancer` 的 `Service` 設定 IP 位址,必須先提供可供分配的 IP 位址範圍。這些資訊儲存在 Kubernetes 的 `ConfigMap` 中,kube-vip 可以存取該 `ConfigMap`。您可以透過 ConfigMap 中的 key 來控制 IP 分配的範圍。可以指定 CIDR 區段或 IP 範圍,並將其設定為全域(整個叢集)或特定 Namespace 使用。 若要允許 kube-vip 在任何 Namespace 中為類型為 `LoadBalancer` 的 `Service` 分配 IP 位址,您可以建立一個名為 kubevip 的 `ConfigMap`,並設定 key 為 `cidr-global`,其值為您環境中可用的 CIDR 區段。例如,以下指令建立了一個全域的 CIDR 區段 `172.20.6.65/32`,kube-vip 將從中分配 IP 位址。 ```! kubectl create configmap -n kube-system kubevip --from-literal cidr-global=172.20.6.65/32 ``` > 其他指定 IP 的方式,請參考以下連結 : > [The kube-vip Cloud Provider ConfigMap - Kube-vip Docs](https://kube-vip.io/docs/usage/cloud-provider/#the-kube-vip-cloud-provider-configmap) ## 5. 建立測試的應用程式 kube-vip 的「平均」故障轉移時間約為 **3** 秒,但這可能會因底層基礎設施而有很大差異,例如虛擬和實體交換器可能會阻止或限制 kube-vip 更新網路。要開始評估您的基礎設施效能,可以進行以下最簡單的測試: ```! kubectl create deployment source-ip-app \ --image=registry.k8s.io/echoserver:1.10 \ --replicas 3 ``` ### 5.1. 檢視 pod 狀態 ``` kubectl get pods -o wide ``` 執行結果 : ``` NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES source-ip-app-78477d56f4-cqmhh 1/1 Running 0 78s 10.42.0.10 k3s-kubevip-a1 <none> <none> source-ip-app-78477d56f4-fw6d7 1/1 Running 0 78s 10.42.2.2 k3s-kubevip-a2 <none> <none> source-ip-app-78477d56f4-hsgrv 1/1 Running 0 78s 10.42.1.8 k3s-kubevip-a3 <none> <none> ``` ### 5.2. 建一個 loadbalancer service ``` kubectl expose deployment source-ip-app \ --name=loadbalancer \ --port=80 \ --target-port=8080 \ --type=LoadBalancer ``` ### 5.3. 檢視 SVC 的 `EXTERNAL-IP` ``` kubectl get svc ``` 執行結果 : ``` NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 13h loadbalancer LoadBalancer 10.43.123.204 172.20.6.65 80:32303/TCP 2m11s ``` ### 5.4. 查詢 kube-vip 為服務選舉的 leader 節點 ```! kubectl get lease -n kube-system plndr-svcs-lock ``` 執行結果 : ``` NAME HOLDER AGE plndr-svcs-lock k3s-kubevip-a3 5h17m ``` ### 5.5. 檢視網卡 連線到 `kube-vip` pod 所在的 node ``` ssh <user>@<node ip> ``` 檢視 eth0 網卡資訊 ``` ip a s eth0 ``` 執行結果 : ``` 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether bc:24:11:70:32:5c brd ff:ff:ff:ff:ff:ff altname enp0s18 altname ens18 inet 172.20.6.63/16 brd 172.20.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 172.20.6.65/16 brd 172.20.255.255 scope global secondary eth0 valid_lft forever preferred_lft forever ``` > 可以看到這張網卡多了一個 IP 位址 ### 5.6 . 測試 Failover 在同網路的其他機器執行以下命令 ```! while true; do start=$(date +%s%3N); wget --timeout=1 -qO - 172.20.6.65 | grep Hostname || echo "wget error"; end=$(date +%s%3N); echo "耗時: $((end - start)) 毫秒"; sleep 1; done ``` 執行結果 : ``` Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 7 毫秒 Hostname: source-ip-app-78477d56f4-sgm2x 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-sgm2x 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-sgm2x 耗時: 5 毫秒 ... ``` 將 kube-vip leader pod 所在的節點關機用以模擬故障 ``` ssh <user>@<NodeIP> sudo poweroff ``` 執行結果 : ``` Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-sgm2x 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5007 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2006 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2007 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2006 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2006 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2006 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2006 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 2007 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 Hostname: source-ip-app-78477d56f4-jjmhj 耗時: 6 毫秒 Hostname: source-ip-app-78477d56f4-wf8kw 耗時: 5 毫秒 ``` 實測下來 vip failover 的時間約 **5** 秒 如果該節點同時是 cp 和 service 的 leader,failover 的時間為 **9** 秒左右 ### 5.7 再次查詢 kube-vip leader ```! kubectl get lease -n kube-system plndr-svcs-lock ``` 執行結果 : ``` NAME HOLDER AGE plndr-svcs-lock k3s-kubevip-a3 5h17m ``` > 有發現 kube-vip 無法更新這個 lease,但 VIP 實際上已移轉至其他節點 ### 5.8. 驗證網卡資訊 連線到 `kube-vip` pod 所在的 node ``` ssh <user>@<node ip> ``` 檢視 eth0 網卡資訊 ``` ip a s eth0 ``` 執行結果 : ``` 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether bc:24:11:d7:a9:4d brd ff:ff:ff:ff:ff:ff altname enp0s18 altname ens18 inet 172.20.6.62/16 brd 172.20.255.255 scope global noprefixroute eth0 valid_lft forever preferred_lft forever inet 172.20.6.65/16 brd 172.20.255.255 scope global secondary eth0 valid_lft forever preferred_lft forever ``` ## 6. 清除環境 ``` # 1. 移除測試應用 kubectl delete deploy source-ip-app; kubectl delete svc loadbalancer # 2. 移除 kube-vip cloud provider kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml # 3. 到每台 Contol-plane 移除 kube-vip static pod yaml rm /var/lib/rancher/k3s/agent/pod-manifests/kube-vip.yaml ``` ## 7. 參考文件 - [Static Pods - Kube-vip Docs](https://kube-vip.io/docs/installation/static/) - [On-Premises (kube-vip-cloud-controller) - Kube-vip Docs](https://kube-vip.io/docs/usage/cloud-provider/)