Istio(五):使用服務網格Istio進行流量路由

2022-10-29 18:00:16

一.模組概覽

在本模組中,我們將開始使用 Istio 服務網格在服務之間進行流量路由。使用流量路由,我們將學習如何部署新版本的服務,並在已釋出的生產版本的服務旁邊執行,而不干擾生產流量。隨著兩個服務版本的部署,我們將逐步釋出(金絲雀釋出)新版本,並開始將一定比例的傳入流量路由到最新版本。

使用服務網格Istio進行流量路由的前提是已經安裝好了istio,關於istio的安裝部署,請檢視部落格《Istio(二):在Kubernetes(k8s)叢集上安裝部署istio1.14》https://www.cnblogs.com/renshengdezheli/p/16836404.html

二.系統環境

伺服器版本 docker軟體版本 Kubernetes(k8s)叢集版本 Istio軟體版本 CPU架構
CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 v1.21.9 Istio1.14 x86_64

Kubernetes叢集架構:k8scloude1作為master節點,k8scloude2,k8scloude3作為worker節點

伺服器 作業系統版本 CPU架構 程序 功能描述
k8scloude1/192.168.110.130 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico k8s master節點
k8scloude2/192.168.110.129 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker節點
k8scloude3/192.168.110.128 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker節點

三.簡單路由

3.1 簡單路由

我們可以使用 VirtualService 資源在 Istio 服務網格中進行流量路由通過 VirtualService,我們可以定義流量路由規則,並在使用者端試圖連線到服務時應用這些規則。例如向 dev.example.com 傳送一個請求,最終到達目標服務。

讓我們看一下在叢集中執行 customers 應用程式的兩個版本(v1 和 v2)的例子。我們有兩個 Kubernetes 部署,customers-v1customers-v2。屬於這些部署的 Pod 有一個標籤 version:v1 或一個標籤 version:v2 的設定。

路由到 Customers如下所示:

我們想把 VirtualService 設定為將流量路由到應用程式的 V1 版本。70% 的傳入流量應該被路由到 V1 版本。30% 的請求應該被傳送到應用程式的 V2 版本。

下面是上述情況下 VirtualService 資源的yaml檔案:

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers-route
 spec:
   hosts:
   - customers.default.svc.cluster.local
   http:
   - name: customers-v1-routes
     route:
     - destination:
         host: customers.default.svc.cluster.local
         subset: v1
       weight: 70
   - name: customers-v2-routes
     route:
     - destination:
         host: customers.default.svc.cluster.local
         subset: v2
       weight: 30

hosts 欄位下,我們要定義流量被傳送到的目標主機。在我們的例子中,這就是 customers.default.svc.cluster.local Kubernetes 服務。

下一個欄位是 http,這個欄位包含一個 HTTP 流量的路由規則的有序列表。destination 是指服務登入檔中的一個服務,也是路由規則處理後請求將被傳送到的目的地Istio 的服務登入檔包含所有的 Kubernetes 服務,以及任何用 ServiceEntry資 源宣告的服務

