一、背景

在微服务系统中,我们会碰到很多关于服务治理的问题,下面是我整理了一些关于服务治理常见的问题:

  1. 服务发现:在动态的微服务环境中,如何实时地发现和注册新的服务实例?
  2. 负载均衡:如何在服务实例之间有效地分配请求流量,以实现高性能和高可用性?
  3. 容错处理:如何处理服务之间的故障,例如服务实例故障、网络故障等?
  4. 流量管理:如何控制服务间的请求流量,例如请求路由、流量分割、金丝雀发布等?
  5. 服务监控:如何实时地监控服务的性能和健康状况?
  6. 链路追踪:如何跟踪和分析分布式系统中的请求调用链?
  7. 安全性:如何确保服务之间的通信安全,例如身份验证、授权和加密?
  8. 策略执行:如何实施和管理服务治理的策略,例如限流、熔断、访问控制等?
  9. 配置管理:如何在服务之间统一和动态地管理配置信息?
  10. 服务编排:如何协调服务之间的交互,以实现复杂的业务流程?

解决方案:

  1. 通过使用 Istio 的服务发现机制,新的服务实例可以自动注册到服务注册表中,并且 Istio 可以实时地检测到新增的服务实例并将其纳入流量管理范围。
  2. Istio 使用负载均衡和流量管理功能来有效地分配请求流量给不同的服务实例,以实现高性能和高可用性,可以根据配置的策略进行流量分配、故障转移和自动扩缩容等操作。
  3. 当服务实例发生故障或网络故障时,Istio 提供了故障注入和故障恢复机制,可以自 动检测故障并进行故障转移和重试,确保服务之间的通信稳定性和可靠性。
  4. 通过使用 Istio 的流量规则和路由配置,可以灵活地控制服务之间的请求流量,包括 请求路由、流量分割、金丝雀发布等,以便实现灵活的版本控制、A/B 测试和流量调节等功能。
  5. Istio 提供了丰富的监控和指标收集功能,可以实时监控服务的性能和健康状况,例如请求成功率、延迟、吞吐量等指标,帮助运维人员及时发现和解决问题。
  6. 通过使用 Istio 的分布式追踪功能,可以跟踪和分析分布式系统中的请求调用链,即使在复杂的微服务架构中也能够实时定位问题,并进行性能优化和故障排查。
  7. Istio 提供了强大的安全控制功能,可以确保服务之间的通信安全,包括身份验证、 授权和加密等机制,帮助防止未经授权的访问和数据泄露。
  8. 通过使用 Istio 的策略配置,可以轻松实施和管理服务治理的策略,例如限流、熔 断、访问控制等,以保护后端服务免受过载和恶意请求的影响。
  9. Istio 提供了统一的配置管理功能,可以集中管理服务之间的配置信息,包括环境变 量、特性开关、数据库连接等,实现了配置的动态更新和快速回滚。
  10. 通过使用 Istio 的服务网格功能,可以协调和管理服务之间的交互,实现复杂的业务 流程,包括事务处理、消息队列、分布式锁等,确保服务之间的顺序和一致性。

二、基于Istio的金丝雀发布

2.1 金丝雀发布(Canary Release)

背景:

新版本上线之前,经历过开发和测试人员的验证,也经过产品经理的验收。可是当要上 线到生产环境时,谁也保证不了上线一定就能跑起来。所以往往需要在上线时保持新版 本和旧版本同时在用,测试人员或内测用户可以访问新版本,其他人继续使用旧版本。 再有就是上线时新旧系统能够丝滑切换,用户完全感知不到这种变化。

并且新版本上线时,不应该影响旧版本的运行,要求实现不停止更新。但是除了应用本 身,还涉及到新旧版本共有同一个数据库,新旧版本应用对应的数据库表结构不一样。

