Kubernetes之Istio应用

一、基于istio安装灰度发布(金丝雀发布)

发布策略 核心目标 Istio 核心实现 应用场景
蓝绿发布 零停机、快速回滚 VirtualService 路由规则 版本切换、重大升级
A/B测试 根据规则分流用户 VirtualService + 请求头/URI匹配 功能试验、用户体验优化
镜像发布 生产流量复制测试 VirtualService 的 mirror 字段 新版本压力/稳定性测试
金丝雀/灰度 风险可控的渐进发布 VirtualService 的权重分流 新功能逐步上线
故障注入 主动测试系统韧性 VirtualService 的 fault 字段 混沌工程、健壮性测试

追求零风险和快速回滚 -> 蓝绿发布

需要根据用户属性测试功能 -> A/B测试

想用真实流量验证新版本性能 -> 镜像发布

需要可控、渐进式地推出新版本 -> 灰度发布

主动验证系统健壮性 -> 故障注入

1、二进制安装Istio

bash 复制代码
## 下载安装
wget https://github.com/istio/istio/releases/download/1.20.0/istio-1.20.0-linux-amd64.tar.gz

tar -xzf istio-1.20.0-linux-amd64.tar.gz
cd istio-1.20.0


# 1. 安装 istio-base (包含CRD)
helm install istio-base manifests/charts/base \
  -n istio-system \
  --create-namespace \
  --wait

# 2. 安装 istiod (控制平面)
helm install istiod manifests/charts/istio-control/istio-discovery \
  -n istio-system \
  --wait

# 3. 安装 istio-ingressgateway (入口网关)
helm install istio-ingressgateway manifests/charts/gateways/istio-ingress \
  -n istio-system \
  --set service.type=NodePort \
  --wait

2、 安装Isito查看

bash 复制代码
[root@mster isito]# helm list -n istio-system
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
istio-base              istio-system    1               2026-02-04 01:12:00.470351762 -0800 PST deployed        base-1.20.0             1.20.0     
istio-ingressgateway    istio-system    1               2026-02-04 01:16:24.240675604 -0800 PST failed          istio-ingress-1.20.0    1.20.0     
istiod                  istio-system    1               2026-02-04 01:12:14.260723994 -0800 PST deployed        istiod-1.20.0           1.20.0    

[root@mster isito]# # 查看Pod运行状态(所有Pod应为Running)
[root@mster isito]# kubectl get pods -n istio-system -w
NAME                                    READY   STATUS    RESTARTS       AGE
istio-ingressgateway-54cb8b478b-5n946   1/1     Running   3 (114m ago)   5d23h
istiod-999d56b9f-7pt8j                  1/1     Running   3 (114m ago)   5d23h
^C[root@mster isito]# 查看安装的服务
[root@mster isito]# kubectl get svc -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                                      AGE
istio-ingressgateway   LoadBalancer   10.104.4.238     <pending>     15021:32253/TCP,80:30881/TCP,443:30677/TCP   5d23h
istiod                 ClusterIP      10.110.103.147   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP        5d23h

3、创建流量管理配置

这是最关键的一步,你需要按顺序创建以下3个YAML文件,它们共同定义了完整的灰度规则。

bash 复制代码
cat  << EOF destinationrule.yaml   
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: demo-service
spec:
  host: demo-service.default.svc.cluster.local # K8s Service的完整域名
  subsets:
  - name: v1
    labels:
      version: v1 # 必须匹配v1 Pod的标签
  - name: v2
    labels:
      version: v2 # 必须匹配v2 Pod的标签
EOF
bash 复制代码
cat  << EOF gateway.yaml 
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: demo-gateway
spec:
  selector:
    istio: ingressgateway # 使用已安装的入口网关
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*" # 允许所有主机访问,生产环境请替换为你的域名,如 demo.example.com


EOF 
bash 复制代码
cat   << EOF virtualservice.yaml
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service
spec:
  hosts:
  - "*" # 匹配的域名,与Gateway中的hosts对应,生产环境请用具体域名
  gateways:
  - demo-gateway # 绑定上面创建的网关
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local # 目标服务
        subset: v1 # 全部流量指向v1
      weight: 80
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2 # 0%流量指向v2,初始状态
      weight: 20
   
EOF
bash 复制代码
# 按顺序应用配置
kubectl apply -f destinationrule.yaml
kubectl apply -f gateway.yaml
kubectl apply -f virtualservice.yaml

4、注意事项

bash 复制代码
执行kubectl apply -f destinationrule.yaml报以下错误:
error: resource mapping not found for name: "demo-service" namespace: "" from "destinationrule.yaml": no matches for kind "DestinationRule" in version "networking.istio.io/v1"
ensure CRDs are installed first
  • 查看以下模板是否安装
bash 复制代码
[root@mster isito]# kubectl get crd | grep istio
authorizationpolicies.security.istio.io               2026-02-04T09:11:58Z
destinationrules.networking.istio.io                  2026-02-04T09:11:58Z
envoyfilters.networking.istio.io                      2026-02-04T09:11:58Z
gateways.networking.istio.io                          2026-02-04T09:11:58Z
istiooperators.install.istio.io                       2026-02-04T09:11:58Z
peerauthentications.security.istio.io                 2026-02-04T09:11:58Z
proxyconfigs.networking.istio.io                      2026-02-04T09:11:58Z
requestauthentications.security.istio.io              2026-02-04T09:11:58Z
serviceentries.networking.istio.io                    2026-02-04T09:11:58Z
sidecars.networking.istio.io                          2026-02-04T09:11:58Z
telemetries.telemetry.istio.io                        2026-02-04T09:11:58Z
virtualservices.networking.istio.io                   2026-02-04T09:11:58Z
wasmplugins.extensions.istio.io                       2026-02-04T09:11:58Z
workloadentries.networking.istio.io                   2026-02-04T09:11:58Z
workloadgroups.networking.istio.io                    2026-02-04T09:11:58Z

[root@mster isito]# kubectl  get crd | grep -E 'destinationrule|virtualservice|gateway|serviceentries' | head -5
destinationrules.networking.istio.io                  2026-02-04T09:11:58Z
gateways.networking.istio.io                          2026-02-04T09:11:58Z
serviceentries.networking.istio.io                    2026-02-04T09:11:58Z
virtualservices.networking.istio.io                   2026-02-04T09:11:58Z
bash 复制代码
 # 查询 DestinationRule 支持的所有 API 版本
[root@mster isito]# kubectl api-resources | grep -i destinationrule
destinationrules                    dr           networking.istio.io/v1beta1       true         DestinationRule
[root@mster isito]# 
  • 修改apiVersion
bash 复制代码
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1

# 按顺序应用配置
kubectl apply -f destinationrule.yaml
kubectl apply -f gateway.yaml
kubectl apply -f virtualservice.yaml

5、创建service和deployment

bash 复制代码
cat  << EOF demo-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: demo-service
  namespace: default
spec:
  selector:
    app: demo-service  # 这个标签必须匹配你的Deployment中Pod的标签
  ports:
  - port: 80          # Service端口
    targetPort: 80    # Pod容器端口
    protocol: TCP