我們也在設定每個目的地的權重(weight權重等於傳送到每個子集的流量的比例。所有權重的總和應該是 100。如果我們有一個單一的目的地,權重被假定為 100。

通過 gateways 欄位,我們還可以指定我們想要繫結這個 VirtualService 的閘道器名稱。比如說:

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers-route
 spec:
   hosts:
     - customers.default.svc.cluster.local
   gateways:
     - my-gateway
   http:
     ...

上面的 YAML 檔案將 customers-route 這個VirtualService 繫結到名為 my-gateway 的閘道器上。這有效地暴露了通過閘道器的目標路由

當一個 VirtualService 被附加到一個閘道器上時,只允許在閘道器資源中定義的主機。下表解釋了閘道器資源中的 hosts 欄位如何作為過濾器,以及 VirtualService 中的 hosts 欄位如何作為匹配。

Gateway Hosts VirtualService Hosts 行為
* customers.default.svc.cluster.local 流量通過 VirtualService 傳送,因為 * 允許所有主機
customers.default.svc.cluster.local customers.default.svc.cluster.local hosts 匹配,流量將被傳送
hello.default.svc.cluster.local customers.default.svc.cluster.local hosts 不匹配,無效
hello.default.svc.cluster.local ["hello.default.svc.cluster.local", "customers.default.svc.cluster.local"] 只允許 hello.default.svc.cluster.local。它絕不允許 customers.default.svc.cluster.local 通過閘道器。然而,這仍然是一個有效的設定,因為 VirtualService 可以連線到第二個閘道器,該閘道器的 hosts 欄位中包含 *.default.svc.cluster.local

四.Subset和DestinationRule

4.1 Subset 和 DestinationRule

目的地指的是不同的子集(subset)或服務版本通過子集,我們可以識別應用程式的不同變體。在我們的例子中,我們有兩個子集,v1v2,它們對應於我們 customer 服務的兩個不同版本。每個子集都使用鍵/值對(標籤)的組合來確定哪些 Pod 要包含在子集中。我們可以在一個名為 DestinationRule 的資源型別中宣告子集

下面是定義了兩個子集的 DestinationRule 資源的yaml檔案。

 apiVersion: networking.istio.io/v1alpha3
 kind: DestinationRule
 metadata:
   name: customers-destination
 spec:
   host: customers.default.svc.cluster.local
   subsets:
   - name: v1
     labels:
       version: v1
   - name: v2
     labels:
       version: v2

讓我們看看我們可以在 DestinationRule 中設定的流量策略。

4.2 DestinationRule 中的流量策略

通過 DestinationRule,我們可以定義設定,如負載均衡設定、連線池大小、區域性異常檢測等,在路由發生後應用於流量。我們可以在 trafficPolicy 欄位下設定流量策略。以下是這些設定:

  • 負載均衡器設定
  • 連線池設定
  • 區域性異常點檢測
  • 使用者端 TLS 設定
  • 埠流量策略

4.2.1 負載均衡器設定

通過負載均衡器設定,我們可以控制目的地使用哪種負載均衡演演算法。下面是一個帶有流量策略的 DestinationRule 的例子,它把目的地的負載均衡演演算法設定為 round-robin(輪詢)。

 apiVersion: networking.istio.io/v1alpha3
 kind: DestinationRule
 metadata:
   name: customers-destination
 spec:
   host: customers.default.svc.cluster.local
   trafficPolicy:
     loadBalancer:
       simple: ROUND_ROBIN
   subsets:
   - name: v1
     labels:
       version: v1
   - name: v2
     labels:
       version: v2

我們還可以設定基於雜湊的負載均衡,並根據 HTTP 頭、cookies 或其他請求屬性提供對談親和性。下面是一個流量策略的片段,它設定了基於雜湊的負載均衡,並使用一個叫做 location 的 cookie 來實現親和力。

 trafficPolicy:
   loadBalancer:
     consistentHash:
       httpCookie:
         name: location
         ttl: 4s

4.2.2 連線池設定

這些設定可以在 TCP 和 HTTP 層面應用於上游服務的每個主機,我們可以用它們來控制連線量

下面是一個片段,顯示了我們如何設定對服務的並行請求的限制

 spec:
   host: myredissrv.prod.svc.cluster.local
   trafficPolicy:
     connectionPool:
       http:
         http2MaxRequests: 50

4.2.3 異常點檢測

異常點檢測是一個斷路器的實現,它跟蹤上游服務中每個主機(Pod)的狀態。如果一個主機開始返回 5xx HTTP 錯誤,它就會在預定的時間內被從負載均衡池中彈出。對於 TCP 服務,Envoy 將連線超時或失敗計算為錯誤

下面是一個例子,它設定了 500 個並行的 HTTP2 請求(http2MaxRequests)的限制,每個連線不超過 10 個請求(maxRequestsPerConnection)到該服務。每 5 分鐘掃描一次上游主機(Pod)(interval),如果其中任何一個主機連續失敗 10 次(contracticalErrors),Envoy 會將其彈出 10 分鐘(baseEjectionTime)。

 trafficPolicy:
   connectionPool:
     http:
       http2MaxRequests: 500
       maxRequestsPerConnection: 10
   outlierDetection:
     consecutiveErrors: 10
     interval: 5m
     baseEjectionTime: 10m

4.2.4 使用者端 TLS 設定

包含任何與上游服務連線的 TLS 相關設定。下面是一個使用提供的證書設定 mTLS 的例子。

 trafficPolicy:
   tls:
     mode: MUTUAL
     clientCertificate: /etc/certs/cert.pem
     privateKey: /etc/certs/key.pem
     caCertificates: /etc/certs/ca.pem

其他支援的 TLS 模式有 DISABLE沒有 TLS 連線),SIMPLE在上游端點發起 TLS 連線),以及 ISTIO_MUTUAL(與 MUTUAL 類似,使用 Istio 的 mTLS 證書)。