Kubernetes 中虽然有滚动升级,能够逐渐使用新版本的 Pod 替换旧版本的 Pod,两个版本共存。但是并不能做到流量自由切分,一个流量进入时,依然会在新旧版本的 Pod 中轮询选择。但是我们还是可以调整 v1 和 v2 的 Pod 数量,实现流量按照比例划分,比如 v1 的 Pod 数量有 40 个,v2 的 Pod 的数量有 60 个,那么按照比例,会有 60% 的流 量进入到 v2 中。不过这样并没有什么作用。

Istio 中虽然没有名为 金丝雀发布的功能,但是按照之前我们所学到的 Istio 的 VirtualService 、DestinationRule 就可以实现根据请求的 header 等,将流量转发到不同的子版本中,我们使用这些操作方法即可实现金丝雀发布。

我们可以 使用 VirtualService 资源在 Istio 服务网格中进行流量路由通过 VirtualService,我们可以定义流量路由规则,并在客户端试图连接到服务时应用这些 规则。例如向 dev.kubernets.cn 发送一个请求,最终到达目标服务。

Day10-服务治理-图20

我们想把 VirtualService 配置为将流量路由到不同的应用程序的版本。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。

2.2 VirtualService 中的gateways

通过 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

如上主要讲了基于 VirtualService 和 Gateway 的一些特性带来的场景,实验部分咱们放 到本即可的末尾再一一的去给大家演示!

三、流量管理和版本控制

3.1 Subset

Subset 是一种将服务实例划分为不同子集的方式。通过定义签或条件来选择性地路由流量到特定的服务实例。

例如,你可以根据版本号、环境、地理位置或其他自定义标签来创建实现对特定子集的流量进行控制和管理。

通过子集,我们可以识别应用程序的不同变体,可能听起来有点绕,在我们的例子中, 我们有两个子集, v1 和 v2,它们对应于我们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 中设置的流量策略。

3.2 DestinationRule

DestinationRule(目标规则)是定义了将请求发送到特定服务版本的路由规则。它与 Subset 搭配使用,用于指定流量应该如何被路由到具体的 Subset。

通过 DestinationRule,我们可以定义设置,如负载均衡配置、连接池大小、局部异常检测等,在路由发生后应用于流量。

DestinationRule重要属性:

  • host:必选字段,标识规则的适用对象,取值是服务注册中心中注册的服务名,可 以是网格内或以 serviceEntry(用于在Istio服务网格之外启用对服务的请求) 方式 注册的网格外的服务。
  • subsets:定义的服务子集,经常用来定义一个服务版本。
  • trafficPolicy:定义规则内容,包括负载均衡、连接池策略、异常点检查扥。
  • exportTo:控制在一个命名空间下定义的资源对象是否可以被其他命名空间下的 sidecar执行。

未赋值,则默认全局可见。“.” 表示仅应用到当前命名空间,“*” 表示应用到所有命名空间。

trafficPolicy(流量策略):

  • loadBalancer:LoadBalancerSettings类型,描述服务的负载均衡算法。
  • connectionPool:ConnectionPoolSettings类型,描述服务的连接池配置。
  • outlierDetection:OutlierDetection,描述服务的异常点检查。
  • tls:TLSSettings类型,描述服务的TLS连接设置。

3.2.1 负载均衡器设置(loadBalancer)

通过负载均衡器设置,我们可以控制目的地使用哪种负载均衡算法

例如下面是一个带有流量策略的 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      

3.2.2 连接池配置(ConnectionPoolSettings)

Istio连接池管理在协议上分为 TCP 流量和 HTTP 流量治理

TCP连接池配置:

  • maxConnections:上游服务的所有实例建立的最大连接数,默认是 1024,属于 TCP层的配置,对于 HTTP,只用于 HTTP/1.1,因为 HTTP/2对每个主机都使用单个连接。
  • connectTimeout:TCP 连接超时,表示主机网络连接超时,可以改善因调用服务变慢导致整个链路变慢的情况。
  • tcpKeepalive:设置TCP keepalives,是 Istio1.1 新支持的配置,定期给对端发送 一个 keepalive 的探测包,判断连接是否可用。