EOF
bash 复制代码
cat  << EOF demo-deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-service-v1
  labels:
    app: demo-service
    version: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo-service
      version: v1
  template:
    metadata:
      labels:
        app: demo-service
        version: v1
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        - name: NGINX_VERSION_TAG
          value: "v1"
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "echo 'Demo Service Version: v1' > /usr/share/nginx/html/index.html"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-service-v2
  labels:
    app: demo-service
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-service
      version: v2
  template:
    metadata:
      labels:
        app: demo-service
        version: v2
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        - name: NGINX_VERSION_TAG
          value: "v2"
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "echo 'Demo Service Version: v2' > /usr/share/nginx/html/index.html"]
             
EOF             
bash 复制代码
## 按照顺序执行
kubectl apply -f demo-service.yaml
kubectl apply -f demo-deployment.yaml

6、测试与验证

1、修改权重比

bash 复制代码
cat << EOF virtualservice.yaml
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service
spec:
  hosts:
  - "*" # 匹配的域名,与Gateway中的hosts对应,生产环境请用具体域名
  gateways:
  - demo-gateway # 绑定上面创建的网关
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local # 目标服务
        subset: v1 # 全部流量指向v1
      weight: 80
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2 # 0%流量指向v2,初始状态
      weight: 20

EOF
bash 复制代码
kubectl apply -f virtualservice.yaml

2、测试联通性

bash 复制代码
 kubectl run test-curl --image=curlimages/curl --rm -it --restart=Never -- sh -c 'curl -s http://demo-service.default.svc.cluster.local'

Demo Service Version: v1
pod "test-curl" deleted

预期输出:Demo Service Version: v1 或 Demo Service Version: v2(根据你的VirtualService权重分配)。

3、通过网关进行外部测试:

bash 复制代码
export GATEWAY_URL=http://192.168.1.100:30881

# 发送多个请求,观察版本分布
for i in {1..10}; do
    curl -s $GATEWAY_URL
done

# 或者统计版本
for i in {1..20}; do
    curl -s $GATEWAY_URL | grep -o "v[12]"
done | sort | uniq -c

预期结果:根据你VirtualService中配置的权重(v1:80, v2:20),你可能会看到大约80%的请求返回"v1",20%返回"v2"。

4、再次调整权重比例

bash 复制代码
 cat << EOF  virtualservice.yaml 
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service
spec:
  hosts:
  - "*" # 匹配的域名,与Gateway中的hosts对应,生产环境请用具体域名
  gateways:
  - demo-gateway # 绑定上面创建的网关
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local # 目标服务
        subset: v1 # 全部流量指向v1
      weight: 10
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2 # 0%流量指向v2,初始状态
      weight: 90
     
EOF
bash 复制代码
kubectl apply -f virtualservice.yaml
bash 复制代码
kubectl run test-curl --image=curlimages/curl --rm -it --restart=Never -- sh -c 'curl -s http://demo-service.default.svc.cluster.local'
If you don't see a command prompt, try pressing enter.
Demo Service Version: v1
pod "test-curl" deleted

二、基于Isito的蓝绿发布

蓝绿发布与灰度发布的区别

  • 蓝绿发布
text 复制代码
状态 A(切换前):[🔵🔵🔵🔵🔵] 100% v1  +  🟢🟢🟢 0% v2(待机)
                ↓ 执行 kubectl apply(瞬间切换)
状态 B(切换后):[🔵🔵🔵] 0% v1      +  🟢🟢🟢🟢🟢 100% v2
  • 灰度发布
text 复制代码
阶段 1: [🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵] 100% v1  +  🟢 0% v2
                ↓ 调整权重
阶段 2: [🔵🔵🔵🔵🔵🔵🔵🔵🔵] 90% v1   +  🟢🟢 10% v2(观察1小时)
                ↓ 调整权重
阶段 3: [🔵🔵🔵🔵🔵] 50% v1          +  🟢🟢🟢🟢🟢 50% v2(观察1小时)
                ↓ 调整权重
阶段 4: [🔵] 0% v1                 +  🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢 100% v2

✅ 选蓝绿发布的情况:

必须全量:法规要求或业务特性,不允许新旧版本并存(如支付接口)

依赖强一致性:数据库Schema不兼容,不能同时写两个版本

资源充足:你有两套完整的生产环境资源

追求极简操作:不想分阶段放量,就想一键切换、一键回滚

真实案例:银行核心系统升级。周日凌晨2点,100%流量从旧系统瞬间切换到新系统,验证5分钟没问题就收工,有问题立刻切回。

✅ 选灰度发布的情况:

功能迭代:每周/每天发布新功能,风险逐步释放

用户体验优先:不想让100%用户同时遇到bug

资源受限:没有两套完整环境的资源预算

需要真实反馈:想收集一小部分用户的真实使用数据

真实案例:某App改版首页。先让5%的用户体验新界面,观察3天,点击率提升10%后,扩大到50%,没问题再全量。

1、创建流量配置

bash 复制代码
cat << EOF  destinationrule.yaml 
# destinationrule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: demo-service
  namespace: default
spec:
  host: demo-service.default.svc.cluster.local
  subsets:
  - name: blue  # 蓝组 = v1
    labels:
      version: v1
  - name: green # 绿组 = v2
    labels:
      version: v2
EOF
bash 复制代码
cat  << EOF gateway.yaml 
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: demo-gateway
spec:
  selector:
    istio: ingressgateway # 使用已安装的入口网关
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*" # 允许所有主机访问,生产环境请替换为你的域名,如 demo.example.com

EOF 
bash 复制代码
cat  << EOF virtualservice.yaml  
# virtualservice-bluegreen.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service
  namespace: default
spec:
  hosts:
  - "*"  # 与Gateway保持一致
  gateways:
  - demo-gateway
  - mesh   # 添加这一行,使规则对网格内 sidecar 也生效
  http:
  - route:
    # 🔵 蓝组 (当前生产环境 - v1)
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: blue
      weight: 100  # 蓝组流量百分比
    # 🟢 绿组 (待发布版本 - v2)  
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: green
      weight: 0    # 绿组流量百分比
EOF 
bash 复制代码
cat  << EOF demo-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: demo-service
  namespace: default
spec:
  selector:
    app: demo-service  # 这个标签必须匹配你的Deployment中Pod的标签
  ports:
  - port: 80          # Service端口
    targetPort: 80    # Pod容器端口
    protocol: TCP

EOF 
bash 复制代码
cat  << EOF deployment-gree-blue-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-service-v1
  namespace: default
  labels:
    app: demo-service
    version: v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo-service
      version: v1
  template:
    metadata:
      labels:
        app: demo-service
        version: v1
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        command: ["/bin/sh"]
        args: ["-c", "echo 'Version: v1 - $(date)' > /usr/share/nginx/html/index.html; nginx -g 'daemon off;'"]

EOF 
bash 复制代码
cat << EOF  deployment-gree-blue-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-service-v2
  namespace: default
  labels:
    app: demo-service
    version: v2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: demo-service
      version: v2
  template:
    metadata:
      labels:
        app: demo-service
        version: v2
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        command: ["/bin/sh"]
        args: ["-c", "echo 'Version: v2 - $(date)' > /usr/share/nginx/html/index.html; nginx -g 'daemon off;'"]


EOF

2、测试与验证

1、容器内存测试(有错误后续分析原因)

  • 安装测试pod (istio是由二进制安装)