4.2.5 埠流量策略

註釋:LEAST_CONN表示最少連線,ROUND_ROBIN表示輪詢,輪詢演演算法是把請求平均的轉發給各個後端,使它們的負載大致相同。這有個前提,就是每個請求所佔用的後端時間要差不多,如果有些請求佔用的時間很長,會導致其所在的後端負載較高。在這種場景下,把請求轉發給連線數較少的後端,能夠達到更好的負載均衡效果,這就是least_conn演演算法。least_conn演演算法很簡單,首選遍歷後端叢集,比較每個後端的conns/weight,選取該值最小的後端。如果有多個後端的conns/weight值同為最小的,那麼對它們採用加權輪詢演演算法。

使用 portLevelSettings 欄位,我們可以將流量策略應用於單個埠。比如說:

 trafficPolicy:
   portLevelSettings:
   - port:
       number: 80
     loadBalancer:
       simple: LEAST_CONN
   - port:
       number: 8000
     loadBalancer:
       simple: ROUND_ROBIN

五.高階路由

5.1 高階路由

在前面,我們瞭解瞭如何利用流量的比例(weight 欄位)在多個子集之間進行流量路由在某些情況下,純粹的基於權重的流量路由或分割已經足夠了。然而,在有些場景和情況下,我們可能需要對流量如何被分割和轉發到目標服務進行更細化的控制

Istio 允許我們使用傳入請求的一部分,並將其與定義的值相匹配。例如,我們可以匹配傳入請求的 URI 字首,並基於此路由流量。

屬性 描述
uri 將請求 URI 與指定值相匹配
schema 匹配請求的 schema(HTTP、HTTPS...)
method 匹配請求的 method(GET、POST...)
authority 匹配請求 authority 頭
headers 匹配請求頭。頭資訊必須是小寫的,並以連字元分隔(例如:x-my-request-id)。注意,如果我們使用頭資訊進行匹配,其他屬性將被忽略(urischemamethodauthority)。

上述每個屬性都可以用這些方法中的一種進行匹配:

  • 精確匹配:例如,exact: "value" 匹配精確的字串
  • 字首匹配:例如,prefix: "value" 只匹配字首
  • 正則匹配:例如,regex:"value" 根據 ECMAscript 風格的正則進行匹配

例如,假設請求的 URI 看起來像這樣:https://dev.example.com/v1/api。為了匹配該請求的 URI,我們會這樣寫:

 http:
 - match:
   - uri:
       prefix: /v1

上述片段將匹配傳入的請求,並且請求將被路由到該路由中定義的目的地

另一個例子是使用正則並在頭上進行匹配。

 http:
 - match:
   - headers:
       user-agent:
         regex: '.*Firefox.*'