HTTP连接池配置:

  • http1MaxPendingRequests:最大等待 HTTP 请求数,默认值是 1024,只适用于 HTTP/1.1 的服务,因为 HTTP/2 协议的请求在到来时会立即复用连接,不会在连接 池等待。

  • http2MaxRequests:最大请求数,默认是1024。只适用于HTTP/2服务,因为 HTTP/1.1使用最大连接数maxConnections即可,表示上游服务的所有实例处理的 最大请求数。

  • maxRequestsPerConnection:每个连接的最大请求数。HTTP/1.1和HTTP/2连接 池都遵循此参数。如果没有设置,则没有限制。设置为1时表示每个连接只处理一个 请求,也就是禁用了Keep-alive。

  • maxRetries:最大重试次数,默认是3,表示服务可以执行的最大重试次数。如果 调用端因为偶尔抖动导致请求直接失败,则可能会带来业务损失,一般建议配置重试,若重试成功则可正常返回数据, 只不过比原来响应得慢一点,但重试次数太多会影响性能,要谨慎使用。不要重试那些重试了也总还是失败的请求;不要对那些消耗大的服务进行重试,特别是那些不会被取消的服务。

  • idleTimeout:空闲超时,定义在多长时间内没有活动请求则关闭连接。

下面是一个片段,显示了我们如何设置对服务的并发请求的限制

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-destination-rule
spec:
  host: my-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 0.5s
      http:
        http2MaxRequests: 100
        maxRequestsPerConnection: 10
        idleTimeout: 1m
        maxRetries: 3
  • 在 tcp 部分, maxConnections 设置了最大连接数为 100, connectTimeout 设置了连接超时时间为0.5 秒
  • 在 http 部分, http2MaxRequests 设置了每个连接的最大 HTTP/2 请求数为 100,maxRequestsPerConnection 设置了每个连接的最大请求数为 10,idleTimeout 设置了空闲超时时间为 1 分钟, maxRetries 设置了最大重试次数为3。

3.2.3 异常点检测(OutlierDetection)

异常点检测是一个断路器的实现,它跟踪上游服务中每个主机(Pod)的状态。 两种健康检查:

  • 主动型的健康检查:定期探测目标服务实例,根据应答来判断服务实例的健康状态。如负载均衡器中的健康检查。
  • 被动型的健康检查:通过实践的访问情况来找出不健康的实例,如 isito 中的异常点检查。

异常实例检查相关的配置:

  • consecutiveErrors:实例被驱逐前的连续错误次数,默认是 5。对于 HTTP 服务, 返回 502、503 和 504 的请求会被认为异常;对于 TCP 服务,连接超时或者连接错 误事件会被认为异常。

  • interval:驱逐的时间间隔,默认值为10秒,要求大于1毫秒,单位可以是时、分、 毫秒。

  • baseEjectionTime:最小驱逐时间。一个实例被驱逐的时间等于这个最小驱逐时间 乘以驱逐的次数。这样一个因多次异常被驱逐的实例,被驱逐的时间会越来越长。 默认值为30秒,要求大于1毫秒,单位可以是时、分、毫秒。

  • maxEjectionPercent:指负载均衡池中可以被驱逐的故障实例的最大比例,默认是 10%,设置这个值是为了避免太多的服务实例被驱逐导致服务整体能力下降。

  • minHealthPercent:最小健康实例比例。当负载均衡池中的健康实例数的比 例大于 这个比例时,异常点检查机制可用;当可用实例数的比例小于这个比例时,异常点检查功能将被禁用,所有服务实例不管被认定为健康还是不健康,都可以接收请求。参数的默认值为50%。

consecutiveErrors 设置了连续错误次数达到 5 次时会触发异常检测, interval 设 置了检测的时间间隔为5秒, baseEjectionTime 设置了异常时的最小驱逐时间为30秒, maxEjectionPercent 设置了最大驱逐百分比为 50。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-destination-rule
spec:
  host: my-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
        connectTimeout: 0.5s
      http:
        http2MaxRequests: 100
        maxRequestsPerConnection: 10
        idleTimeout: 1m
        maxRetries: 3
    outlierDetection:
      consecutiveErrors: 5
      interval: 5s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

