# 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/)