Istio(五):使用服务网格Istio进行流量路由

时间:2022-10-29 18:07:09

在本模块中,我们将开始使用 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如下所示:

Istio(五):使用服务网格Istio进行流量路由

我们想把 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,如下图所示。

Istio(五):使用服务网格Istio进行流量路由

如果我们部署了 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 的响应显示在下图中。

Istio(五):使用服务网格Istio进行流量路由

为了改变发送到一个或另一个版本的流量比例,我们可以更新 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