bash 复制代码
kubectl apply -f ~/istio-1.20.0/samples/sleep/sleep.yaml
bash 复制代码
kubectl exec deploy/sleep -- sh -c '
  for i in $(seq 1 100); do 
    curl -s http://demo-service.default.svc.cluster.local | grep -o "v[12]"
  done' | sort | uniq -c
     45 v1
     55 v2

2、主机测试

bash 复制代码
[root@node ~]# export GATEWAY_URL=http://192.168.1.100:30881
[root@node ~]# for i in {1..100}; do curl -s $GATEWAY_URL; done | grep -o "v[12]" | sort | uniq -c
    100 v1

3、pod内部无法安装全流量注入的原因以及修复方案

1、为 default 命名空间开启自动注入

bash 复制代码
kubectl label namespace default istio-injection=enabled --overwrite
kubectl get namespace default -L istio-injection
  • default 命名空间没有启用 Istio sidecar 自动注入(ISTIO-INJECTION 列为空),这是导致 sleep Pod 没有 sidecar 的根本原因。
bash 复制代码
# 删除已存在的 sleep 部署
kubectl delete deploy,sa,svc -l app=sleep

# 重新应用 sleep.yaml(推荐使用 Istio 官方示例)
kubectl apply -f istio-1.20.0/samples/sleep/sleep.yaml
# 如果您没有离线包,也可以使用以下命令在线获取:
# kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/sleep/sleep.yaml
bash 复制代码
kubectl get pod -l app=sleep -w
text 复制代码
NAME                     READY   STATUS    RESTARTS   AGE
sleep-xxx-xxx            2/2     Running   0          30s
  • 这样操作下来。pod 内部流量还是会打到v2上

2、 修改pod 无法全部打到v1版本上

  • 现有的 demo-service VirtualService 仅用于网关(demo-gateway),hosts: ["*"] 不变。
    新建一个 VirtualService 专门用于网格内部(mesh),hosts 设置为您的服务全名
bash 复制代码
cat  << EOF vs-internal.yaml  


apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-internal
  namespace: default
spec:
  hosts:
  - demo-service.default.svc.cluster.local  # 必须指定确切的服务 FQDN
  gateways:
  - mesh                                    # 仅作用于网格内部
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: blue
      weight: 100
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: green
      weight: 0
     
EOF      
  • pod 内部流量无法打打v1版本上,在实际生产中,如果pod 无法打到同一个版本中,就会导致,接口调用报错

4、撤换流量测试

1、为两个vs进行打标签

bash 复制代码
# 给 demo-service 打标签
kubectl label vs demo-service release-group=bluegreen

# 给 demo-service-internal 打标签
kubectl label vs demo-service-internal release-group=bluegreen

2、查看标签

bash 复制代码
## 打标签前
[root@mster isito]# kubectl get virtualservices.networking.istio.io --show-labels
NAME                    GATEWAYS           HOSTS                                        AGE     LABELS
demo-service            ["demo-gateway"]   ["*"]                                        3d17h   <none>
demo-service-internal   ["mesh"]           ["demo-service.default.svc.cluster.local"]   30h     <none>


## 打标签后
[root@mster isito]# kubectl get virtualservices.networking.istio.io --show-labels
NAME                    GATEWAYS           HOSTS                                        AGE     LABELS
demo-service            ["demo-gateway"]   ["*"]                                        3d17h   release-group=bluegreen
demo-service-internal   ["mesh"]           ["demo-service.default.svc.cluster.local"]   30h     release-group=bluegreen

3、设计流量切换

通过更改配置文件

bash 复制代码
# 获取所有带有标签 release-group=bluegreen 的 VirtualService 名称
for vs in $(kubectl get vs -n default -l release-group=bluegreen -o name); do
  echo "Patching $vs ..."
  kubectl patch $vs -n default --type='json' \
    -p='[
      {"op": "replace", "path": "/spec/http/0/route/0/weight", "value": 100},
      {"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 0}
    ]'
done

通过脚本形式进行替换

bash 复制代码
cat << EOF switch.sh 
#!/bin/bash
# 用法: ./switch.sh blue 100   # 将两个 VS 的 blue 权重设为 100, green 为 0
#       ./switch.sh green 100  # 将两个 VS 的 green 权重设为 100, blue 为 0

TARGET=$1
WEIGHT=$2

if [ "$TARGET" == "blue" ]; then
  BLUE_WEIGHT=$WEIGHT
  GREEN_WEIGHT=$((100 - WEIGHT))
elif [ "$TARGET" == "green" ]; then
  BLUE_WEIGHT=$((100 - WEIGHT))
  GREEN_WEIGHT=$WEIGHT
else
  echo "Usage: $0 [blue|green] weight"
  exit 1
fi

for vs in demo-service demo-service-internal; do
  echo "Patching $vs to blue=$BLUE_WEIGHT, green=$GREEN_WEIGHT"
  kubectl patch vs $vs -n default --type='json' -p="[
    {\"op\": \"replace\", \"path\": \"/spec/http/0/route/0/weight\", \"value\": $BLUE_WEIGHT},
    {\"op\": \"replace\", \"path\": \"/spec/http/0/route/1/weight\", \"value\": $GREEN_WEIGHT}
  ]"
done

EOF

4、查看权重信息

bash 复制代码
 kubectl get vs -l release-group=bluegreen -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.http[0].route}{"\n"}{end}'

5、测试版本切换

bash 复制代码
 ./switch.sh   blue 100 
./switch.sh   green 100 

## 容器测试
kubectl exec deploy/sleep -- sh -c '
  for i in $(seq 1 100); do 
    curl -s http://demo-service.default.svc.cluster.local | grep -o "v[12]"
  done' | sort | uniq -c

## 主机测试
export GATEWAY_URL=http://192.168.1.100:30881
for i in {1..100}; do curl -s $GATEWAY_URL; done | grep -o "v[12]" | sort | uniq -c


三、基于Istio的A\B测试发布方案

1、内部网关

bash 复制代码
cat << EOF vs-ab-testing.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-ab-internal
  namespace: default
spec:
  hosts:
  - "demo-service.default.svc.cluster.local"
  gateways:
  - mesh
  http:
  - match:
    - headers:
        x-experiment:
          exact: "beta"
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2
  - match:
    - headers:
        user-agent:
          regex: ".*Mobile.*"
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1
       
EOF      

2、外部网关

bash 复制代码
 cat  <<  EOF vs-ab-gateway.yaml 
 apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-ab-gateway
  namespace: default
spec:
  hosts:
  - '*'
  gateways:
  - demo-gateway
  http:
  - match:
    - headers:
        cookie:
          regex: .*experiment=beta.*
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2
  - match:
    - headers:
        user-agent:
          regex: .*(iPhone|Android|Mobile).*
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v2
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1

EOF

3、路由策略

bash 复制代码
cat  << EOF destinationrule.yaml
# destinationrule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: demo-service
  namespace: default
spec:
  host: demo-service.default.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v1
    labels:
      version: v2
   
EOF 

4、deployment