上述匹配將匹配任何使用者代理頭與 Regex 匹配的請求。

5.2 重定向和重寫請求

在頭資訊和其他請求屬性上進行匹配是有用的,但有時我們可能需要通過請求 URI 中的值來匹配請求

例如,讓我們考慮這樣一種情況:傳入的請求使用 /v1/api 路徑,而我們想把請求路由到 /v2/api 端點

這樣做的方法是重寫所有傳入的請求和與 /v1/api 匹配的 authority/host headers 到 /v2/api

例如:

 ...
 http:
   - match:
     - uri:
         prefix: /v1/api
     rewrite:
       uri: /v2/api
     route:
       - destination:
           host: customers.default.svc.cluster.local
 ...

即使目標服務不在 /v1/api 端點上監聽,Envoy 也會將請求重寫到 /v2/api

我們還可以選擇將請求重定向或轉發到一個完全不同的服務。下面是我們如何在頭資訊上進行匹配,然後將請求重定向到另一個服務

 ...
 http:
   - match:
     - headers:
         my-header:
           exact: hello
     redirect:
       uri: /hello
       authority: my-service.default.svc.cluster.local:8000
 ...

redirectdestination 欄位是相互排斥的。如果我們使用 redirect,就不需要設定 destination

5.3 AND 和 OR 語意

在進行匹配時,我們可以使用 AND 和 OR 兩種語意。讓我們看一下下面的片段:

 ...
 http:
   - match:
     - uri:
         prefix: /v1
       headers:
         my-header:
           exact: hello
 ...

上面的片段使用的是 AND 語意這意味著 URI 字首需要與 /v1 相匹配,並且頭資訊 my-header 有一個確切的值 hello

使用 OR 語意,我們可以新增另一個 match,像這樣:

 ...
 http:
   - match:
     - uri:
         prefix: /v1
     ...
   - match:
     - headers:
         my-header:
           exact: hello
 ...

在上面的例子中,將首先對 URI 字首進行匹配,如果匹配,請求將被路由到目的地。如果第一個不匹配,演演算法會轉移到第二個,並嘗試匹配頭。如果我們省略路由上的匹配欄位,它將總是評估為 true

六.實戰:簡單流量路由

6.1 簡單流量路由

我們將學習如何使用權重在不同的服務版本之間路由流量。然後,我們將部署 Customers 服務版本 v2,並使用子集在這兩個版本之間分配流量。

讓我們從部署 Gateway 開始:

 apiVersion: networking.istio.io/v1alpha3
 kind: Gateway
 metadata:
   name: gateway
 spec:
   selector:
     istio: ingressgateway
   servers:
     - port:
         number: 80
         name: http
         protocol: HTTP
       hosts:
         - '*'

將上述 YAML 儲存為 gateway.yaml,並使用 kubectl apply -f gateway.yaml 部署 Gateway。

接下來,我們將建立 Web Frontend 和 Customers 服務的部署以及相應的 Kubernetes 服務。讓我們首先從 web-frontend 開始:

 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: web-frontend
   labels:
     app: web-frontend
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: web-frontend
   template:
     metadata:
       labels:
         app: web-frontend
         version: v1
     spec:
       containers:
         - image: gcr.io/tetratelabs/web-frontend:1.0.0
           imagePullPolicy: Always
           name: web
           ports:
             - containerPort: 8080
           env:
             - name: CUSTOMER_SERVICE_URL
               value: 'http://customers.default.svc.cluster.local'
 ---
 kind: Service
 apiVersion: v1
 metadata:
   name: web-frontend
   labels:
     app: web-frontend
 spec:
   selector:
     app: web-frontend
   ports:
     - port: 80
       name: http
       targetPort: 8080

注意,我們正在設定一個名為 CUSTOMER_SERVICE_URL 的環境變數,它指向我們接下來要部署的 customer 服務。Web Frontend 使用這個 URL 來呼叫 Customers 服務。

