第4章 核心概念:虚拟服务、目标规则与网关
聊到 Istio 流量管理,就绕不开三个老朋友:VirtualService、DestinationRule 和 Gateway。我最早接触它们的时候,觉得挺绕,又是虚拟又是规则又是网关的。后来在项目里亲手配了几次,踩了几回坑,才摸清楚它们各自管什么、怎么配合。
说白了,这三个资源就是 Istio 控制“谁来”、“去哪”、“怎么去”的三大手柄。你只要把这几样理清了,流量管理就打通了七成。
虚拟服务:流量路由的大脑
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
hosts 字段可以写 K8s Service 名称,也可以写 FQDN。我曾经手误把 hosts 写成服务简称但忘记加命名空间,结果路由完全不生效,排查了半天才发现。
目标规则:后端服务的详细配置
DestinationRule 是搭配 VirtualService 使用的“后端定义”。它告诉 Envoy:这个服务有哪些版本(subsets)、用什么负载均衡算法、连接池大小、TLS 怎么配。
你可以把 VirtualService 想象成导航路线,而 DestinationRule 就是路况——哪条道能走、限速多少。
| 属性 | 作用 | 典型配置 |
|---|---|---|
| subsets | 按 label 划分版本,如 v1、v2 | labels: version: v2 |
| trafficPolicy | 负载均衡、连接池、异常点检测 | loadBalancer: ROUND_ROBIN |
| connectionPool | TCP/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,背后发生了什么?
- Gateway 监听到 443 端口,验证 TLS,确认 host 在白名单内,放行。
- VirtualService 根据 URI 前缀匹配到路由规则,决定将请求发往
orders.svc.cluster.local的哪个 subset。 - DestinationRule 告诉 Envoy 这个 subset 的 Pod 有哪些、负载均衡用 LEAST_CONN、连接池上限 100。
- Envoy 选择一个健康的 Pod 转发请求。
说白了,Gateway 是门卫,VirtualService 是导航员,DestinationRule 是路况管理员。三者缺一不可,但也互相独立。你可以只配 VirtualService 和 DestinationRule 做集群内的东西向流量管理,不配 Gateway;也可以为了安全只暴露 Gateway 到公网。
📌 我常用的调试命令: 先 istioctl proxy-status 看 envoy 是否都同步到了配置,再 istioctl describe pod <pod-name> 确认 VirtualService / DestinationRule 有没有被正确合并。如果路由不生效,十有八九是 host 或 subset 拼写不一致。