bash 复制代码
cat  << EOF demo-service-v1.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"demo-service","version":"v1"},"name":"demo-service-v1","namespace":"default"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"demo-service","version":"v1"}},"template":{"metadata":{"labels":{"app":"demo-service","version":"v1"}},"spec":{"containers":[{"args":["-c","echo 'Version: v1 - $(date)' \u003e /usr/share/nginx/html/index.html; nginx -g 'daemon off;'"],"command":["/bin/sh"],"image":"nginx:alpine","name":"nginx","ports":[{"containerPort":80}]}]}}}}
  creationTimestamp: "2026-02-12T16:45:43Z"
  generation: 1
  labels:
    app: demo-service
    version: v1
  name: demo-service-v1
  namespace: default
  resourceVersion: "89185"
  uid: 09f554fb-e447-4bf2-9338-71556d0cfab1
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: demo-service
      version: v1
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: demo-service
        version: v1
    spec:
      containers:
      - args:
        - -c
        - 'echo ''Version: v1 - $(date)'' > /usr/share/nginx/html/index.html; nginx
          -g ''daemon off;'''
        command:
        - /bin/sh
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: "2026-02-12T16:45:43Z"
    lastUpdateTime: "2026-02-12T16:45:46Z"
    message: ReplicaSet "demo-service-v1-8689c65f4" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2026-02-14T01:32:53Z"
    lastUpdateTime: "2026-02-14T01:32:53Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 1
  readyReplicas: 2
  replicas: 2
  updatedReplicas: 2

EOF
bash 复制代码
cat   << EOF  demo-service-v2.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"demo-service","version":"v2"},"name":"demo-service-v2","namespace":"default"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"demo-service","version":"v2"}},"template":{"metadata":{"labels":{"app":"demo-service","version":"v2"}},"spec":{"containers":[{"args":["-c","echo 'Version: v2 - $(date)' \u003e /usr/share/nginx/html/index.html; nginx -g 'daemon off;'"],"command":["/bin/sh"],"image":"nginx:alpine","name":"nginx","ports":[{"containerPort":80}]}]}}}}
  creationTimestamp: "2026-02-12T16:47:06Z"
  generation: 1
  labels:
    app: demo-service
    version: v2
  name: demo-service-v2
  namespace: default
  resourceVersion: "89242"
  uid: 1a27302b-6367-4961-a403-76b98fba7725
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: demo-service
      version: v2
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: demo-service
        version: v2
    spec:
      containers:
      - args:
        - -c
        - 'echo ''Version: v2 - $(date)'' > /usr/share/nginx/html/index.html; nginx
          -g ''daemon off;'''
        command:
        - /bin/sh
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: "2026-02-12T16:47:06Z"
    lastUpdateTime: "2026-02-12T16:47:09Z"
    message: ReplicaSet "demo-service-v2-6698597ff4" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2026-02-14T01:32:56Z"
    lastUpdateTime: "2026-02-14T01:32:56Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 1
  readyReplicas: 2
  replicas: 2
  updatedReplicas: 2

EOF

5、service

