有了多集群服務和跨集群的流量調度之后,使用 Kubernetes 的方式會發生很大的變化。流量的管理不再限制單一集群內,而是橫向跨越了多個集群。最重要的是這一切“靜悄悄地”發生,對應用來說毫無感知。
就拿 Kubernetes 版本升級來說吧。記得曾經經歷過集群的原地升級:團隊的幾個人經過多次、多個環境的演練,還要在凌晨的時候進行生產環境的升級。幸好最后是有驚無險,整個過程的體驗就像是下圖一樣:為飛行中的飛機換引擎。

解決了跨集群的流量調度之后,一切就會變得簡單:只需重新建個集群,慢慢將應用遷移到新的集群,讓乘客來個“空中轉機”。
方案
與之前所做的示例類似,整個方案的核心仍然是跨集群的服務調用:服務可以像使用本地 Service 一樣使用多集群 Service。
升級過程中,創建新版本的 Kubernetes 集群,其他的中間件復用現有的,這樣少了數據同步等問題。剩下的便是,調整 CD的流程將服務“同時部署到新的集群”。然后再通過?全局流量策略?慢慢放少部分流量到新的集群中進行測試,邊測試邊調整流量。兩個集群的服務也可保持一段時間觀察穩定性,然后再慢慢減少原集群的實例直至所有實例下線。
接下來,我們使用進行下示例演示。
演示
環境準備
如上面的圖展示的那樣,我們先創建兩個集群:control-plane
、1-23
?和?1-25
。集群?1-23
?就是我們現有的集群,1-25
?就是最新版本的集群。
集群 | 對外訪問地址 | api-server 對外端口 | LB 對外端口 | 描述 |
control-plane | HOST_IP(192.168.1.110) | 6444 | N/A | 控制平面集群 |
1-23 | HOST_IP(192.168.1.110) | 6445 | 81 | 應用集群 |
1-25 | HOST_IP(192.168.1.110) | 6446 | 82 | 應用集群 |
環境搭建
獲取主機 IP 地址備用,192.168.1.110
?是我本機的地址。
export?HOST_IP=192.168.1.110
搭建集群
使用 k3d 來創建這 3 個 集群。
API_PORT=6444?#6445?6446
PORT=80?#81?82
for?CLUSTER_NAME?in?control-plane?1-23?1-25
dok3d?cluster?create?${CLUSTER_NAME}?\--image?docker.io/rancher/k3s:v1.23.8-k3s2?\--api-port?"${HOST_IP}:${API_PORT}"?\--port?"${API_PORT}:6443@server:0"?\--port?"${PORT}:80@server:0"?\--servers-memory?4g?\--k3s-arg?"--disable=traefik@server:0"?\--timeout?120s?\--wait((API_PORT=API_PORT+1))((PORT=PORT+1))
done
安裝 FSM
在 3 個集群中安裝 FSM。
helm?repo?update
export?FSM_NAMESPACE=flomesh
exportFSM_VERSION=0.2.0-alpha.9
for?CLUSTER_NAME?in?control-plane?1-23?1-25
do?kubectx?k3d-${CLUSTER_NAME}sleep?1helm?install?--namespace?${FSM_NAMESPACE}?--create-namespace?--version=${FSM_VERSION}?--set?fsm.logLevel=5?fsm?fsm/fsmsleep?1kubectl?wait?--for=condition=ready?pod?--all?-n?$FSM_NAMESPACE?--timeout=120s
done
加入集群組
將集群?1-23
?和?1-25
?納入集群?control-plane
?的管理。不管是新集群還是舊集群,如果要進行跨集群的服務調用,都是要加入集群組的。
export?HOST_IP=192.168.1.110
kubectx?k3d-control-plane
sleep?1
PORT=81
for?CLUSTER_NAME?in?1-23?1-25
dokubectl?apply?-f?-?<<EOF
apiVersion:?flomesh.io/v1alpha1
kind:?Cluster
metadata:name:?${CLUSTER_NAME}
spec:gatewayHost:?${HOST_IP}gatewayPort:?${PORT}kubeconfig:?|+
`k3d?kubeconfig?get?${CLUSTER_NAME}?|?sed?'s|^|????|g'?|?sed?"s|0.0.0.0|$HOST_IP|g"`
EOF
((PORT=PORT+1))
done
安裝服務網格
下載 osm CLI
system=$(uname?-s?|?tr?[:upper:]?[:lower:])
arch=$(dpkg?--print-architecture)
release=v1.3.0-beta.1
curl?-L?https://github.com/flomesh-io/osm-edge/releases/download/$release/osm-edge-$release-$system-$arch.tar.gz?|?tar?-vxzf?-
./${system}-${arch}/osm?version
cp?./${system}-${arch}/osm?/usr/local/bin/
將服務網格 osm-edge 安裝到集群?1-23
?和?1-25
。控制平面不處理應用流量,無需安裝。
export?OSM_NAMESPACE=osm-system
export?OSM_MESH_NAME=osm
for?CLUSTER_NAME?in?1-23?1-25
dokubectx?k3d-${CLUSTER_NAME}DNS_SVC_IP="$(kubectl?get?svc?-n?kube-system?-l?k8s-app=kube-dns?-o?jsonpath='{.items[0].spec.clusterIP}')"
osm?install?\--mesh-name?"$OSM_MESH_NAME"?\--osm-namespace?"$OSM_NAMESPACE"?\--set=osm.certificateProvider.kind=tresor?\--set=osm.image.pullPolicy=Always?\--set=osm.sidecarLogLevel=error?\--set=osm.controllerLogLevel=warn?\--timeout=900s?\--set=osm.localDNSProxy.enable=true?\--set=osm.localDNSProxy.primaryUpstreamDNSServerIPAddr="${DNS_SVC_IP}"
done
部署實例應用
在集群?1-23
?的?httpbin
?命名空間(由網格管理,會注入 sidecar)下,部署?httpbin
?應用。這里的?httpbin
?應用由?Pipy[1]?實現,會返回當前的集群名,并提示被網格管理。
NAMESPACE=httpbin
CLUSTER_NAME="1-23"kubectx?k3d-${CLUSTER_NAME}
kubectl?create?namespace?${NAMESPACE}
osm?namespace?add?${NAMESPACE}
kubectl?apply?-n?${NAMESPACE}?-f?-?<<EOF
apiVersion:?apps/v1
kind:?Deployment
metadata:name:?httpbinlabels:app:?pipy
spec:replicas:?1selector:matchLabels:app:?pipytemplate:metadata:labels:app:?pipyspec:containers:-?name:?pipyimage:?flomesh/pipy:latestports:-?containerPort:?8080command:-?pipy-?-e-?|pipy().listen(8080).serveHTTP(new?Message('Hi,?I?am?from?${CLUSTER_NAME}?and?controlled?by?mesh!\n'))
---
apiVersion:?v1
kind:?Service
metadata:name:?httpbin
spec:ports:-?port:?8080targetPort:?8080protocol:?TCPselector:app:?pipy
EOFsleep?3
kubectl?wait?--for=condition=ready?pod?-n?${NAMESPACE}?--all?--timeout=60s
在命名空間?curl
?下部署?curl
?應用,這個命名空間也是被網格管理的,注入的 sidecar 會完全流量的跨集群調度。
export?NAMESPACE=curl
kubectx?k3d-1-23
kubectl?create?namespace?${NAMESPACE}
osm?namespace?add?${NAMESPACE}
kubectl?apply?-n?${NAMESPACE}?-f?-?<<EOF
apiVersion:?v1
kind:?ServiceAccount
metadata:name:?curl
---
apiVersion:?v1
kind:?Service
metadata:name:?curllabels:app:?curlservice:?curl
spec:ports:-?name:?httpport:?80selector:app:?curl
---
apiVersion:?apps/v1
kind:?Deployment
metadata:name:?curl
spec:replicas:?1selector:matchLabels:app:?curltemplate:metadata:labels:app:?curlspec:serviceAccountName:?curlcontainers:-?image:?curlimages/curlimagePullPolicy:?IfNotPresentname:?curlcommand:?["sleep",?"365d"]
EOFsleep?3
kubectl?wait?--for=condition=ready?pod?-n?${NAMESPACE}?--all?--timeout=60s
測試
在集群?1-23
?中的?curl
?向?httpbin
?發送請求。
kubectx?k3d-1-23
curl_client="$(kubectl?get?pod?-n?curl?-l?app=curl?-o?jsonpath='{.items[0].metadata.name}')"kubectl?exec?"${curl_client}"?-n?curl?-c?curl?--?curl?-s?http://httpbin.httpbin:8080/
看到下面的輸出,說明服務正常。
Hi,?I?am?from?1-23?and?controlled?by?mesh!
導出服務
服務的導出就是向集群組注冊服務,執行下面的命令將集群?1-23
?中的服務?httpbin
?注冊到集群組。注意,該命令是在集群?1-23
?中執行的。
export?NAMESPACE_MESH=httpbin
CLUSTER_NAME="1-23"
kubectx?k3d-${CLUSTER_NAME}
kubectl?apply?-f?-?<<EOF
apiVersion:?flomesh.io/v1alpha1
kind:?ServiceExport
metadata:namespace:?${NAMESPACE_MESH}name:?httpbin
spec:serviceAccountName:?"*"rules:-?portNumber:?8080path:?"/${CLUSTER_NAME}/httpbin-mesh"pathType:?Prefix
EOF
sleep?1
此時,我們的系統如下圖所示。