3.2.4 客户端 TLS 设置

包含任何与上游服务连接的 TLS 相关设置。下面是一个使用提供的证书配置 mTLS 的例子。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: my-destination-rule
spec:
  host: my-service
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
      clientCertificate: /etc/certs/client.crt
      privateKey: /etc/certs/private.key
      caCertificates: /etc/certs/root-ca.crt

针对名为 "my-service" 的目标服务进行了 TLS 配置。这个配置位于 trafficPolicy 下 的 tls 字段。

  • mode 字段指定了 TLS 的模式,这里设置为 ISTIO_MUTUAL,表示启用相互认证的双向 TLS 连接。
  • clientCertificate 字段指定了客户端证书的路径,用于在客户端和服务端之间建立双向 TLS 连接时进行身份验证。

  • privateKey 字段指定了私钥的路径,它与客户端证书配对使用,用于加密和解密通信过程中的数据。

  • caCertificates 字段指定了根证书的路径,用于验证服务端证书的有效性。

tls 字段的 mode 可以设置以下几种类型:

  1. DISABLE: 禁用 TLS,即不使用 TLS 加密和认证。
  2. SIMPLE: 使用简单的单向 TLS 加密,服务端需要提供有效的证书用于加密通信,但不要求客户端进行任何认证。
  3. MUTUAL: 使用双向 TLS 加密和认证,客户端和服务端都需要提供有效的证书,相互进行身份验证。
  4. ISTIO_MUTUAL: 使用 Istio 专有的双向 TLS 加密和认证。在该模式下,Istio 自动为 每个Sidecar 自动生成和分发证书,实现自动化的 TLS 加密和双向认证。这是推荐的默认模式。

3.2.5 端口流量策略

  • ROUND_ROBIN:轮询算法,如果未指定,则默认采用这种算法。
  • LEAST_CONN:最少连接算法,算法实现是从两个随机选择的服务后端选择一个活动请求数较少的后端实例。
  • RANDOM:从可用的健康实例中随机选择一个。
  • PASSTHROUGH:直接转发连接到客户端连接的目标地址,即没有做负载均衡。
trafficPolicy:
  portLevelSettings:
    - port:
        number: 80
      loadBalancer:
        simple: LEAST_CONN
    - port:
        number: 8000
      loadBalancer:
        simple: ROUND_ROBIN
    - port:
        number: 8000
      loadBalancer:
        simple: RANDOM

四、小结

通过结合使用 Subset 和 DestinationRule,你可以实现更精细的流量控制和版本管理。

例如,可以使用 Subset 将流量按照版本划分为 A/B 测试组,然后使用 DestinationRule将特定的流量路由到一个或多个 Subset 上。

这样可以方便地控制流量的分布,灰度发布新版本,进行流量切换等操作。

五、高级路由

5.1 高级路由

在前面,我们了解了如何 利用流量的比例( weight 字段)在多个子集之间进行流量路由

在某些情况下,纯粹的基于权重的流量路由分割已经足够了

然而,在有些场景和情况下,可能需要对流量如何被分割和转发到目标服务进行更细化的控制

Istio 允许我们使用传入请求的一部分,并将其与定义的值相匹配。例如,我们可以匹配 传入请求的 URI 前缀,并基于此路由流量。

属性 描述
uri 将请求 URI 与指定值相匹配
schema schema(HTTP、HTTPS...)匹配请求的
method method(GET、POST...)匹配请求的
authority 匹配请求 authority 头
headers 匹配请求头。头信息必须是小写的,并以连字符分隔(例如: request-id)。注意,如果我们使用头信息进行匹配,其他属性将被忽略( uri、 schema、 method、 authority)。

上述每个属性都可以用这些方法中的一种进行匹配:

  • 精确匹配:例如, exact: "value" 匹配精确的字符串
  • 前缀匹配:例如, prefix: "value" 只匹配前缀
  • 正则匹配:例如, regex:"value" 根据 ECMAscript 风格的正则进行匹配