bash 复制代码
cat << EOF demo-service.yaml 
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"demo-service","namespace":"default"},"spec":{"ports":[{"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"demo-service"}}}
  creationTimestamp: "2026-02-10T07:58:49Z"
  name: demo-service
  namespace: default
  resourceVersion: "39883"
  uid: ad840115-cc2c-4e89-bac5-158d94c524ff
spec:
  clusterIP: 10.97.68.36
  clusterIPs:
  - 10.97.68.36
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demo-service
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

EOF

6、执行以上所有资源配置文件

7、测试验证

bash 复制代码
##集群pod内部应用
[root@mster isito]# kubectl exec deploy/sleep -- curl -s http://demo-service.default.svc.cluster.local
Version: v1 - $(date)
[root@mster isito]# kubectl exec deploy/sleep -- curl -s -H "X-Experiment: beta" http://demo-service.default.svc.cluster.local
Version: v2 - $(date)
[root@mster isito]# kubectl exec deploy/sleep -- curl -s -A "Mobile" http://demo-service.default.svc.cluster.local
Version: v2 - $(date)
[root@mster isito]# kubectl exec deploy/sleep -- curl -s -A "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" http://demo-service.default.svc.cluster.local
Version: v2 - $(date)

### 外部链条访问
[root@node ~]# export GATEWAY_URL=http://192.168.1.100:30881
[root@node ~]# curl -s $GATEWAY_URL
Version: v1 - $(date)
[root@node ~]# curl -s --cookie "experiment=beta" $GATEWAY_URL
Version: v2 - $(date)
[root@node ~]# curl -s -A "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" $GATEWAY_URL
Version: v2 - $(date)
[root@node ~]# 

四、基于Istio的镜像发布方案

🧩 为什么镜像发布不属于发布流程?

目的不同:发布流程的核心是让用户访问新版本(如蓝绿、灰度)。镜像发布的目的是在不影响用户的前提下,用真实流量测试新版本,新版本的响应被丢弃,用户完全无感知。

阶段不同:镜像发布通常发生在发布之前,作为"预发布"验证环节。只有当镜像验证通过后,才会进入真正的发布流程(如灰度或蓝绿发布),将流量切换到新版本。

用户无变化:发布流程一定会改变用户所见的服务版本或行为。镜像发布则完全不改变用户体验,仅用于后端评估。

1、镜像发布与A\B测试的区别

策略 镜像发布 A/B测试
本质 流量复制(影子流量) 流量分流(真实用户)
目标 在不影响生产的情况下... 对比两个版本的业务效果...
用户感知 无感知(响应仍来自原版本) 有感知(部分用户直接使用新版本)

镜像发布:主请求 → v1(响应返回客户端),同时复制一份 → v2(响应被丢弃)。用户无感知,仅用于后端测试。

权重路由(蓝绿/灰度发布):根据权重将真实用户请求直接发送到 v1 或 v2,客户端会直接收到对应版本的响应。

2、内部网关

bash 复制代码
cat   << EOF vs-mirror-internal.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-mirror-internal
  namespace: default
spec:
  hosts:
  - "demo-service.default.svc.cluster.local"
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1
      weight: 100
    mirror:
      host: demo-service.default.svc.cluster.local
      subset: v2
    mirrorPercentage:
      value: 50.0
EOF

3、外部网关

bash 复制代码
cat  << EOF vs-mirror-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-mirror-gateway
  namespace: default
spec:
  hosts:
  - "*"
  gateways:
  - demo-gateway
  http:
  - route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1
      weight: 100
    mirror:
      host: demo-service.default.svc.cluster.local
      subset: v2
    mirrorPercentage:
      value: 50.0   # 镜像 50% 的请求,可调整
     
EOF      

4、应用资源

bash 复制代码
## 应用以上两个资源文件
## 关闭所有多余的vs资源
[root@mster isito]# kubectl get vs 
NAME                           GATEWAYS           HOSTS                                        AGE
demo-service-mirror-gateway    ["demo-gateway"]   ["*"]                                        82m
demo-service-mirror-internal   ["mesh"]           ["demo-service.default.svc.cluster.local"]   82m

5、查看dr资源

bash 复制代码
[root@mster isito]# kubectl get dr
NAME           HOST                                     AGE
demo-service   demo-service.default.svc.cluster.local   3d22h
[root@mster isito]# kubectl get dr demo-service  -o yaml 
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"networking.istio.io/v1beta1","kind":"DestinationRule","metadata":{"annotations":{},"name":"demo-service","namespace":"default"},"spec":{"host":"demo-service.default.svc.cluster.local","subsets":[{"labels":{"version":"v1"},"name":"v1"},{"labels":{"version":"v2"},"name":"v1"}]}}
  creationTimestamp: "2026-02-10T07:42:57Z"
  generation: 4
  name: demo-service
  namespace: default
  resourceVersion: "99440"
  uid: b5222fb0-6371-4bfc-bc84-c3ad991afc1d
spec:
  host: demo-service.default.svc.cluster.local
  subsets:
  - labels:
      version: v1
    name: v1
  - labels:
      version: v2
    name: v2

6、测试验证

bash 复制代码
## 查看deployment资源
[root@mster isito]# kubectl get deployment 
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
demo-service-v1   2/2     2            2           37h
demo-service-v2   2/2     2            2           37h
sleep             1/1     1            1           36h
[root@mster isito]# kubectl get deployment --show-labels
NAME              READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
demo-service-v1   2/2     2            2           37h   app=demo-service,version=v1
demo-service-v2   2/2     2            2           37h   app=demo-service,version=v2
sleep             1/1     1            1           36h   <none>
  • 内部pod测试
bash 复制代码
[root@mster isito]# for i in {1..10}; do   kubectl exec $SLEEP_POD -- curl -s http://demo-service.default.svc.cluster.local  ; done
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
[root@mster isito]# kubectl logs -l version=v2 -c istio-proxy --tail=20
error: container istio-proxy is not valid for pod demo-service-v2-6698597ff4-qqbpm
[root@mster isito]# kubectl logs -l version=v2 --tail=10
10.244.167.186 - - [14/Feb/2026:02:09:03 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.184 - - [14/Feb/2026:02:59:06 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "10.244.219.64"
10.244.167.184 - - [14/Feb/2026:03:01:37 +0000] "GET / HTTP/1.1" 200 22 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" "10.244.219.64"
10.244.167.184 - - [14/Feb/2026:03:22:14 +0000] "GET / HTTP/1.1" 200 22 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" "10.244.219.64"
10.244.167.186 - - [14/Feb/2026:03:25:35 +0000] "GET / HTTP/1.1" 200 22 "-" "Mobile" "-"
10.244.167.186 - - [14/Feb/2026:03:25:44 +0000] "GET / HTTP/1.1" 200 22 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" "-"
10.244.167.186 - - [14/Feb/2026:04:45:45 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.184 - - [14/Feb/2026:04:50:27 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "10.244.219.64,10.244.167.184"
10.244.167.186 - - [14/Feb/2026:04:53:25 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:04:56:48 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:03:21:41 +0000] "GET / HTTP/1.1" 200 22 "-" "Mobile" "-"
10.244.167.184 - - [14/Feb/2026:03:22:06 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "10.244.219.64"
10.244.167.186 - - [14/Feb/2026:03:25:27 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:45:45 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:04:45:46 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.184 - - [14/Feb/2026:04:51:07 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/7.29.0" "10.244.219.64,10.244.167.184"
10.244.167.186 - - [14/Feb/2026:04:56:47 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:04:56:47 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:04:56:48 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
10.244.167.186 - - [14/Feb/2026:04:58:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "10.244.167.186"
[root@mster isito]# date
Fri Feb 13 20:58:39 PST 2026
[root@mster isito]# kubectl logs -l version=v1 --tail=10
10.244.167.186 - - [14/Feb/2026:04:58:10 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:10 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:11 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:11 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:16 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:17 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:17 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:56:47 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:56:47 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:56:49 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:07 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:10 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:10 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:17 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:17 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:17 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
10.244.167.186 - - [14/Feb/2026:04:58:18 +0000] "GET / HTTP/1.1" 200 22 "-" "curl/8.18.0" "-"
You have new mail in /var/spool/mail/root
  • 外部主机测试
bash 复制代码
root@node ~]# for i in {1..10}; do   curl  $GATEWAY_URL ; done
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
Version: v1 - $(date)
[root@node ~]# date
Fri Feb 13 20:58:01 PST 2026

执行 curl 命令返回的都是 Version: v1

这是因为镜像发布中,主请求仍然由 v1 处理并返回响应,用户只能看到 v1 的结果。这是镜像发布的核心设计:让用户无感知,只将流量复制到 v2 进行后端验证。

v2 Pod 的日志中出现了最新的请求记录

您通过 kubectl logs -l version=v2 --tail=10 看到了来自 sleep Pod IP(10.244.167.186)的请求,例如 04:58:18 的日志。这些请求的时间与您测试的时间(PST 20:58)匹配,证明v2 确实收到了镜像流量。

7、版本切换

1、 切换脚本

bash 复制代码
cat    << EOF  adjust-traffic.sh 
#!/bin/bash
#
# 智能流量调整脚本 (适用于 Istio VirtualService)
# 用法:
#   ./adjust-traffic.sh [--create] <v1-weight> <v2-weight>
#   ./adjust-traffic.sh --target <version> <weight>
#   ./adjust-traffic.sh --step <target-version> <target-weight> [--interval <秒>]
#
# 示例:
#   ./adjust-traffic.sh 90 10                # 将两个 VS 的权重设置为 v1=90, v2=10
#   ./adjust-traffic.sh --target v2 50        # 将 v2 权重设为 50,v1 自动为 50
#   ./adjust-traffic.sh --create 80 20        # 创建网关和内部 VS (如果不存在),并设权重
#   ./adjust-traffic.sh --step v2 100 --interval 10  # 每10秒增加 10% 直到 v2=100

set -e

# 默认配置
NAMESPACE=${NAMESPACE:-default}
GW_VS_NAME=${GW_VS_NAME:-demo-service-canary-gateway}
INTERNAL_VS_NAME=${INTERNAL_VS_NAME:-demo-service-canary-internal}
GATEWAY_NAME=${GATEWAY_NAME:-demo-gateway}
SERVICE_FQDN=${SERVICE_FQDN:-demo-service.default.svc.cluster.local}
DR_NAME=${DR_NAME:-demo-service}

# 检查依赖命令
for cmd in kubectl jq; do
    if ! command -v $cmd &> /dev/null; then
        echo "错误: 未找到命令 '$cmd',请先安装。"
        exit 1
    fi
done

# 显示帮助
usage() {
    echo "用法: $0 [选项] <v1权重> <v2权重>"
    echo "  或: $0 --target <版本> <权重>"
    echo "  或: $0 --step <目标版本> <目标权重> [--interval <秒>]"
    echo ""
    echo "选项:"
    echo "  --create         如果 VirtualService 不存在则创建 (默认使用模板)"
    echo "  --target <ver>   指定目标版本 (v1 或 v2),权重将设为给定值,另一版本自动补全"
    echo "  --step           逐步调整模式,需指定目标版本和目标权重"
    echo "  --interval <秒>  逐步调整时的间隔 (默认 10 秒)"
    echo "  --help           显示此帮助"
    exit 0
}

# 检查参数
if [ $# -eq 0 ]; then usage; fi

# 解析参数
CREATE=false
STEP=false
INTERVAL=10
TARGET=""
TARGET_WEIGHT=""

while [[ $# -gt 0 ]]; do
    case $1 in
        --help) usage ;;
        --create) CREATE=true; shift ;;
        --step) STEP=true; shift ;;
        --interval) INTERVAL=$2; shift 2 ;;
        --target) TARGET=$2; TARGET_WEIGHT=$3; shift 3 ;;
        *) break ;;
    esac
done

# 权重处理
if [ -n "$TARGET" ]; then
    if [[ ! "$TARGET" =~ ^(v1|v2)$ ]]; then
        echo "错误: --target 必须是 v1 或 v2"
        exit 1
    fi
    if ! [[ "$TARGET_WEIGHT" =~ ^[0-9]+$ ]] || [ "$TARGET_WEIGHT" -lt 0 ] || [ "$TARGET_WEIGHT" -gt 100 ]; then
        echo "错误: 权重必须是 0-100 的整数"
        exit 1
    fi
    if [ "$TARGET" == "v1" ]; then
        V1_WEIGHT=$TARGET_WEIGHT
        V2_WEIGHT=$((100 - TARGET_WEIGHT))
    else
        V2_WEIGHT=$TARGET_WEIGHT
        V1_WEIGHT=$((100 - TARGET_WEIGHT))
    fi
else
    if [ $# -ne 2 ]; then
        echo "错误: 需要指定 v1 和 v2 权重"
        usage
    fi
    V1_WEIGHT=$1
    V2_WEIGHT=$2
    if ! [[ "$V1_WEIGHT" =~ ^[0-9]+$ ]] || ! [[ "$V2_WEIGHT" =~ ^[0-9]+$ ]] || [ $((V1_WEIGHT + V2_WEIGHT)) -ne 100 ]; then
        echo "错误: 权重必须为 0-100 的整数,且总和为 100"
        exit 1
    fi
fi

# 逐步调整模式
if [ "$STEP" = true ]; then
    if [ -z "$TARGET" ] || [ -z "$TARGET_WEIGHT" ]; then
        echo "错误: --step 需要指定 --target <版本> <目标权重>"
        exit 1
    fi
    # 获取当前权重
    if ! kubectl get vs $GW_VS_NAME -n $NAMESPACE &>/dev/null; then
        echo "错误: VirtualService $GW_VS_NAME 不存在,无法逐步调整"
        exit 1
    fi
    # 解析当前权重
    CUR_V1=$(kubectl get vs $GW_VS_NAME -n $NAMESPACE -o jsonpath='{.spec.http[0].route[?(@.destination.subset=="v1")].weight}')
    CUR_V2=$(kubectl get vs $GW_VS_NAME -n $NAMESPACE -o jsonpath='{.spec.http[0].route[?(@.destination.subset=="v2")].weight}')
    if [ -z "$CUR_V1" ] || [ -z "$CUR_V2" ]; then
        echo "错误: 无法从 $GW_VS_NAME 中获取当前权重"
        exit 1
    fi
    CURRENT_V1=$CUR_V1
    CURRENT_V2=$CUR_V2
    echo "当前权重: v1=$CURRENT_V1, v2=$CURRENT_V2"
    # 计算增量
    if [ "$TARGET" == "v1" ]; then
        TARGET_V1=$TARGET_WEIGHT
        TARGET_V2=$((100 - TARGET_WEIGHT))
        DIFF_V1=$((TARGET_V1 - CURRENT_V1))
        DIFF_V2=$(( - DIFF_V1 ))
    else
        TARGET_V2=$TARGET_WEIGHT
        TARGET_V1=$((100 - TARGET_WEIGHT))
        DIFF_V2=$((TARGET_V2 - CURRENT_V2))
        DIFF_V1=$(( - DIFF_V2 ))
    fi
    if [ $DIFF_V1 -eq 0 ] && [ $DIFF_V2 -eq 0 ]; then
        echo "当前已是目标权重,无需调整。"
        exit 0
    fi
    # 确定步长(每次调整 10% 或 5%,视差值而定,至少 1%)
    STEP_SIZE=10
    if [ ${DIFF_V1#-} -lt $STEP_SIZE ]; then
        STEP_SIZE=${DIFF_V1#-}
    fi
    if [ ${DIFF_V2#-} -lt $STEP_SIZE ]; then
        STEP_SIZE=${DIFF_V2#-}
    fi
    if [ $STEP_SIZE -eq 0 ]; then STEP_SIZE=1; fi
    echo "将逐步调整,每 $INTERVAL 秒调整 $STEP_SIZE% ..."
    while true; do
        # 计算下一步权重
        NEXT_V1=$CURRENT_V1
        NEXT_V2=$CURRENT_V2
        if [ $DIFF_V1 -gt 0 ]; then
            NEXT_V1=$((CURRENT_V1 + STEP_SIZE > TARGET_V1 ? TARGET_V1 : CURRENT_V1 + STEP_SIZE))
            NEXT_V2=$((100 - NEXT_V1))
        elif [ $DIFF_V1 -lt 0 ]; then
            NEXT_V1=$((CURRENT_V1 - STEP_SIZE < TARGET_V1 ? TARGET_V1 : CURRENT_V1 - STEP_SIZE))
            NEXT_V2=$((100 - NEXT_V1))
        fi
        echo "调整至: v1=$NEXT_V1, v2=$NEXT_V2"
        # 执行更新
        update_vs "$NEXT_V1" "$NEXT_V2"
        CURRENT_V1=$NEXT_V1
        CURRENT_V2=$NEXT_V2
        if [ $CURRENT_V1 -eq $TARGET_V1 ] && [ $CURRENT_V2 -eq $TARGET_V2 ]; then
            echo "已达到目标权重。"
            break
        fi
        sleep $INTERVAL
    done
    exit 0
fi

# 更新 VirtualService 的函数
update_vs() {
    local v1=$1
    local v2=$2
    echo "更新网关 VirtualService $GW_VS_NAME 为 v1=$v1, v2=$v2"
    kubectl patch vs $GW_VS_NAME -n $NAMESPACE --type='json' -p="[
        {\"op\": \"replace\", \"path\": \"/spec/http/0/route/0/weight\", \"value\": $v1},
        {\"op\": \"replace\", \"path\": \"/spec/http/0/route/1/weight\", \"value\": $v2}
    ]" 2>/dev/null || {
        if [ "$CREATE" = true ]; then
            echo "VirtualService $GW_VS_NAME 不存在,正在创建..."
            create_gateway_vs "$v1" "$v2"
        else
            echo "错误: 网关 VirtualService $GW_VS_NAME 不存在。请先创建或使用 --create 选项。"
            exit 1
        fi
    }
    echo "更新内部 VirtualService $INTERNAL_VS_NAME 为 v1=$v1, v2=$v2"
    kubectl patch vs $INTERNAL_VS_NAME -n $NAMESPACE --type='json' -p="[
        {\"op\": \"replace\", \"path\": \"/spec/http/0/route/0/weight\", \"value\": $v1},
        {\"op\": \"replace\", \"path\": \"/spec/http/0/route/1/weight\", \"value\": $v2}
    ]" 2>/dev/null || {
        if [ "$CREATE" = true ]; then
            echo "内部 VirtualService $INTERNAL_VS_NAME 不存在,正在创建..."
            create_internal_vs "$v1" "$v2"
        else
            echo "错误: 内部 VirtualService $INTERNAL_VS_NAME 不存在。请先创建或使用 --create 选项。"
            exit 1
        fi
    }
    echo "权重更新成功。"
}

# 创建网关 VirtualService 的模板
create_gateway_vs() {
    local v1=$1
    local v2=$2
    cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: $GW_VS_NAME
  namespace: $NAMESPACE
spec:
  hosts:
  - "*"
  gateways:
  - $GATEWAY_NAME
  http:
  - route:
    - destination:
        host: $SERVICE_FQDN
        subset: v1
      weight: $v1
    - destination:
        host: $SERVICE_FQDN
        subset: v2
      weight: $v2
EOF
}

create_internal_vs() {
    local v1=$1
    local v2=$2
    cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: $INTERNAL_VS_NAME
  namespace: $NAMESPACE
spec:
  hosts:
  - "$SERVICE_FQDN"
  gateways:
  - mesh
  http:
  - route:
    - destination:
        host: $SERVICE_FQDN
        subset: v1
      weight: $v1
    - destination:
        host: $SERVICE_FQDN
        subset: v2
      weight: $v2
EOF
}

# 主流程:执行更新
update_vs $V1_WEIGHT $V2_WEIGHT

# 验证 DestinationRule 是否存在
if ! kubectl get dr $DR_NAME -n $NAMESPACE &>/dev/null; then
    echo "警告: DestinationRule $DR_NAME 不存在,请确保已定义 v1 和 v2 子集。"
else
    echo "DestinationRule 检查通过。"
fi

# 可选:显示更新后的权重
echo "当前权重:"
kubectl get vs $GW_VS_NAME -n $NAMESPACE -o jsonpath='{.spec.http[0].route}' | jq .

EOF

2、脚本执行情况

bash 复制代码
./adjust-traffic.sh --create 90 10
更新网关 VirtualService demo-service-mirror-gateway 为 v1=90, v2=10
VirtualService demo-service-mirror-gateway 不存在,正在创建...
virtualservice.networking.istio.io/demo-service-mirror-gateway configured
更新内部 VirtualService demo-service-mirror-internal 为 v1=90, v2=10
内部 VirtualService demo-service-mirror-internal 不存在,正在创建...
virtualservice.networking.istio.io/demo-service-mirror-internal configured
权重更新成功。
DestinationRule 检查通过。
当前权重:
[
  {
    "destination": {
      "host": "demo-service.default.svc.cluster.local",
      "subset": "v1"
    },
    "weight": 90
  },
  {
    "destination": {
      "host": "demo-service.default.svc.cluster.local",
      "subset": "v2"
    },
    "weight": 10
  }
]
[root@mster isito]# kubectl get vs 
NAME                           GATEWAYS           HOSTS                                        AGE
demo-service-mirror-gateway    ["demo-gateway"]   ["*"]                                        99m
demo-service-mirror-internal   ["mesh"]           ["demo-service.default.svc.cluster.local"]   99m
[root@mster isito]# kubectl get vs -o yaml 
apiVersion: v1
items:
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.istio.io/v1beta1","kind":"VirtualService","metadata":{"annotations":{},"name":"demo-service-mirror-gateway","namespace":"default"},"spec":{"gateways":["demo-gateway"],"hosts":["*"],"http":[{"route":[{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v1"},"weight":90},{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v2"},"weight":10}]}]}}
    creationTimestamp: "2026-02-14T04:38:04Z"
    generation: 3
    name: demo-service-mirror-gateway
    namespace: default
    resourceVersion: "120217"
    uid: ca642410-f291-4ba3-ad7b-86a9db409e1a
  spec:
    gateways:
    - demo-gateway
    hosts:
    - '*'
    http:
    - route:
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v1
        weight: 90
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v2
        weight: 10
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.istio.io/v1beta1","kind":"VirtualService","metadata":{"annotations":{},"name":"demo-service-mirror-internal","namespace":"default"},"spec":{"gateways":["mesh"],"hosts":["demo-service.default.svc.cluster.local"],"http":[{"route":[{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v1"},"weight":90},{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v2"},"weight":10}]}]}}
    creationTimestamp: "2026-02-14T04:37:49Z"
    generation: 3
    name: demo-service-mirror-internal
    namespace: default
    resourceVersion: "120219"
    uid: 37e9474c-5128-4eef-9e57-f3e24db95cc4
  spec:
    gateways:
    - mesh
    hosts:
    - demo-service.default.svc.cluster.local
    http:
    - route:
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v1
        weight: 90
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v2
        weight: 10
kind: List
metadata:
  resourceVersion: ""
[root@mster isito]# ./adjust-traffic.sh --create 50 50 
更新网关 VirtualService demo-service-mirror-gateway 为 v1=50, v2=50
virtualservice.networking.istio.io/demo-service-mirror-gateway patched
更新内部 VirtualService demo-service-mirror-internal 为 v1=50, v2=50
virtualservice.networking.istio.io/demo-service-mirror-internal patched
权重更新成功。
DestinationRule 检查通过。
当前权重:
[
  {
    "destination": {
      "host": "demo-service.default.svc.cluster.local",
      "subset": "v1"
    },
    "weight": 50
  },
  {
    "destination": {
      "host": "demo-service.default.svc.cluster.local",
      "subset": "v2"
    },
    "weight": 50
  }
]
[root@mster isito]# kubectl get vs -o yaml 
apiVersion: v1
items:
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.istio.io/v1beta1","kind":"VirtualService","metadata":{"annotations":{},"name":"demo-service-mirror-gateway","namespace":"default"},"spec":{"gateways":["demo-gateway"],"hosts":["*"],"http":[{"route":[{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v1"},"weight":90},{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v2"},"weight":10}]}]}}
    creationTimestamp: "2026-02-14T04:38:04Z"
    generation: 4
    name: demo-service-mirror-gateway
    namespace: default
    resourceVersion: "120709"
    uid: ca642410-f291-4ba3-ad7b-86a9db409e1a
  spec:
    gateways:
    - demo-gateway
    hosts:
    - '*'
    http:
    - route:
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v1
        weight: 50
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v2
        weight: 50
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"networking.istio.io/v1beta1","kind":"VirtualService","metadata":{"annotations":{},"name":"demo-service-mirror-internal","namespace":"default"},"spec":{"gateways":["mesh"],"hosts":["demo-service.default.svc.cluster.local"],"http":[{"route":[{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v1"},"weight":90},{"destination":{"host":"demo-service.default.svc.cluster.local","subset":"v2"},"weight":10}]}]}}
    creationTimestamp: "2026-02-14T04:37:49Z"
    generation: 4
    name: demo-service-mirror-internal
    namespace: default
    resourceVersion: "120710"
    uid: 37e9474c-5128-4eef-9e57-f3e24db95cc4
  spec:
    gateways:
    - mesh
    hosts:
    - demo-service.default.svc.cluster.local
    http:
    - route:
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v1
        weight: 50
      - destination:
          host: demo-service.default.svc.cluster.local
          subset: v2
        weight: 50
kind: List
metadata:
  resourceVersion: ""

3、测试情况

  • pod内部测试
bash 复制代码
[root@mster isito]# SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}')
[root@mster isito]# kubectl exec $SLEEP_POD -- sh -c 'i=1; while [ $i -le 100 ]; do curl -s http://demo-service.default.svc.cluster.local; i=$((i+1)); done' | grep -o "v[12]" | sort | uniq -c
     44 v1
     56 v2
  • 外部主机测试
bash 复制代码
[root@node ~]# export GATEWAY_URL=http://192.168.1.100:30881
[root@node ~]# for i in {1..100}; do curl -s $GATEWAY_URL; done | grep -o "v[12]" | sort | uniq -c
     87 v1
     13 v2
[root@node ~]# for i in {1..100}; do curl -s $GATEWAY_URL; done | grep -o "v[12]" | sort | uniq -c
     46 v1
     54 v2

五、基于Istio的故障注入

故障注入是一种主动向系统中引入故障(如延迟、错误响应)以测试系统弹性和容错能力的测试手段。它并不是一种发布流程(如灰度或蓝绿),而是混沌工程的核心实践,旨在验证系统在面对真实故障时的表现。

🎯 典型使用场景

  1. 验证超时与重试策略
    假设你的服务设置了超时2秒、重试2次。通过注入3秒延迟,可以验证:

请求是否在2秒后超时?

超时后是否触发重试?

重试是否超过了总次数限制?

这能确保配置的合理性,避免因超时设置不当导致连锁故障。

  1. 测试熔断与降级机制
    当你注入高比例的故障(如50%的503错误),可以验证:

服务熔断器是否会在错误率达到阈值时打开?

降级逻辑(如返回缓存数据或空结果)是否被正确触发?

熔断半开后恢复试探是否正常?

  1. 模拟依赖服务故障
    微服务架构中,一个服务的故障可能传播到上游。通过向下游服务注入故障,可以测试:

上游服务的容错逻辑(如快速失败、回退)。

全链路的监控告警是否能准确捕捉到故障源头。

  1. 验证监控与告警系统

    故障注入可以主动触发错误,验证监控系统是否能够准确记录、告警规则是否合理,确保在生产真的出问题时,团队能第一时间感知。

  2. 混沌工程实验

    在生产或类生产环境中,定期进行故障注入演练,提前发现系统弱点,提升整体韧性。例如:

"五一"大促前,模拟下游支付服务延迟,验证订单系统的处理能力。

随机注入实例故障,验证负载均衡是否重新分发流量。

📌 为什么它不属于"发布流程"?

与灰度发布、蓝绿发布等不同,故障注入的目的不是发布新功能,而是验证现有系统在异常情况下的行为。它通常发生在:

系统上线前:作为压力测试的一部分。

配置变更后:验证新设置(如超时、重试)是否生效。

定期演练:持续保证系统弹性。

1、内部网关

bash 复制代码
cat << EOF vs-fault-internal.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-fault-internal
  namespace: default
spec:
  hosts:
  - "demo-service.default.svc.cluster.local"   # 必须为具体FQDN
  gateways:
  - mesh
  http:
  - fault:
      delay:
        percentage:
          value: 50.0
        fixedDelay: 3s
      abort:
        percentage:
          value: 10.0
        httpStatus: 503
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1
      weight: 100

EOF 

2、外部网关

bash 复制代码
cat  << EOF vs-fault-gateway.yaml 
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demo-service-fault-gateway
  namespace: default
spec:
  hosts:
  - "*"                     # 外部网关允许通配符
  gateways:
  - demo-gateway
  http:
  - fault:
      # 延迟注入:对50%的请求注入3秒延迟
      delay:
        percentage:
          value: 50.0
        fixedDelay: 3s
      # 中断注入:对10%的请求返回503错误
      abort:
        percentage:
          value: 10.0
        httpStatus: 503
    route:
    - destination:
        host: demo-service.default.svc.cluster.local
        subset: v1           # 所有流量仍去v1(故障注入发生在路由前)
      weight: 100

EOF

3 、应用资源

bash 复制代码
删除之前的vs
[root@mster isito]# kubectl get vs 
NAME                          GATEWAYS           HOSTS                                        AGE
demo-service-fault-gateway    ["demo-gateway"]   ["*"]                                        8m11s
demo-service-fault-internal   ["mesh"]           ["demo-service.default.svc.cluster.local"]   8m20s

4、 验证测试

1、内部pod 测试

bash 复制代码
SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}')
 for i in {1..20}; do   kubectl exec $SLEEP_POD -- time curl -s -o /dev/null -w "请求 $i: HTTP %{http_code}, 耗时 %{time_total}s\n" http://demo-service.default.svc.cluster.local; done

2、外部主机测试

bash 复制代码
 export GATEWAY_URL=http://192.168.1.100:30881
 for i in {1..20}; do   time curl -s -o /dev/null -w "请求 $i: HTTP %{http_code}, 耗时 %{time_total}s\n" $GATEWAY_URL; done

六、大成之作

1、架构图

混沌工程
发布流水线
金丝雀
蓝绿
A/B测试
代码提交
镜像构建
镜像发布验证
发布策略选择
逐步放量
瞬时切换
按条件分流
全量发布
故障注入实验
系统韧性验证
生产稳定运行
外部客户端
curl -H 'Host: gray.example.com'
curl -H 'Host: canary.example.com'
curl -H 'Host: bluegreen.example.com'
curl -H 'Host: mirror.example.com'
curl -H 'Host: fault.example.com'
Ingress Gateway

istio-ingressgateway
VirtualService gray
VirtualService canary
VirtualService bluegreen
VirtualService mirror
VirtualService fault
gray命名空间

nginx服务
canary命名空间

tomcat服务
bluegreen命名空间

alpine服务
mirror命名空间

nginx-alpine服务
fault命名空间

nginx-slim服务

2、通用网关配置(已存在)

bash 复制代码
cat  << EOF gateway.yaml  
#apiVersion: networking.istio.io/v1
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: demo-gateway
spec:
  selector:
    istio: ingressgateway # 使用已安装的入口网关
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*" # 允许所有主机访问,生产环境请替换为你的域名,如 demo.example.com

EOF 

2、 灰度发布(gray 命名空间)

bash 复制代码
kubectl create namespace gray
kubectl label namespace gray istio-injection=enabled  ## 允许自动注入

kubectl get namespace gray -L istio-injection  ## 查看标签情况,默认为空
相关推荐
2501_924878732 小时前
中小电商自建直播间成本降47%:AI口播智能体轻量部署(含Docker+OBS链路)
人工智能·docker·容器·排序算法·宽度优先
uNhPiLgvQDpJ2 小时前
基于STM32的实验室环境检测系统:温湿度、烟雾、空气质量检测和报警功能
kubernetes
taiguisheng2 小时前
Windows系统中安装WSL2与Docker-Desktop
windows·docker·容器
西门吹雪分身3 小时前
K8S之Pod生命周期
java·kubernetes·k8s
岳来3 小时前
网络小白对容器参数endpoint 和gateway 对比
网络·docker·容器·gateway·endpoint
市安3 小时前
基于Centos构建Nginx镜像(Dokerfile)
linux·运维·nginx·docker·容器·centos·镜像
invicinble3 小时前
梳理docker的提供机制
运维·docker·容器
礼拜天没时间.6 小时前
Docker 部署分布式 Hadoop(超详细实战版)
linux·hadoop·分布式·docker·容器
DeeplyMind13 小时前
第1章 Docker入门:容器化技术简介
docker·容器·eureka