第4章 核心概念:虚拟服务、目标规则与网关

聊到 Istio 流量管理,就绕不开三个老朋友:VirtualService、DestinationRule 和 Gateway。我最早接触它们的时候,觉得挺绕,又是虚拟又是规则又是网关的。后来在项目里亲手配了几次,踩了几回坑,才摸清楚它们各自管什么、怎么配合。

说白了,这三个资源就是 Istio 控制“谁来”、“去哪”、“怎么去”的三大手柄。你只要把这几样理清了,流量管理就打通了七成。

Istio 流量管理三大核心概念关系 外部流量 Gateway 入口/出口网关 VirtualService 路由规则、匹配、权重 DestinationRule 负载均衡、连接池、TLS Service v1 / v2 子集 请求流程:外部流量 → Gateway(监听端口/域名) → VirtualService(匹配路由规则) → DestinationRule(定义后端子集、负载均衡策略) → Service(实际 Pod)

虚拟服务:流量路由的大脑

VirtualService 负责定义“流量应该往哪走”。它有点像 nginx 里的 location 块,但能力更强——可以按 header、URI、源标签做条件匹配,还能做权重分发、超时、重试、故障注入。

拿我参与的一个电商项目来说,当时要做灰度发布:新版本 v2 只接 5% 的流量,而且只针对内部测试账号。我用 VirtualService 配置了三条路由规则:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: orders-vs
spec:
  hosts:
  - orders.svc.cluster.local
  http:
  - match:
    - headers:
        x-test-user:
          exact: "true"
    route:
    - destination:
        host: orders.svc.cluster.local
        subset: v2
    - match:
    - uri:
        prefix: /api/v1/orders
    route:
    - destination:
        host: orders.svc.cluster.local
        subset: v1
      weight: 95
    - destination:
        host: orders.svc.cluster.local
        subset: v2
      weight: 5
⚠️ 注意: VirtualService 的 hosts 字段可以写 K8s Service 名称,也可以写 FQDN。我曾经手误把 hosts 写成服务简称但忘记加命名空间,结果路由完全不生效,排查了半天才发现。

目标规则:后端服务的详细配置

DestinationRule 是搭配 VirtualService 使用的“后端定义”。它告诉 Envoy:这个服务有哪些版本(subsets)、用什么负载均衡算法、连接池大小、TLS 怎么配。

你可以把 VirtualService 想象成导航路线,而 DestinationRule 就是路况——哪条道能走、限速多少。

属性作用典型配置
subsets按 label 划分版本,如 v1、v2labels: version: v2
trafficPolicy负载均衡、连接池、异常点检测loadBalancer: ROUND_ROBIN
connectionPoolTCP/HTTP 连接数限制tcp: maxConnections: 100

上面那个灰度场景的 DestinationRule 大致长这样:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: orders-dr
spec:
  host: orders.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_CONN
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

💡 个人习惯: 我一般把 VirtualService 和 DestinationRule 放在同一个 YAML 文件里,按 --- 分隔。这样部署时一次 apply 两个资源,不容易漏掉。

网关:流量进出的门禁

Gateway 负责管理网格边缘的入口和出口流量。它本质上就是一组监听端口 + TLS 凭证 + 主机名白名单。只有通过 Gateway 的流量才会被 VirtualService 处理。

我见过不少新手把 Gateway 和 Ingress 搞混。其实 Istio Gateway 比传统 K8s Ingress 灵活得多——你可以为每个微服务定义独立的网关,甚至做网关之间的级联。

下面是一个暴露内部服务 orders 的入口网关示例:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: orders-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "orders.mycompany.com"

配上 VirtualService 的 gateway 字段,就能把外部请求路由到你想要的服务上:

spec:
  gateways:
  - orders-gateway   # 关联网关
  hosts:
  - "orders.mycompany.com"

🔐 避坑指南: 我曾经在生产环境配了一个 HTTPS 网关,但忘记给 TLS 证书设置 credentialName,结果页面一直报“SSL_ERROR_BAD_CERT_DOMAIN”。后来发现是因为证书名写错了 namespace,自那以后我每次都会在 apply 前检查证书 Secret 是否存在。

三者如何协同工作

你想想看,一个外部用户访问 https://orders.mycompany.com/api/v1/orders,背后发生了什么?

  1. Gateway 监听到 443 端口,验证 TLS,确认 host 在白名单内,放行。
  2. VirtualService 根据 URI 前缀匹配到路由规则,决定将请求发往 orders.svc.cluster.local 的哪个 subset。
  3. DestinationRule 告诉 Envoy 这个 subset 的 Pod 有哪些、负载均衡用 LEAST_CONN、连接池上限 100。
  4. Envoy 选择一个健康的 Pod 转发请求。

说白了,Gateway 是门卫,VirtualService 是导航员,DestinationRule 是路况管理员。三者缺一不可,但也互相独立。你可以只配 VirtualService 和 DestinationRule 做集群内的东西向流量管理,不配 Gateway;也可以为了安全只暴露 Gateway 到公网。

📌 我常用的调试命令: 先 istioctl proxy-status 看 envoy 是否都同步到了配置,再 istioctl describe pod <pod-name> 确认 VirtualService / DestinationRule 有没有被正确合并。如果路由不生效,十有八九是 host 或 subset 拼写不一致。