例如,假设请求的 URI 看起来像这样: https://dev.kubernets.cn/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 匹配到 / v2/ api 。

例如:

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

即使目标服务不在 /v1/api 端点上监听,Envoy 也会将请求重写到 我们还可以选择将请求重定向或转发到一个完全不同的服务。

下面是我们如何在头信息上进行匹配,然后将请求重定向到另一个服务:

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

注意: redirect 和 destination 字段是相互排斥的。如果我们使用 redirect,就不需要设置 destination。

5.3 AND 和 OR 语义

在进行匹配时,我们可以使用 AND 和 OR 两种语义。让我们看一下下面的片段:

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

上面的片段使用的是 AND 语义这意味着 URI 前缀需要与my- header 有一个确切的值 hello 。

使用 OR 语义,我们可以添加另一个match ,像这样:

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

在上面的例子中,将首先对 URI 前缀进行匹配,如果匹配,请求将被路由到目的地。

如果第一个不匹配,算法会转移到第二个,并尝试匹配头。如果我们省略路由上的匹配 字段,它将总是评估为 true 。

六、实战(1):简单流量路由

6.1 简单流量路由

我们将学习如何 使用权重在不同的服务版本之间路由流量

然后,我们将部署 Customers 服务版本 v2,并使用子集在这两个版本之间分配流量。 让我们从部署 Gateway 开始:

[root@master01 istioyaml]# vim gateway.yaml
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。

[root@master01 istioyaml]# kaf gateway.yaml

#验证查看
[root@master01 istioyaml]# kg gw.networking.istio -A
NAMESPACE      NAME         AGE
default        gateway      17s

接下来,我们将创建 Web Frontend 和 Customers 服务的部署以及相应的 Kubernetes 服务。让我们首先从 web-frontend 开始:

[root@master01 istioyaml]# vim web-frontend.yaml
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: registry.cn-hangzhou.aliyuncs.com/github_images1024/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 创建部署和服务。

# 验证
[root@master01 istioyaml]# kg -f web-frontend.yaml
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web-frontend   1/1     1            1           7s

NAME                   TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)   AGE
service/web-frontend   ClusterIP   192.168.249.145   <none>        80/TCP    7s

现在我们可以部署Customers 服务的 v1版本了。注意我们是如何在 Pod 模板中设置 version: v1 标签的。

然而,该服务在其选择器中只使用 app: customers 。这是因为我们将在 DestinationRule 中创建子集,这些子集将在选择器中应用额外的版本标签,使我们能 够到达运行特定版本的 Pod。

[root@master01 istioyaml]# vim customers-v1.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: registry.cn-hangzhou.aliyuncs.com/github_images1024/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 创建部署和服务。

[root@master01 istioyaml]# kaf customers-v1.yaml

我们应该有两个应用程序的部署在运行:

[root@master01 istioyaml]# kubectl get po | grep customers-v1
customers-v1-6f6b6f57d6-cr8hg              1/1     Running   0               27s

现在我们可以为 web-frontend 创建一个 VirtualService,并将其绑定到 Gateway 资源上:

[root@master01 istioyaml]# vim web-frontend-vs.yaml
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。

[root@master01 istioyaml]# kaf web-frontend-vs.yaml

现在我们可以在浏览器输入http://10.0.0.12/,打开 GATEWAY_URL,并进入显示 Customers 服务中客户列表 的 Web Frontend,如下图所示。

[root@master01 istioyaml]# kubectl get svc -n istio-system istio-ingressgateway
NAME                   TYPE           CLUSTER-IP        EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   LoadBalancer   192.168.149.121   10.0.0.12     15021:31360/TCP,80:31978/TCP,443:31805/TCP,31400:30191/TCP,15443:30922/TCP   14h

image-20250417110721031

如果我们部署了 Customers 服务 v2 版本,我们在调用

http://customers.default.svc.cluster.local ,得到的回应将是随机的。它们要么来自 Customers 服务的 v2 版本,要么来自 v1 版本。