將上述 YAML 儲存為 web-frontend.yaml ,並使用 kubectl apply -f web-frontend.yaml 建立部署和服務。

現在我們可以部署 Customers 服務的 v1版本了。注意我們是如何在 Pod 模板中設定 version: v1 標籤的。然而,該服務在其選擇器中只使用app: customers。這是因為我們將在 DestinationRule 中建立子集,這些子集將在選擇器中應用額外的版本標籤,使我們能夠到達執行特定版本的 Pod

 apiVersion: apps/v1
 kind: Deployment             
 metadata:
   name: customers-v1
   labels:
     app: customers
     version: v1
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: customers
       version: v1
   template:
     metadata:
       labels:
         app: customers
         version: v1
     spec:
       containers:
         - image: gcr.io/tetratelabs/customers:1.0.0
           imagePullPolicy: Always
           name: svc
           ports:
             - containerPort: 3000
 ---
 kind: Service
 apiVersion: v1
 metadata:
   name: customers
   labels:
     app: customers
 spec:
   selector:
     app: customers
   ports:
     - port: 80
       name: http
       targetPort: 3000

將上述內容儲存為 customers-v1.yaml,並使用 kubectl apply -f customers-v1.yaml 建立部署和服務。

我們應該有兩個應用程式的部署在執行:

 $ kubectl get po
 NAME                            READY   STATUS    RESTARTS   AGE
 customers-v1-7857944975-5lxc8   2/2     Running   0          36s
 web-frontend-659f65f49-jz58r    2/2     Running   0          3m38s

現在我們可以為 web-frontend 建立一個 VirtualService,並將其繫結到 Gateway 資源上:

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: web-frontend
 spec:
   hosts:
     - '*'
   gateways:
     - gateway
   http:
     - route:
         - destination:
             host: web-frontend.default.svc.cluster.local
             port:
               number: 80

將上述 YAML 儲存為 web-frontend-vs.yaml,並使用 kubectl apply -f web-frontend-vs.yaml 建立 VirtualService。

現在我們可以在瀏覽器中開啟 GATEWAY_URL,並進入顯示 Customers 服務中客戶列表的 Web Frontend,如下圖所示。

如果我們部署了 Customers 服務 v2 版本,我們在呼叫 http://customers.default.svc.cluster.local,得到的迴應將是隨機的。它們要麼來自 Customers 服務的 v2 版本,要麼來自 v1 版本。

我們需要為 Customers 服務建立 DestinationRule,並定義兩個子集,代表 v1 和 v2 版本。然後,我們可以建立一個 VirtualService,並將所有流量路由到 v1 版本的子集。之後,我們可以在不影響現有服務的情況下部署 v2 版本的 Customers 服務。

讓我們從 DestinationRule 和兩個子集開始:

 apiVersion: networking.istio.io/v1alpha3
 kind: DestinationRule
 metadata:
   name: customers
 spec:
   host: customers.default.svc.cluster.local
   subsets:
     - name: v1
       labels:
         version: v1
     - name: v2
       labels:
         version: v2

將上述內容儲存到 customers-dr.yaml,並使用 kubectl apply -f customers-dr.yaml 建立 DestinationRule。

我們可以建立 VirtualService 並在目標中指定 v1子集:

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers
 spec:
   hosts:
     - 'customers.default.svc.cluster.local'
   http:
     - route:
         - destination:
             host: customers.default.svc.cluster.local
             port:
               number: 80
             subset: v1

每當有請求被傳送到 Kubernetes Customers 服務時,它將被路由到同一服務的 v1子集。

將上述 YAML 儲存為 customers-vs.yaml,並使用 kubectl apply -f customers-vs.yaml 建立 VirtualService。

現在我們已經準備好部署 v2 版的 Customers 服務了。v2 版本返回與前一版本相同的客戶列表,但它也包括城市名稱。