升級遷移
新集群中部署應用
有了新版本的集群之后,我們慢慢向新集群遷移服務。在集群?1-25
?中部署?httpbin
?服務。
NAMESPACE=httpbin
CLUSTER_NAME="1-25"kubectx?k3d-${CLUSTER_NAME}
kubectl?create?namespace?${NAMESPACE}
osm?namespace?add?${NAMESPACE}
kubectl?apply?-n?${NAMESPACE}?-f?-?<<EOF
apiVersion:?apps/v1
kind:?Deployment
metadata:name:?httpbinlabels:app:?pipy
spec:replicas:?1selector:matchLabels:app:?pipytemplate:metadata:labels:app:?pipyspec:containers:-?name:?pipyimage:?flomesh/pipy:latestports:-?containerPort:?8080command:-?pipy-?-e-?|pipy().listen(8080).serveHTTP(new?Message('Hi,?I?am?from?${CLUSTER_NAME}?and?controlled?by?mesh!\n'))
---
apiVersion:?v1
kind:?Service
metadata:name:?httpbin
spec:ports:-?port:?8080targetPort:?8080protocol:?TCPselector:app:?pipy
---
apiVersion:?v1
kind:?Service
metadata:name:?httpbin-${CLUSTER_NAME}
spec:ports:-?port:?8080targetPort:?8080protocol:?TCPselector:app:?pipy
EOFsleep?3
kubectl?wait?--for=condition=ready?pod?-n?${NAMESPACE}?--all?--timeout=60s
導出服務
向集群組注冊集群?1-25
?的服務?httpbin
。
export?NAMESPACE_MESH=httpbin
CLUSTER_NAME="1-25"
kubectx?k3d-${CLUSTER_NAME}
kubectl?apply?-f?-?<<EOF
apiVersion:?flomesh.io/v1alpha1
kind:?ServiceExport
metadata:namespace:?${NAMESPACE_MESH}name:?httpbin
spec:serviceAccountName:?"*"rules:-?portNumber:?8080path:?"/${CLUSTER_NAME}/httpbin-mesh"pathType:?Prefix
EOF
sleep?1
回到集群?1-23
,查看?ServiceImports
?httpbin
,可以看到已經發現了集群?1-25
?注冊的服務。
kubectl?get?serviceimports?httpbin?-n?httpbin?-o?jsonpath='{.spec}'?|?jq
{"ports":?[{"endpoints":?[{"clusterKey":?"default/default/default/1-25","target":?{"host":?"192.168.1.110","ip":?"192.168.1.110","path":?"/1-25/httpbin-mesh","port":?82}}],"port":?8080,"protocol":?"TCP"}],"serviceAccountName":?"*","type":?"ClusterSetIP"
}
但此時在?curl
發送請求,并不會收到集群?1-25
?中的響應。還記得?上篇?中提到過,默認的全局流量策略是?Locality
,因此集群外的節點并不會參與請求的處理。
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
我們創建一個?ActiveActive
?的全局流量策略。注意,這里設置?targets
?的時候我們加上了代表權重的字段?weight
。?將其設置為 10,表示將 1/11 的流量導入集群?1-25
?中,記住本集群的權重總是 100。
kubectx?k3d-1-23
kubectl?apply?-n?httpbin?-f??-?<<EOF
apiVersion:?flomesh.io/v1alpha1
kind:?GlobalTrafficPolicy
metadata:name:?httpbin
spec:lbType:?ActiveActivetargets:-?clusterKey:?default/default/default/1-25weight:?10
EOF
這次我們請求 20 次,結果也正如我們所期望的,只有 1-2 次請求進入集群?1-25
。
for?i?in?{1..20};?do?kubectl?exec?"${curl_client}"?-n?curl?-c?curl?--?curl?-s?http://httpbin.httpbin:8080/?;?done
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
我們將集群?1-25
?的權重設置為?900
,因此會有 90% 的流量進入集群?1-25
。
kubectx?k3d-1-23
kubectl?apply?-n?httpbin?-f??-?<<EOF
apiVersion:?flomesh.io/v1alpha1
kind:?GlobalTrafficPolicy
metadata:name:?httpbin
spec:lbType:?ActiveActivetargets:-?clusterKey:?default/default/default/1-25weight:?900
EOF
然后請求 10 次,會看到 9 次的請求都進入到集群?1-25
,也就是 90% 的流量分流到了其他集群。
for?i?in?{1..10};?do?kubectl?exec?"${curl_client}"?-n?curl?-c?curl?--?curl?-s?http://httpbin.httpbin:8080/?;?done
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-23?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
Hi,?I?am?from?1-25?and?controlled?by?mesh!
接著就是停止集群?1-23
?中的實例,使所有流量都分流到外集群。
kubectl?scale?deploy?httpbin?-n?httpbin?--replicas?0
最后的工作就是慢慢得將?curl
?服務也遷移到新的集群中,進而是所有的服務都遷移完成之后,下線舊的集群。
總結
自維護的 Kubernetes 集群升級不是一件容易的事情,原地升級風險高,尤其是升級控制面。不管是藍綠還是金絲雀升級,都面臨著流量跨集群的問題:流量除了從入口進入還會有其他的途徑,比如消息系統,定時任務等等。
解決了流量的跨集群調度問題后,這些問題都迎刃而解。剩下的問題就是如何讓遷移做到自動化、可控的遷移了。
引用鏈接
[1]
?Pipy:?https://flomesh.io/pipy