我们需要为 Customers 服务创建 DestinationRule,并定义两个子集,代表 v1 和 v2 版本。然后,我们可以创建一个 VirtualService,并将所有流量路由到 v1 版本的子集。之后,我们可以在不影响现有服务的情况下部署 v2 版本的Customers 服务。

让我们从 DestinationRule 和两个子集开始:

[root@master01 istioyaml]# vim customers-dr.yaml 
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。

#创建
[root@master01 istioyaml]# kaf  customers-dr.yaml 

#验证
[root@master01 istioyaml]# kg dr 
NAME        HOST                                  AGE
customers   customers.default.svc.cluster.local   14s

我们可以创建 VirtualService 并在目标中指定 v1子集:

[root@master01 istioyaml]# vim customers-vs.yaml
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。

[root@master01 istioyaml]# kaf customers-vs.yaml

#验证
[root@master01 istioyaml]# kg -f customers-vs.yaml
NAME        GATEWAYS   HOSTS                                     AGE
customers              ["customers.default.svc.cluster.local"]   4s

现在我们已经准备好部署 v2 版的 Customers 服务了。v2 版本返回与前一版本相同的客户列表,但它也包括城市名称。

让我们创建 Customers v2 部署。我们不需要部署 Kubernetes 服务,因为我们已经部署了一个 v1 版本的服务。

[root@master01 istioyaml]# vim customers-v2.yaml 
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: registry.cn-hangzhou.aliyuncs.com/github_images1024/customers:2.0.0
          imagePullPolicy: Always
          name: svc
          ports:
            - containerPort: 3000

该部署与 v1 部署几乎相同。唯一的区别是所使用的 Docker 镜像版本和设置在版本标签上的 v2 值。

将上述 YAML 保存为 customers-v2.yaml ,并使用 kubectl apply -f customers-v2.yaml 创建部署。

[root@master01 istioyaml]# kaf customers-v2.yaml 

#验证
[root@master01 istioyaml]# kg -f customers-v2.yaml 
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
customers-v2   1/1     1            1           6s

让我们使用 weight 字段并修改 VirtualService,使 50% 的流量被发送到 v1 子集,另50% 发送到 v2 子集。

要做到这一点,我们将创建第二个 destination ,有相同的主机名,但有不同的子集。我们还将为 destination 添加 weight: 50 ,以便在两个版本之间平均分配流量。

[root@master01 istioyaml]# vim customers-50-50.yaml
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。

#应用
[root@master01 istioyaml]# kaf customers-50-50.yaml

#验证
[root@master01 istioyaml]# kg -f  customers-50-50.yaml
NAME        GATEWAYS   HOSTS                                     AGE
customers              ["customers.default.svc.cluster.local"]   4m1s

在浏览器中打开 GATEWAY_URL ,这里为10.0.0.12。刷新几次页面,看看不同的响应。来自 Customers v2的响应显示在下图中。

image-20250417111757119

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

kubectl delete dr customers

kubectl delete gw.networking.istio gateway

七、实战(2):高级流量路由

7.1 高级流量路由

在这个实验中,我们将学习如何 使用请求属性在多个服务版本之间路由流量

我们将从部署 Gateway 开始:

[root@master01 istioyaml]# vim gateway-2.yaml
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-2.yaml 并使用 kubectl apply -f gateway-2.yaml 部署网关。

[root@master01 istioyaml]# kaf gateway-2.yaml

#查看gw
[root@master01 istioyaml]# kg -f gateway-2.yaml
NAME      AGE
gateway   11s

接下来,我们将部署Web前端、Customers v1、Customers v2,以及相应的 VirtualServices 和 DestinationRule。一旦一切部署完毕,所有流量将被路由到 Customers v1。

部署web前端

[root@master01 istioyaml]# vim web-frontend-2.yaml 
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: registry.cn-hangzhou.aliyuncs.com/github_images1024/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-2.yaml 并使用 kubectl apply -f web-frontend-2.yaml 创建部署和服务。

[root@master01 istioyaml]# kaf web-frontend-2.yaml 

部署web后端