讓我們建立 Customers v2 部署。我們不需要部署 Kubernetes 服務,因為我們已經部署了一個 v1 版本的服務。

 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: customers-v2
   labels:
     app: customers
     version: v2
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: customers
       version: v2
   template:
     metadata:
       labels:
         app: customers
         version: v2
     spec:
       containers:
         - image: gcr.io/tetratelabs/customers:2.0.0
           imagePullPolicy: Always
           name: svc
           ports:
             - containerPort: 3000

該部署與 v1 部署幾乎相同。唯一的區別是所使用的 Docker 映象版本和設定在版本標籤上的 v2 值。

將上述 YAML 儲存為 customers-v2.yaml,並使用 kubectl apply -f customers-v2.yaml 建立部署。

讓我們使用 weight 欄位並修改 VirtualService,使 50% 的流量被傳送到 v1 子集,另 50% 傳送到 v2 子集。

要做到這一點,我們將建立第二個 destination,有相同的主機名,但有不同的子集。我們還將為 destination 新增 weight: 50,以便在兩個版本之間平均分配流量。

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers
 spec:
   hosts:
     - 'customers.default.svc.cluster.local'
   http:
     - route:
         - destination:
             host: customers.default.svc.cluster.local
             port:
               number: 80
             subset: v1
           weight: 50
         - destination:
             host: customers.default.svc.cluster.local
             port:
               number: 80
             subset: v2
           weight: 50

將上述 YAML 儲存為 customers-50-50.yaml 並使用 kubectl apply -f customers-50-50.yaml 更新 VirtualService。

在瀏覽器中開啟 GATEWAY_URL,重新整理幾次頁面,看看不同的響應。來自 Customers v2 的響應顯示在下圖中。

為了改變傳送到一個或另一個版本的流量比例,我們可以更新 VirtualService。同樣,我們也可以新增 v3 或 v4 版本,並在這些版本之間分割流量。

6.2 清理

刪除 Deployments、Services、VirtualServices、DestinationRule 和 Gateway:

 kubectl delete deploy web-frontend customers-{v1,v2}
 kubectl delete svc customers web-frontend
 kubectl delete vs customers web-frontend
 kubectl delete dr customers
 kubectl delete gateway gateway

七.實戰:高階流量路由

7.1 高階流量路由

在這個實驗中,我們將學習如何使用請求屬性在多個服務版本之間路由流量

我們將從部署 Gateway 開始:

 apiVersion: networking.istio.io/v1alpha3
 kind: Gateway
 metadata:
   name: gateway
 spec:
   selector:
     istio: ingressgateway
   servers:
     - port:
         number: 80
         name: http
         protocol: HTTP
       hosts:
         - '*'

將上述 YAML 儲存為 gateway.yaml 並使用 kubectl apply -f gateway.yaml 部署閘道器。

接下來,我們將部署Web前端、Customers v1、Customers v2,以及相應的 VirtualServices 和 DestinationRule。一旦一切部署完畢,所有流量將被路由到 Customers v1。

 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: web-frontend
   labels:
     app: web-frontend
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: web-frontend
   template:
     metadata:
       labels:
         app: web-frontend
         version: v1
     spec:
       containers:
         - image: gcr.io/tetratelabs/web-frontend:1.0.0
           imagePullPolicy: Always
           name: web
           ports:
             - containerPort: 8080
           env:
             - name: CUSTOMER_SERVICE_URL
               value: 'http://customers.default.svc.cluster.local'
 ---
 kind: Service
 apiVersion: v1
 metadata:
   name: web-frontend
   labels:
     app: web-frontend
 spec:
   selector:
     app: web-frontend
   ports:
     - port: 80
       name: http
       targetPort: 8080
 ---
 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: web-frontend
 spec:
   hosts:
     - '*'
   gateways:
     - gateway
   http:
     - route:
         - destination:
             host: web-frontend.default.svc.cluster.local
             port:
               number: 80

