4. 流量治理基础

在 OpenShift Service Mesh 中,Istio 控制面把“流量怎么走”拆成三类可组合的声明式资源:Gateway 负责把集群外的南北向流量接进来,VirtualService 负责在服务网格内部决定请求匹配哪条路由并转发到哪个目标,DestinationRule 负责在选定目标之后定义“以什么策略访问这个目标”,例如子集划分、负载均衡、连接池与熔断。三者不是并列替代关系,而是入口 → 路由匹配 → 目标策略的接力关系。理解这条链路,是后续灰度发布、超时重试、故障注入等治理能力的基础。

先看职责边界。Gateway 只描述监听端口、协议、TLS 与主机名,它本身不决定后端服务;VirtualService 通过 gateways 字段绑定 Gateway,用 hosts 声明它服务哪些域名,再用 http 或 tcp 规则做匹配;DestinationRule 则用 host 指向某个 Kubernetes Service,并通过 subsets 把该 Service 的 Pod 按标签切成不同版本子集。一个常见的误区是:只在 VirtualService 里写 subset: v2,却没有在 DestinationRule 中定义 v2 子集,结果路由规则被 Envoy 拒绝或回退,表现为 503。因此子集必须先在 DestinationRule 中声明,VirtualService 才能引用。

南北向入口 + 网格内路由 + 目标策略 的接力链路 外部客户端 HTTPS / shop.example.com Gateway 端口 443 / HTTPS hosts: shop.example.com 仅负责接入,不选后端 VirtualService hosts: shop.example.com match: uri /api/v1 route → subset: v2 (90%) 决定“匹配谁、发给谁” DestinationRule host: product-svc subsets: v1 / v2 LB: ROUND_ROBIN 决定“怎么访问目标” product-svc Kubernetes Service subset v1 labels: version=v1 subset v2 labels: version=v2 关键约束 ① VirtualService 的 subset 必须先在 DestinationRule 中定义,否则 Envoy 无法解析目标。 ② Gateway 只做接入,VirtualService 通过 gateways 字段绑定它;未绑定的 Gateway 不会自动生效。 ③ 同一 host 的 VirtualService 与 DestinationRule 必须指向同一 Service,否则路由与策略脱节。

下面用一个可运行的示例把三者串起来。假设集群中已有 product-svc,其 Pod 带有 version=v1 与 version=v2 两组标签。目标是把 shop.example.com/api/v1 的请求按 90/10 分流到 v2 与 v1,并对外只暴露 443 端口。

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: shop-gw
  namespace: bookinfo
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: shop-cert
    hosts:
    - shop.example.com
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: product-vs
  namespace: bookinfo
spec:
  hosts:
  - shop.example.com
  gateways:
  - shop-gw
  http:
  - match:
    - uri:
        prefix: /api/v1
    route:
    - destination:
        host: product-svc
        subset: v2
      weight: 90
    - destination:
        host: product-svc
        subset: v1
      weight: 10
    timeout: 3s
    retries:
      attempts: 2
      perTryTimeout: 1s
      retryOn: 5xx
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: product-dr
  namespace: bookinfo
spec:
  host: product-svc
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
    connectionPool:
      tcp:
        maxConnections: 100
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

这段配置里,Gateway 的 selector: istio: ingressgateway 表示它由入口网关的 Pod 承载;credentialName: shop-cert 引用的是预先创建在 istio-system 命名空间下的 TLS Secret。VirtualService 的 gateways 字段把 shop-gw 与当前规则绑定,hosts 与 Gateway 的 hosts 保持一致,否则请求会因域名不匹配而落到默认 404。路由部分先按 URI 前缀匹配,再按权重分流;timeout 与 retries 写在路由级别,表示对本次匹配到的请求生效。DestinationRule 则把 product-svc 拆成 v1、v2 两个子集,并设置轮询负载均衡与连接池上限。

配置完成后,验证顺序建议从外到内。先确认入口网关地址与证书是否就绪:

oc -n bookinfo get gateway shop-gw -o yaml
oc -n istio-system get secret shop-cert
oc -n bookinfo get virtualservice product-vs -o yaml
oc -n bookinfo get destinationrule product-dr -o yaml

再用 istioctl analyze -n bookinfo 检查资源之间是否存在引用错误,例如 VirtualService 引用了未定义的 subset、Gateway 未被任何 VirtualService 绑定等。随后通过入口网关发起请求,观察响应头中的版本标识:

for i in $(seq 1 20); do
  curl -sk -H "Host: shop.example.com" \
    https://$(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}')/api/v1/info \
    -o /dev/null -w "%{http_code}\n"
done

如果 v2 与 v1 的响应中带有不同的版本头,且比例大致接近 9:1,说明权重路由生效。若出现 503,优先检查 DestinationRule 中是否真的存在对应 subset,以及 subset 的标签是否与 Pod 实际标签一致;若出现 404,则检查 VirtualService 的 hosts 与 Gateway 的 hosts 是否一致、URI 前缀是否匹配。若请求全部落到 v1,则检查权重字段是否被写成了整数以外的格式,或是否存在更高优先级的匹配规则抢先命中。

在 OpenShift 中,Gateway 的 selector 通常指向 istio: ingressgateway,而入口网关本身由 istio-system 命名空间下的 Deployment 与 Service 承载。若使用 OpenShift Route 暴露入口网关,Route 只负责把外部流量送到网关 Service,真正的域名与 TLS 终止仍由 Gateway 资源控制,两者不要混淆。

最后需要强调匹配优先级。VirtualService 中的多条 http 规则按从上到下的顺序匹配,第一条命中的规则生效,因此精确匹配应放在前缀匹配之前。权重只在同一条路由的多个 destination 之间分配,不同路由之间不存在权重叠加。DestinationRule 的 trafficPolicy 可以写在顶层作为默认策略,也可以写在每个 subset 下覆盖默认值,例如给 v2 单独设置更小的连接池或不同的负载均衡算法。掌握这三类资源的分工与引用关系,后续的灰度发布、超时重试、熔断与故障注入才能在同一套路由骨架上稳定叠加。