Istio 負載均衡的區域感知
Envoy/Istio 1.1 中有個有趣的新特性:負載均衡提供了區域感知 的能力。簡單說來,就是在分割槽部署的較大規模的叢集,或者公有云上,Istio 負載均衡可以根據節點的區域標籤,對呼叫目標做出就近選擇。在跨區部署的應用中,原始的 Kubernetes 負載均衡可能會把來自 A 區的請求傳送給遠在 B 區的服務,造成高成本的跨區呼叫。要縮減這種損耗,通常都需要實現更多的邏輯,Istio 的區域感知特性在某種程度上提供了一種解決辦法。
準備工作
接下來首先做一些瑣碎的安裝工作,這裡選擇了常見的 GCP 作為測試環境,Istio 版本為 1.1.2。
-
在 GCP 的
us-central1
建立一個區域叢集:$ gcloud beta container clusters create "standard-cluster-1" \ ... --no-enable-basic-auth \ --cluster-version "1.12.6-gke.10" \ --machine-type "n1-standard-1" --image-type "COS" \ ... --num-nodes "2" \ --no-enable-cloud-logging --no-enable-cloud-monitoring \ ...--no-enable-ip-alias \ --addons HorizontalPodAutoscaling \ --enable-autoupgrade --enable-autorepair
-
獲取本地認證,為
kubectl
生成context
:$ gcloud beta container clusters get-credentials \ standard-cluster-1 --region us-central1 \ --project dustise-mesh-lab
-
檢視節點標籤,這裡會看到不同的節點會使用區域標籤 進行標識:
$ kubectl get nodes --show-labels ... failure-domain.beta.kubernetes.io/region=us-central1,failure-domain.beta.kubernetes.io/zone=us-central1-a ... failure-domain.beta.kubernetes.io/region=us-central1,failure-domain.beta.kubernetes.io/zone=us-central1-c ...
-
為 Istio 準備 RBAC:
$ kubectl create clusterrolebinding cluster-admin-binding \ --clusterrole=cluster-admin \ --user=$(gcloud config get-value core/account)
-
初始化 Istio CRD:
$ kubectl create namespace istio-system $ helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f - configmap/istio-crd-10 created configmap/istio-crd-11 created serviceaccount/istio-init-service-account created clusterrole.rbac.authorization.k8s.io/istio-init-istio-system created clusterrolebinding.rbac.authorization.k8s.io/istio-init-admin-role-binding-istio-system created job.batch/istio-init-crd-10 created job.batch/istio-init-crd-11 created
-
安裝 Isto :
$ helm template install/kubernetes/helm/istio \ --name istio --namespace istio-system \ --values install/kubernetes/helm/istio/values-istio-demo-auth.yaml | kubectl apply -f - ...... handler.config.istio.io/kubernetesenv created rule.config.istio.io/kubeattrgenrulerule created rule.config.istio.io/tcpkubeattrgenrulerule created kubernetes.config.istio.io/attributes created destinationrule.networking.istio.io/istio-policy created destinationrule.networking.istio.io/istio-telemetry created
-
標記
default
名稱空間,啟動自動注入:
$ kubectl label namespaces default istio-injection=enabled --overwrite kubectl namespace/default labeled
部署應用
為了方便演示,我們給慣用的 flaskapp 和 sleep 加上NodeSelector
,要求按照版本分佈到不同區域的節點上,例如:
nodeSelector: failure-domain.beta.kubernetes.io/zone: us-central1-f
標籤內容可以參照上文kubectl get nodes --show-labels
的顯示結果。
修改了部署清單之後,就可以部署了:
$ kubectl apply -f flaskapp/flaskapp.istio.yaml service/flaskapp created deployment.extensions/flaskapp-v1 created deployment.extensions/flaskapp-v2 created $ kubectl apply -f sleep/sleep.istio.yaml service/sleep created deployment.extensions/sleep-v1 created deployment.extensions/sleep-v2 created deployment.extensions/sleep-v3 created
稍候片刻,檢視部署結果:
$ kubectl get pods -o wide NAMEREADYSTATUSRESTARTSAGEIPNODENOMINATED NODE flaskapp-v1-b9644bd75-g82nj2/2Running092m10.40.4.9gke-standard-cluster-1-default-pool-0570ecb1-lm7q<none> flaskapp-v2-77d648fbd-cvfql2/2Running092m10.40.3.5gke-standard-cluster-1-default-pool-f2347d89-q79k<none> sleep-v1-84c85c8946-c7bvc2/2Running091m10.40.1.3gke-standard-cluster-1-default-pool-0570ecb1-1qnq<none> sleep-v2-57cf55db78-vrvtc2/2Running092m10.40.3.7gke-standard-cluster-1-default-pool-f2347d89-q79k<none>
和上文比較,可以看到,sleep 和 flaskapp 的 v1、v2 兩個版本,分別執行在us-central1-a
和us-central1-f
中。
驗證路由的區域感知功能
接下來分別從網格內部和 Ingress Gateway 來驗證這一功能。
服務網格內部請求
$ kubectl exec -it -c sleep sleep-v1-84c85c8946-c7bvc bash # for i in {1..10}; do http --bodyhttp://flaskapp/env/version ; done v1 v2 v2 v1 ...
可以看到,請求被隨機分配到不同的版本,也就是說,此時的呼叫是無視分割槽的。接下來我們設定 Pilot 的環境變數,啟用區域感知功能,過程很簡單,給它的 Pod 加入環境變數PILOT_ENABLE_LOCALITY_LOAD_BALANCING
,並任意賦值即可,例如:
- name: PILOT_TRACE_SAMPLING value: "100" ... - name: PILOT_ENABLE_LOCALITY_LOAD_BALANCING value: "1" ...
再次進入 Pod 訪問 flaskapp 服務:
$ kubectl exec -it -c sleep sleep-v1-84c85c8946-c7bvc bash # for i in {1..10}; do http --bodyhttp://flaskapp/env/version ; done v1 v1 v1 ... # exit $ kubectl exec -it -c sleep sleep-v2-57cf55db78-vrvtc bash # for i in {1..10}; do http --bodyhttp://flaskapp/env/version ; done v2 v2 v2 ...
可以看到,果然按照我們預想的情況,不同區域的請求,會交由不同區域的服務來進行響應。如果此時刪除同區的目標負載,會發現開始平均訪問其它區的服務。
Ingress 閘道器
Ingress 閘道器控制器在網格內同樣也會分配到不同的節點上,因此也同樣會受到區域的影響。例如我們為 flaskapp 建立一個VirtualService
+Gateway
的組合,引入外部流量:
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: flaskapp-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - "flaskapp.example.com" --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin spec: hosts: - "flaskapp.example.com" gateways: - flaskapp-gateway http: - route: - destination: port: number: 80 host: flaskapp
提交後,可以在外使用curl --resolve
來驗證:
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') $ for i in {1..10}; do curl --resolve flaskapp.example.com:80:$INGRESS_HOST http://flaskapp.example.com/env/version ; done v3v3v3v3v3v3v3v3v3v3
可以看到,對 Ingress Gateway 進入的流量,區域感知功能也是同樣生效的。
區域間分流
如果只是簡單的就近原則,雖然方便,但也難免有些枯燥,例如我的叢集中的三個分割槽之間存在優先次序,或者強行指派一個區的請求需要由指定的其它分割槽的服務進行處理,又該怎樣呢?
istio-system 中有個叫做 istio 的 configmap,其中包含了Istio 的一些核心配置 ,裡面的LocalityLoadBalancerSetting ,包含了對區域感知負載均衡的一些行為配置。例如我們分配所有分割槽的流量,都分配到前面兩個區域:
localityLbSetting: distribute: - from: us-central1/us-central1-a/* to: us-central1/us-central1-a/*: 90 us-central1/us-central1-b/*: 10 - from: us-central1/us-central1-b/* to: us-central1/us-central1-a/*: 90 us-central1/us-central1-b/*: 10 - from: us-central1/us-central1-f/* to: us-central1/us-central1-a/*: 90 us-central1/us-central1-b/*: 10
應用之後,重啟 Galley、Pilot、Injector,並重新注入應用,再次在不同分割槽的 sleep 容器中進行測試。會發現其中的請求呈現了符合配置要求的分配,並且沒有傳送到 us-central1-b 區。
事實上本次測試,並沒有發現比率生效,僅達到有或無的區別。
結論
目前的分割槽域分流功能似乎還有些問題,但是不失為一個新的服務親和思路。 分割槽是基於 Kubernetes Node 標籤完成的,通過對標籤的調整(例如機櫃、樓層),能夠比較方便的在無侵入 的情況下,實現就近呼叫,對服務的跨區 HA,有一定的輔助作用。