將上述 YAML 儲存為 web-frontend.yaml 並使用 kubectl apply -f web-frontend.yaml 建立部署和服務。

 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: customers-v1
   labels:
     app: customers
     version: v1
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: customers
       version: v1
   template:
     metadata:
       labels:
         app: customers
         version: v1
     spec:
       containers:
         - image: gcr.io/tetratelabs/customers:1.0.0
           imagePullPolicy: Always
           name: svc
           ports:
             - containerPort: 3000
 ---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: customers-v2
   labels:
     app: customers
     version: v2
 spec:
   replicas: 1
   selector:
     matchLabels:
       app: customers
       version: v2
   template:
     metadata:
       labels:
         app: customers
         version: v2
     spec:
       containers:
         - image: gcr.io/tetratelabs/customers:2.0.0
           imagePullPolicy: Always
           name: svc
           ports:
             - containerPort: 3000
 ---
 kind: Service
 apiVersion: v1
 metadata:
   name: customers
   labels:
     app: customers
 spec:
   selector:
     app: customers
   ports:
     - port: 80
       name: http
       targetPort: 3000
 ---
 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers
 spec:
   hosts:
     - 'customers.default.svc.cluster.local'
   http:
     - route:
         - destination:
             host: customers.default.svc.cluster.local
             port:
               number: 80
             subset: v1
 ---
 apiVersion: networking.istio.io/v1alpha3
 kind: DestinationRule
 metadata:
   name: customers
 spec:
   host: customers.default.svc.cluster.local
   subsets:
     - name: v1
       labels:
         version: v1
     - name: v2
       labels:
         version: v2

將上述 YAML 儲存為 customers.yaml,用 kubectl apply -f customers.yaml 建立資源。

為了確保一切部署和工作正常,開啟 GATEWAY_URL,並確保我們從 Customers v1 獲得響應。

我們將更新 Customers 的 VirtualService,並更新流量在兩個版本的 Customers 服務之間的路由。

讓我們看一下 YAML,如果請求中包含一個 header user: debug,就把流量路由到 Customers v2。如果沒有設定這個 header,我們就被路由到 Customers v1。

 apiVersion: networking.istio.io/v1alpha3
 kind: VirtualService
 metadata:
   name: customers
 spec:
   hosts:
     - 'customers.default.svc.cluster.local'
   http:
   - match:
     - headers:
         user:
           exact: debug
     route:
     - destination:
         host: customers.default.svc.cluster.local
         port:
           number: 80
         subset: v2
   - route:
       - destination:
           host: customers.default.svc.cluster.local
           port:
             number: 80
           subset: v1

將上述 YAML 儲存為 customers-vs.yaml,然後用 kubectl apply -f customers-vs.yaml 更新 VirtualService。

如果我們不提供埠號,VirtualService 中的目的地也會工作。這是因為該服務只定義了一個埠。

如果我們開啟 GATEWAY_URL,我們仍然應該得到來自 Customers v1的響應。如果我們在請求中新增 header user: debug,我們會注意到customers 的響應是來自 Customers v2。

我們可以使用 ModHeader 擴充套件來修改瀏覽器中的頭資訊。另外,我們也可以使用 cURL,像這樣把頭資訊新增到請求中。

 $ curl -H "user: debug" http://GATEWAY_URL/
 ...
 <th class="px-4 py-2">CITY</th>
 <th class="px-4 py-2">NAME</th>
 ...

如果我們看一下回復,你會注意到有兩欄——CITY 和 NAME。

7.2 清理

刪除 Deployment、Service、VirtualService、DestinationRule 和 Gateway:

 kubectl delete deploy web-frontend customers-{v1,v2}
 kubectl delete svc customers web-frontend
 kubectl delete vs customers web-frontend
 kubectl delete dr customers
 kubectl delete gateway gateway