1. VirtualService 是什么?
- Kubernetes Service:做服务发现与负载均衡。
- Istio VirtualService (VS):定义请求如何被路由。
-
Istio DestinationRule
(DR):定义目标服务的子集(subsets)及连接策略。 - Istio Gateway:把网格内服务暴露到外部。
一句话:Service 负责"找到谁",VirtualService
负责"怎么去",DestinationRule 负责"去向的细化与稳定性策略",Gateway
负责"从哪里进/出"。
2. 使用场景
- 金丝雀发布 / 蓝绿发布
- A/B 实验 / 精准灰度(Header/Cookie/Query)
- 路径路由与重写
- 超时/重试
- 流量镜像
- 故障注入
- gRPC 路由
- 网关入口路由
3. 示例:权重灰度
3.1 Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: productpage-v1
spec:
replicas: 1
selector:
matchLabels: { app: productpage, version: v1 }
template:
metadata:
labels: { app: productpage, version: v1 }
spec:
containers:
- name: app
image: your-registry/productpage:v1
ports: [{ containerPort: 8080 }]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: productpage-v2
spec:
replicas: 1
selector:
matchLabels: { app: productpage, version: v2 }
template:
metadata:
labels: { app: productpage, version: v2 }
spec:
containers:
- name: app
image: your-registry/productpage:v2
ports: [{ containerPort: 8080 }]
---
apiVersion: v1
kind: Service
metadata:
name: productpage
spec:
selector: { app: productpage }
ports:
- name: http
port: 80
targetPort: 8080
3.2 DestinationRule
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: productpage
spec:
host: productpage.default.svc.cluster.local
subsets:
- name: v1
labels: { version: v1 }
- name: v2
labels: { version: v2 }
3.3 VirtualService
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage.default.svc.cluster.local
http:
- route:
- destination:
host: productpage.default.svc.cluster.local
subset: v1
weight: 90
- destination:
host: productpage.default.svc.cluster.local
subset: v2
weight: 10
4. A/B 实验(按 Header 路由)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: productpage-ab
spec:
hosts:
- productpage.default.svc.cluster.local
http:
- match:
- headers:
x-user-role:
exact: staff
route:
- destination:
host: productpage.default.svc.cluster.local
subset: v2
- route:
- destination:
host: productpage.default.svc.cluster.local
subset: v1
5. 路径路由与重写
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api-gateway
spec:
hosts: ["api.example.com"]
gateways: ["ingress-gw"]
http:
- match:
- uri:
prefix: /api/
rewrite:
uri: /
route:
- destination:
host: productpage.default.svc.cluster.local
port: { number: 80 }
6. 超时与重试
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: productpage-retry
spec:
hosts: ["productpage.default.svc.cluster.local"]
http:
- timeout: 2s
retries:
attempts: 2
perTryTimeout: 1s
retryOn: "5xx,gateway-error,connect-failure,retriable-4xx"
route:
- destination:
host: productpage.default.svc.cluster.local
subset: v1
7. 流量镜像
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: productpage-mirror
spec:
hosts: ["productpage.default.svc.cluster.local"]
http:
- route:
- destination:
host: productpage.default.svc.cluster.local
subset: v1
mirror:
host: productpage.default.svc.cluster.local
subset: v2
mirrorPercentage:
value: 20.0
8. 故障注入
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: productpage-fault
spec:
hosts: ["productpage.default.svc.cluster.local"]
http:
- match:
- headers:
x-chaos:
exact: on
fault:
delay:
fixedDelay: 3s
percentage: { value: 100 }
route:
- destination:
host: productpage.default.svc.cluster.local
subset: v1
9. gRPC 路由
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: account-grpc
spec:
hosts: ["account.default.svc.cluster.local"]
http:
- match:
- uri:
prefix: /account.AccountService/
retries:
attempts: 2
perTryTimeout: 800ms
retryOn: "cancelled,connect-failure,refused-stream,5xx"
route:
- destination:
host: account.default.svc.cluster.local
subset: v1
10. Java 代码协作示例
Feign 全局拦截器
@Component
public class CanaryHeaderPropagate implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String header = CanaryContext.getHeader("x-user-role");
if (header != null) {
template.header("x-user-role", header);
}
}
}
Spring WebClient
WebClient client = WebClient.builder().build();
public Mono<String> callDownstream(ServerHttpRequest request) {
String role = request.getHeaders().getFirst("x-user-role");
return client.get()
.uri("http://productpage/api")
.headers(h -> { if (role != null) h.set("x-user-role", role); })
.retrieve()
.bodyToMono(String.class);
}
11. 常见坑与最佳实践
- 子集必须存在
- 匹配顺序:越精准的放前面
- 权重总和最好 100
- Header 名小写
- 网关与主机名要一致
- 观测先行:配合 Prometheus/Grafana/Kiali
- DR 配合 VS:连接池/熔断/逐出在 DR
12. 验证命令
curl -H "Host: api.example.com" http://<ingress-ip>/api/hello
curl -H "x-user-role: staff" http://productpage.default.svc.cluster.local
curl -H "x-chaos: on" http://productpage.default.svc.cluster.local -w '\n%{time_total}\n'
13. 模板清单
- 权重灰度
- Header/Cookie/Query 定向
- 路径路由 + 重写
- 超时 + 重试
- 流量镜像
- 故障注入
- gRPC