[root@master01 istioyaml]# vim customers-2.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: registry.cn-hangzhou.aliyuncs.com/github_images1024/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: registry.cn-hangzhou.aliyuncs.com/github_images1024/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-2.yaml,用 kubectl apply -f customers-2.yaml 创建资源。

[root@master01 istioyaml]# kubectl apply -f customers-2.yaml

为了确保一切部署和工作正常,打开 GATEWAY_URL ,这里为10.0.0.12我们发现 DestinationRule 会将流量送到 v1。

[root@master01 istioyaml]# kgs -n istio-system | grep istio-ingressgateway
istio-ingressgateway   LoadBalancer   192.168.149.121   10.0.0.12     15021:31360/TCP,80:31978/TCP,443:31805/TCP,31400:30191/TCP,15443:30922/TCP   15h

image-20250417125128734

我们将更新 Customers 的 VirtualService,并更新流量在两个版本的 Customers 服务之间的路由。

我们看一下 YAML,如果请求中包含一个 header user: debug ,就把流量路由到Customers v2。如果没有设置这个 header,我们就被路由到 Customers v1。

[root@master01 istioyaml]# vim customers-vs-2.yaml 
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-2.yaml ,然后用 kubectl apply -f customers-vs-2.yaml 更新 VirtualService。

[root@master01 istioyaml]# kubectl apply -f customers-vs-2.yaml

如果我们不提供端口号,VirtualService 中的目的地也会工作。这是因为该服务只定义了一个端口。

如果我们打开 GATEWAY_URL ,我们仍然应该得到来自 Customers v1的响应。如果我们在请求中添加 header user: debug ,我们会注意到customers 的响应是来自Customers v2。

我们可以使用 ModHeader 扩展来修改浏览器中的头信息。另外,我们也可以使用 CURL,像这样把头信息添加到请求中。看一下回复,你会注意到有两栏 -- CITY 和 NAME。

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

不添加头信息到请求上,看一下回复,你会注意到只有一栏 -- NAME。

$ curl  http://10.0.0.12/
...
                <th class="px-4 py-2">NAME</th>
...

7.2 清理

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

kubectl delete deploy web-frontend customers-{v1,v2}

kubectl delete svc customers web-frontend

kubectl delete vs.networking.istio customers web-frontend

kubectl delete dr customers

kubectl delete gw.networking.istio gateway

八、实战(3):高级流量路由(2)

8.1 rewrite

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: customers
spec:
  gateways:
    - gateway
  hosts:
    - 'customers.default.svc.cluster.local'
  http:
    - match:
        - uri:
            exact: "/simple/hello"
      rewrite:
        uri: "/hello"
      route:
        - destination:
            host: customers.default.svc.cluster.local
            port:
              number: 80
            subset: v2
    - match:
        - uri:
            prefix: "/customers"
      rewrite:
        uri: "/"
      route:
        - destination:
            host: customers.default.svc.cluster.local
            port:
              number: 80
            subset: v1

九、总结

Gateway(网关):Gateway 充当进入 Istio 网格的边界,负责接收外部流量并将其引 导到正确的 VirtualService。它定义了对外部请求的入口点,并可以进行流量管理和安全 控制。

VirtualService(虚拟服务):VirtualService 定义了请求流量如何从客户端路由到目标 服务的规则。它可以指定流量的匹配条件,例如请求的 URI、请求头等,以及对匹配请 求的路由规则,例如将流量转发到不同的子集、不同的版本或不同的后端服务。

DestinationRule(目标规则):DestinationRule 定义了在流量路由和负载均衡过程 中如何处理特定目标服务的行为。它可以配置服务的负载均衡策略、连接池大小、TLS 设置等。通过 DestinationRule,可以对不同的子集应用不同的策略。

Subset(子集):Subset 是根据某些标准对服务实例进行逻辑分组的方法。它将服务 实例划分为不同的子集,每个子集可以包含一组符合特定条件的服务实例。

他们之间的关联关系如下:

Gateway--->virtualService--->DestinationRule--->Subset