本教程已加入 Istio 系列:https://istio.whuanle.cn
4, 流量管理
主要演示了使用 Istio Gateway、VirtualService 對外暴露服務的訪問地址 ,以及基於 Istio 實現可觀察性的 Kiali 組件。讓我們回在上一章中部署的 bookinfo 示例已經學習了什麼:
-
使用 Istio Gateway 創建 “站點”;
-
使用 Istio VistualService 暴露 Kubernetes Service,並指定暴露的路由後綴。
-
使用 Kiali 收集服務間的指標。
通過快速練習,我們學到了如何在 Istio 中暴露服務,以及只暴露部分 API。可是隻暴露服務並沒有太大的用處,因爲市面上各種網關都可以做到,並且功能更加豐富。
在微服務系統中,我們會碰到很多關於服務治理的問題,下面是筆者從 ChatGPT 中獲取到的一些關於服務治理常見的問題。
- 服務發現:在動態的微服務環境中,如何實時地發現和註冊新的服務實例?
- 負載均衡:如何在服務實例之間有效地分配請求流量,以實現高性能和高可用性?
- 容錯處理:如何處理服務之間的故障,例如服務實例故障、網絡故障等?
- 流量管理:如何控制服務間的請求流量,例如請求路由、流量分割、金絲雀發佈等?
- 服務監控:如何實時地監控服務的性能和健康狀況?
- 鏈路追蹤:如何跟蹤和分析分佈式系統中的請求調用鏈?
- 安全性:如何確保服務之間的通信安全,例如身份驗證、授權和加密?
- 策略執行:如何實施和管理服務治理的策略,例如限流、熔斷、訪問控制等?
- 配置管理:如何在服務之間統一和動態地管理配置信息?
- 服務編排:如何協調服務之間的交互,以實現複雜的業務流程?
前面的章節提到過, Istio 是服務治理的工具。所以,在本章中,將會介紹 Istio 的流量管理能力,來解決微服務中關於服務治理的部分問題。
Istio 的流量管理模型源於和服務一起部署的 Envoy,網格內 Pod 中的應用發送和接收的所有流量(data plane流量)都經由 Envoy,而應用本身不需要對服務做任何的更改,這對業務來說是非侵入式的,卻可以實現強大的流量管理。
基於版本的路由配置
在第三章訪問的 http://192.168.3.150:30666/productpage?u=normal 地址中,我們每次刷新得到的結果都不一樣。
因爲 Kubenetes Service 通過標籤中的 app: reviews
來綁定對應的 Pod,正常情況下,Kubernetes 會將客戶端的請求以輪詢的方式轉發到 Deployment 中的 Pod,VirtualService 也是如此。
selector:
app: reviews
三個不同的 Reviews Deployment 都帶有相同的 app: reviews
標籤,所以 Service 會把它們的 Pod 放到一起,VirtualService 會將流量以輪詢的方式分發到每個版本中。
labels:
app: reviews
version: v1
labels:
app: reviews
version: v2
labels:
app: reviews
version: v3
所以,流量進入 Reviews VirtualService 之後,會被 Kubernetes 均衡地分配到各個 Pod 中。接下來我們將會使用按照版本的形式將流量劃分到不同的版本服務中。
Istio 通過 DestinationRule 定義了應用的版本,使用 Istio DestinationRule 設置 reviews v1/v2/v3 版本的定義如下所示:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
看看就行,不用執行命令。
這看起來非常簡單, DestinationRule 沒有什麼特別的配置。通過 name: v1
定義版本,通過 labels
指定哪些符合條件的 Pod 劃分到這個版本中。
接下來我們創建一個 yaml 文件,給書店微服務的四個應用都創建一個 DestinationRule 。
service_version.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage
subsets:
- name: v1
labels:
version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: ratings
spec:
host: ratings
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v2-mysql
labels:
version: v2-mysql
- name: v2-mysql-vm
labels:
version: v2-mysql-vm
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: details
spec:
host: details
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
kubectl -n bookinfo apply -f service_version.yaml
執行命令查詢更多信息
$> kubectl get destinationrules -o wide -n bookinfo
NAME HOST AGE
details details 59s
productpage productpage 60s
ratings ratings 59s
reviews reviews 59s
接着我們爲三個微服務 productpage、ratings、details 定義 Istio VirtualService,因爲它們都只有 v1 版本,所以在 VirtualService 中直接將流量轉發的 v1 版本即可。
3vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage
http:
- route:
- destination:
host: productpage
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: details
spec:
hosts:
- details
http:
- route:
- destination:
host: details
subset: v1
---
kubectl -n bookinfo apply -f 3vs.yaml
host: reviews
使用 Service 的名稱。如果這樣填寫,該規則只能應用於當前命名空間的 Service,如果需要將流量引導到其它命名空間的 Service,則需要使用完整的 DNS 路徑,如:reviews.bookinfo.svc.cluster.local
。
而對於 reviews 服務,我們在 VirtualService 只將流量轉發到 v1 版本,忽略 v2、v3。
reviews_v1_vs.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
kubectl -n bookinfo apply -f reviews_v1_vs.yaml
然後我們查看所有的 VirtualService 。
$> kubectl get vs -n bookinfo
NAME GATEWAYS HOSTS AGE
bookinfo ["bookinfo-gateway"] ["*"] 76m
details ["details.bookinfo.svc.local"] 103m
productpage ["productpage.bookinfo.svc.local"] 103m
ratings ["ratings.bookinfo.svc.local"] 103m
reviews ["reviews"] 103m
之後,無論怎麼刷新 http://192.168.3.150:32666/productpage ,右側的 Book Reviews 都不會顯示星星,因爲流量都轉發到 v1 版本中,而 v1 版本是不會有星星的。
Istio 起作用的原理大概是這樣的,首先是 istio-ingressgateway 將流量轉發到 bookinfo 網關中,然後 productpage VirtualService 根據對應的路由規則,判斷是否放通流量,最後轉發到對應的 productpage 應用中。接着 productpage 需要訪問其它服務例如 reviews,發出的請求會經過 Envoy,Envoy 根據配置的 VirtualService 規則,直接將流量轉發到對應的 Pod 中。
基於 Http header 的路由配置
基於 Http header 的轉發,是通過 HTTP 請求中的 header 值,將流量轉發到對應的 Pod 中。
在本節中,我們將會通過配置 DestinationRule ,將 header 帶有 end-user: jason
的流量轉發到 v2 中,其它情況依然轉發到 v1 版本。
將 reviews 的 DestinationRule 描述文件的內容改成:
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
完整的 YAML如下:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
kubectl -n bookinfo apply -f reviews_v2_vs.yaml
然後在頁面中的右上角點擊 Sign in
進行登錄,賬號密碼都是 jason。
此時 Book Reviews 一直顯示星星。
如果我們查看 productpage 的日誌:
productpage 將這個 header 頭轉發給 http://reviews:9080/
,然後流量經過 Envoy 時,Envoy 檢測到 Http header 中帶有 end-user ,通過規則決定將流量轉發到 reviews v2。在這個過程中並不需要 Service 參與。
- 經過上面的配置,下面是請求的流程:
productpage
→reviews:v2
→ratings
(針對jason
用戶)productpage
→reviews:v1
(其他用戶)
當然,我們也可以通過 URL 的方式劃分流量,例如 /v1/productpage
、/v2/productpage
等,在本章中,就不再贅述這些了,讀者可以從官方文檔中瞭解更多。
故障注入
故障注入是 Istio 模擬故障的一種手段,通過故障注入我們可以模擬一個服務出現故障的情況,然後從實際請求中看到出現故障時,整個微服務是否會亂套。通過故意在服務間通信中引入錯誤,例如延遲、中斷或錯誤的返回值,可以測試系統在不理想的運行狀況下的表現。這有助於發現潛在的問題,提高系統的健壯性和可靠性。
將前面部署的 ratings 的 VirtualService,改造一下。
ratings_delay.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
delay:
percentage:
value: 100.0
fixedDelay: 7s
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
kubectl -n bookinfo apply -f ratings_delay.yaml
再次訪問網頁,發現評論區已經加載不出來了,因爲超時。
兩種故障注入
在 Istio 的 VirtualService 中,fault
配置用於注入故障,以模擬和測試應用程序在出現問題時的行爲。主要有兩種類型的故障注入:延遲(delay)和異常(abort)。
延遲故障注入
延遲故障注入用於在應答之前向請求添加指定的延遲時間。這可以測試應用程序在網絡延遲或服務響應緩慢的情況下的表現。以下是一個示例,演示瞭如何在 VirtualService 中添加一個延遲故障注入:
http:
- fault:
delay:
percentage:
value: 100.0
fixedDelay: 5s
延遲(delay)故障注入有兩個主要屬性。
percentage
: 表示注入延遲的概率,取值範圍爲 0.0 到 100.0。例如,50.0 表示有 50% 的概率注入延遲。fixedDelay
: 表示注入的固定延遲時間,通常以秒(s)或毫秒(ms)爲單位。例如,5s
表示 5 秒延遲。
延遲故障注入的示例:
fault:
delay:
percentage:
value: 50.0
fixedDelay: 5s
在這個示例中,delay
配置了一個 50% 概率發生的 5 秒固定延遲。
異常故障注入
異常故障注入用於模擬請求失敗的情況,例如 HTTP 錯誤狀態碼或 gRPC 狀態碼。這可以幫助測試應用程序在遇到故障時的恢復能力。以下是一個示例,演示瞭如何在 VirtualService 中添加一個異常故障注入:
http:
- fault:
abort:
percentage:
value: 100.0
httpStatus: 503
也可以將延遲故障注入 和 異常故障注入兩者放在一起同時使用。
http:
- fault:
delay:
percentage:
value: 50.0
fixedDelay: 5s
abort:
percentage:
value: 50.0
httpStatus: 503
雖然放在一起使用,但是並不會兩種情況同時發生,而是通過 percentage 來配置出現的概率。
異常(abort)故障注入有四個主要屬性。
percentage
: 表示注入異常的概率,取值範圍爲 0.0 到 100.0。例如,50.0 表示有 50% 的概率注入異常。httpStatus
: 表示要注入的 HTTP 錯誤狀態碼。例如,503
表示 HTTP 503 錯誤。grpcStatus
: 表示要注入的 gRPC 錯誤狀態碼。例如,UNAVAILABLE
表示 gRPC 服務不可用錯誤。http2Error
: 表示要注入的 HTTP/2 錯誤。例如,CANCEL
表示 HTTP/2 流被取消。
異常故障注入的示例:
fault:
abort:
percentage:
value: 50.0
httpStatus: 503
實驗完成後,別忘了將 ratings 服務恢復正常。
kubectl -n bookinfo apply -f 3vs.yaml
比例分配流量
使用下面的配置,可以把 50% 的流量分配給 reviews:v1
和 reviews:v3
:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 50
- destination:
host: reviews
subset: v3
weight: 50
刷新瀏覽器中的 /productpage
頁面,大約有 50% 的機率會看到頁面中帶 紅色 星級的評價內容。這是因爲 reviews
的 v3
版本可以訪問帶星級評價,但 v1
版本不能。
請求超時
不同編程語言都會提供 http client 類庫,程序發起 http 請求時,程序本身一般都會有超時時間,超過這個時間,代碼會拋出異常。例如網關如 nginx、apisix 等,也有 http 連接超時的功能。
在 Istio 中,服務間的調用由 Istio 進行管理,可以設置超時斷開。
我們可以爲 reviews 服務設置 http 入口超時時間,當其它服務 請求reviews 服務時,如果 http 請求超過 0.5s,那麼 Istio 立即斷開 http 請求。
reviews_timeout.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
timeout: 0.5s
kubectl -n bookinfo apply -f reviews_timeout.yaml
因爲 reviews 依賴於 ratings 服務,爲了模擬這種超時情況,我們可以給 ratings 注入延遲故障。這樣 ratings 會給所有請求都延遲 2s 纔會返回響應,但是 reviews 要求所有請求 reviews 的流量在 0.5s 內響應。
給 ratings 設置延遲故障:
kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
delay:
percent: 100
fixedDelay: 2s
route:
- destination:
host: ratings
subset: v1
EOF
我們再次刷新頁面。
注:因爲 productpage 是 Python 編寫的,其代碼中設置了請求失敗後自動重試一次,因此頁面刷新後 1 s 後纔會完成,而不是 0.5s。
還有一點關於 Istio 中超時控制方面的補充說明,除了像本文一樣在路由規則中進行超時設置之外,還可以進行請求一級的設置,只需在應用的對外請求中加入 x-envoy-upstream-rq-timeout-ms
請求頭即可。在這個請求頭中的超時設置單位是毫秒而不是秒。
現在讓我們將本小節的故障清理掉,恢復正常的微服務。
kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 50
- destination:
host: reviews
subset: v3
weight: 50
EOF
kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- route:
- destination:
host: ratings
subset: v1
EOF
熔斷
什麼是熔斷
熔斷(Circuit Breaking)是微服務架構中的一種重要的彈性設計模式,在微服務環境中,不同的服務存在依賴關係,當其中一個依賴的服務出現問題時,可能導致請求積壓,從而影響到其他服務和整個系統的穩定性。比如說,B 服務來了 100 個請求,B 需要請求 100 次 A 服務,但是 A 服務故障了,那麼每次失敗時都會重試一次,那麼整體上就一共請求了 200 次。這樣就會造成很大的浪費。而熔斷器可以檢測到這種情況,當檢測到 A 服務故障之後,一段時間內所有對 A 的請求都會直接返回錯誤。
熔斷器模式的工作原理如下:
-
正常狀態:熔斷器處於關閉狀態,允許請求通過(熔斷器會監控請求的成功和失敗率)。
-
故障檢測:當失敗率達到預先定義的閾值時,熔斷器就會啓動。
-
熔斷狀態:熔斷器處於打開狀態時,將拒絕所有新的請求,並返回錯誤響應。這可以防止故障級聯和給故障服務帶來更多的壓力。
-
恢復狀態:在一段時間後,熔斷器會進入半打開狀態,允許一部分請求通過。如果這些請求成功,則熔斷器將返回到關閉狀態;如果仍然存在失敗請求,則熔斷器繼續保持打開狀態。
使用熔斷器模式可以提高微服務系統的彈性和穩定性。這些工具提供了熔斷器模式的實現,以及其他彈性設計模式,如負載均衡、重試和超時等。
創建 httpbin 服務
接下來本節將會使用一個 httpbin 服務,這個服務代碼可以在 istio 官方倉庫中找到: https://github.com/istio/istio/tree/release-1.17/samples/httpbin
創建一個 httpbin 服務。
httpbin.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
serviceAccountName: httpbin
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
ports:
- containerPort: 80
kubectl -n bookinfo apply -f httpbin.yaml
這裏使用的 NodePort 只是爲了分別預覽訪問,後續還需要通過 Gateway 來實驗熔斷。
然後查看 Service 列表。
通過瀏覽器打開對應的服務。
接着給 httpbin 創建一個 DestinationRule ,裏面配置了熔斷規則。
httpbin_circurt.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: httpbin
spec:
host: httpbin
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
kubectl -n bookinfo apply -f httpbin_circuit.yaml
DestinationRule
(目標規則)用於定義訪問特定服務的流量策略。DestinationRule
配置中的 trafficPolicy
屬性允許爲服務指定全局的流量策略,這些策略包括負載均衡設置、連接池設置、異常檢測等。
另外,我們在創建熔斷時也可以設置重試次數。
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx
創建訪問者服務
在 Istio 服務網格環境下,流量進入網格後會被 Envoy 攔截,接着根據相應的配置實現路由,熔斷也是在 Envoy 之間實現的,只有流量經過 Envoy ,纔會觸發 Istio 的熔斷機制。
上一小節中我們部署了 httpbin 應用, 但是熔斷是服務之間通訊出現的,所以我們還需要部署一個服務請求 httpbin,才能觀察到熔斷過程。Istio 官方推薦使用 fortio 。
部署 fortio 的 YAML 如下:
fortio_deploy.yaml
apiVersion: v1
kind: Service
metadata:
name: fortio
labels:
app: fortio
service: fortio
spec:
ports:
- port: 8080
name: http
selector:
app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fortio-deploy
spec:
replicas: 1
selector:
matchLabels:
app: fortio
template:
metadata:
annotations:
# This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
# in addition to the stats normally served by Istio. The Circuit Breaking example task
# gives an example of inspecting Envoy stats via proxy config.
proxy.istio.io/config: |-
proxyStatsMatcher:
inclusionPrefixes:
- "cluster.outbound"
- "cluster_manager"
- "listener_manager"
- "server"
- "cluster.xds-grpc"
labels:
app: fortio
spec:
containers:
- name: fortio
image: fortio/fortio:latest_release
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http-fortio
- containerPort: 8079
name: grpc-ping
kubectl -n bookinfo apply -f fortio_deploy.yaml
部署 fortio 之後,我們進入到 fortio 容器中,執行命令請求 httpbin。
執行命令獲取 fortio 的 Pod 名稱:
export FORTIO_POD=$(kubectl get pods -n bookinfo -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
然後讓 Pod 容器執行命令:
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get
如果上面的命令執行沒問題的話,我們可以通過下面的命令對 httpbin 服務進行大量請求,並且分析請求統計結果。
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
在控制檯中可以看到請求返回 200 和 503 的比例。
創建 productpage 熔斷
在前面的小節中,我們使用了 httpbin 進行熔斷實驗,當然我們也可以給那些暴露到集羣外的應用創建熔斷。
這裏我們繼續使用之前的 bookinfo 微服務的 productpage 應用。
給 productpage 創建一個熔斷規則:
productpage_circuit.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage
subsets:
- name: v1
labels:
version: v1
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
kubectl -n bookinfo apply -f productpage_circuit.yaml
然後我們使用 fortio 測試 productpage 應用,從 istio gateway 入口進行訪問。
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://192.168.3.150:32666/productpage
然後刪除 productpage 的熔斷配置,重新恢復成一個正常的應用。
kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage
subsets:
- name: v1DestinationRule
labels:
version: v1
EOF
重新執行命令:
kubectl -n bookinfo exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 20 -loglevel Warning http://192.168.3.150:32666/productpage
通過打印的日誌可以看出,不會再有 503 錯誤。
此時訪問 http://192.168.3.150:32666/productpage ,頁面也應恢復正常。
清理
本文明實驗之後,可以執行命令清理以下服務:
然後我們清理 fortio:
kubectl -n bookinfo delete svc fortio
kubectl -n bookinfo delete deployment fortio-deploy
清理示例程序:
kubectl -n bookinfo delete destinationrule httpbin
kubectl -n bookinfo delete sa httpbin
kubectl -n bookinfo delete svc httpbin
kubectl -n bookinfo delete deployment httpbin