istio灰度发布部署演示文档

概述

​ 本文档基于kubernetes v1.32.8 + istio-1.28.0 实现部署和服务网格的灰度发布验证。

​ Istio 作为 Kubernetes 生态中的服务网格(Service Mesh),核心能力之一是无需修改业务代码即可实现灵活、精细的灰度发布(又称金丝雀发布 / Canary Release)。其本质是通过拦截服务间的网络流量,按照预设规则(如权重、用户标签、请求参数等)将流量动态分配到不同版本的服务实例,实现风险可控的版本迭代。

  1. 核心原理:

    • 服务版本隔离:通过 DestinationRule 为目标服务定义多个 Subset(如 vtestion: v1vtestion: v2),每个 Subset 对应一个版本的服务实例(Pod 需通过标签区分版本,如 app: test-app, vtestion: v1
    • 流量路由规则:通过 VirtualService 定义流量分配策略(如权重、匹配条件),将符合规则的流量路由到指定 Subset
    • 动态生效:Istiod 监听 VirtualServiceDestinationRule 的变更,实时将配置推送到所有相关 Envoy 代理,无需重启服务即可生效
  2. 关键组件:

    • Envoy: 数据平面代理(Sidecar 注入到每个 Pod),拦截进出流量,执行路由规则
    • Istiod: 控制平面,负责将路由规则、服务发现信息转换为 Envoy 可识别的配置并推送
    • VirtualService: Istio 核心路由配置资源,定义 “流量如何分配到不同服务版本”
    • DestinationRule: 定义服务的子集(Subset),将不同版本的服务实例分组(如 v1、v2)
    • Service: Kubernetes 原生 Service,负责服务实例的注册与健康检查(Istio 依赖其获取服务端点)
    • Gateway: 集群边缘流量入口,接管外部请求并接入 Istio 网格,是外部流量灰度的前提
  3. 常见灰度发布策略

    • 权重路由(最常用): 按比例分配流量到不同版本,适用于逐步放量验证(如: 将 10% 流量导向 v2 版本,90% 流量保留在 v1 版本)
    • 基于请求匹配的路由(精准测试): 根据请求的特定属性(如 Header、参数、Cookie)定向路由,适用于定向测试(如内部员工、特定用户群体)

1. 获取软件和镜像

1.1 下载 istio 软件

# 在可访问公网的服务器下载 istio 
curl -L https://istio.io/downloadIstio | ISTIO_VtestION=1.28.0 TARGET_ARCH=x86_64 sh -

# 或者直接下面的复制到浏览器下载
https://github.com/istio/istio/releases/download/1.28.0/istio-1.28.0-linux-amd64.tar.gz

1.2 下载 istio 需要的镜像

# 1. 在能访问公网的docker上下载istio 镜像,上传到私有harbor仓库
docker pull docker.io/istio/install-cni:1.28.0
docker pull docker.io/istio/proxyv2:1.28.0
docker pull docker.io/istio/pilot:1.28.0
  
# 2. 给镜像打 tag   
docker tag docker.io/istio/install-cni:1.28.0   192.168.133.123/repo/istio/install-cni:1.28.0
docker tag docker.io/istio/proxyv2:1.28.0   192.168.133.123/repo/istio/proxyv2:1.28.0
docker tag docker.io/istio/pilot:1.28.0   192.168.133.123/repo/istio/pilot:1.28.0

# 3. 推送镜像到私有仓库
docker push 192.168.133.123/repo/istio/install-cni:1.28.0
docker push 192.168.133.123/repo/istio/proxyv2:1.28.0
docker push 192.168.133.123/repo/istio/pilot:1.28.0

# 4. 下载 可观测性组件镜像,上传到私有harbor仓库 (可选)
docker pull docker.io/grafana/grafana:12.0.1
docker pull quay.io/kiali/kiali:v2.17
docker pull ghcr.io/prometheus-operator/prometheus-config-reloader:v0.85.0
docker pull prom/prometheus:v3.5.0

docker tag docker.io/grafana/grafana:12.0.1  192.168.133.123/repo/istio/grafana:12.0.1
docker tag quay.io/kiali/kiali:v2.17  192.168.133.123/repo/istio/kiali:v2.17
docker tag ghcr.io/prometheus-operator/prometheus-config-reloader:v0.85.0  192.168.133.123/repo/istio/prometheus-config-reloader:v0.85.0
docker tag prom/prometheus:v3.5.0   192.168.133.123/repo/istio/prometheus:v3.5.0 

docker push 192.168.133.123/repo/istio/grafana:12.0.1
docker push 192.168.133.123/repo/istio/kiali:v2.17
docker push 192.168.133.123/repo/istio/prometheus-config-reloader:v0.85.0
docker push 192.168.133.123/repo/istio/prometheus:v3.5.0

2. 部署 istio

# 在master01 
# 解压软件包,并复制 执行命令到 /usr/local/bin/ 目录
tar -xzf istio-1.28.0-linux-amd64.tar.gz
cd istio-1.28.0
cp bin/istioctl /usr/local/bin/

# 使用私有harbor仓库镜像安装 Istio
istioctl install --set hub=192.168.133.123/repo/istio --set tag=1.28.0 --set components.cni.enabled=true --set components.egressGateways[0].enabled=true --set components.egressGateways[0].name=istio-egressgateway

# 安装参数说明
--set hub=192.168.133.123/repo/istio -> 表示对应 Istio 镜像的仓库地址
--set tag=1.28.0 -> 表示tag 对应镜像的版本标签,指定安装 Istio 1.28.0 版本
--set components.cni.enabled=true -> 表示启用 Istio CNI 组件,提升集群安全性、简化网络配置、避免权限相关的部署报错
--set components.egressGateways[0].enabled=true -> 启用 Istio 出口网关 ,当弃用该参数,需要指定以下参数
--set components.egressGateways[0].name=istio-egressgateway   和上一行参数是一起使用的
# 在master01 
# 以下是部署过程(示例参考)
[root@kube istio-1.28.0]#  cd /root/istio-1.28.0
[root@kube istio-1.28.0]# pwd
/root/istio-1.28.0
[root@kube istio-1.28.0]# ls
bin  LICENSE  manifests  manifest.yaml  README.md  samples  tools
[root@kube istio-1.28.0]#
[root@kube istio-1.28.0]# istioctl install \
>   --set hub=192.168.133.123/repo/istio \
>   --set tag=1.28.0 \
>   --set components.cni.enabled=true \
>   --set components.egressGateways[0].enabled=true \
>   --set components.egressGateways[0].name=istio-egressgateway
        |\
        | \
        |  \
        |   \
      /||    \
     / ||     \
    /  ||      \
   /   ||       \
  /    ||        \
 /     ||         \
/______||__________\
____________________
  \__       _____/
     \_____/

This will install the Istio 1.28.0 profile "default" into the cluster. Proceed? (y/N) y
✔ Istio core installed ⛵️
✔ CNI installed 🪢
✔ Istiod installed 🧠
✔ Egress gateways installed 🛫
✔ Ingress gateways installed 🛬
✔ Installation complete
[root@kube istio-1.28.0]#
# 在master01 
# 出现以下结果说明 istio 已安装完成,可以使用了。
[root@kube istio-1.28.0]# 
[root@kube istio-1.28.0]# kubectl -n istio-system get po
NAME                                    READY   STATUS    RESTARTS   AGE
istio-cni-node-zhqgm                    1/1     Running   0          64s
istio-egressgateway-77dcfdbfbc-4zqlv    1/1     Running   0          62s
istio-ingressgateway-5857bb6896-8bb27   1/1     Running   0          62s
istiod-d7b699bb9-mpp7t                  1/1     Running   0          64s
[root@kube istio-1.28.0]#
[root@kube istio-1.28.0]# kubectl -n istio-system get svc
NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE
istio-egressgateway           ClusterIP      10.233.57.219   <none>        80/TCP,443/TCP                               64s
istio-ingressgateway          LoadBalancer   10.233.58.242   <pending>     15021:31219/TCP,80:30376/TCP,443:31543/TCP   62s
istiod                        ClusterIP      10.233.61.148   <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP        62s
istiod-revision-tag-default   ClusterIP      10.233.54.38    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP        64s
[root@kube istio-1.28.0]#

# # 在master01 ,卸载命令,
# istioctl uninstall --purge

3. 部署 istio 可观测平台(可选)

​ istio 可观测平台,主要是由grafana、prometheus以及kiali组成,通过kiali的web控制端可以比较方便的查看流量流转拓扑。

# 在master01 
# 1. 替换 grafana.yaml 和 kiali.yaml 以及 prometheus.yaml 的镜像为私有镜像
cd /root/istio-1.28.0/samples/addons

[root@kube addons]# pwd
/root/istio-1.28.0/samples/addons
[root@kube addons]# ls
extras  grafana.yaml  jaeger.yaml  kiali.yaml  loki.yaml  prometheus.yaml  README.md
[root@kube addons]#  
[root@kube addons]# sed -i 's@docker.io/grafana/grafana:12.0.1@192.168.133.123/repo/istio/grafana:12.0.1@g' grafana.yaml
[root@kube addons]# sed -i 's@quay.io/kiali/kiali:v2.17@192.168.133.123/repo/istio/kiali:v2.17@g'  kiali.yaml
[root@kube addons]# sed -i 's@prom/prometheus:v3.5.0@192.168.133.123/repo/istio/prometheus:v3.5.0@g'  prometheus.yaml
[root@kube addons]# sed -i 's@ghcr.io/prometheus-operator/prometheus-config-reloader:v0.85.0@192.168.133.123/repo/istio/prometheus-config-reloader:v0.85.0@g' prometheus.yaml
[root@kube addons]#
# 在master01 
# 2. 运行 istio 可观察性应用 
cd /root/istio-1.28.0/samples/addons

kubectl apply -f grafana.yaml -f kiali.yaml -f prometheus.yaml
# 在master01 
[root@kube addons]# # 查看 istio 的 pod 运行状况(以下是正常运行的pod示例参考) 
[root@kube addons]# kubectl -n istio-system get po
NAME                                    READY   STATUS    RESTARTS   AGE
grafana-7c7f97cdd8-7kf7k                1/1     Running   0          3m58s
istio-cni-node-zhqgm                    1/1     Running   0          92m
istio-egressgateway-77dcfdbfbc-4zqlv    1/1     Running   0          92m
istio-ingressgateway-5857bb6896-8bb27   1/1     Running   0          92m
istiod-d7b699bb9-mpp7t                  1/1     Running   0          92m
kiali-8774dbdf7-5pvgw                   1/1     Running   0          3m58s
prometheus-5d655c5778-j2q2l             2/2     Running   0          3m58s
[root@kube addons]#

# 在master01 
# 3.1. 创建 kiali 用于外部访问的的服务,使用户可登录 kiali web 控制台。
# 以下文件 有两处可以修改,其他参数不要修改

cd /root/istio-1.28.0/samples/addons

# cat kiali-nodeport.yaml
---
apiVtestion: v1
kind: Service
metadata:
  labels:
    app: kiali
    app.kubernetes.io/instance: kiali
    app.kubernetes.io/name: kiali
  name: kiali-nodeport  # 替换成自定义kiali 的 名字
  namespace: istio-system
spec:
  ports:
  - appProtocol: http
    name: http
    nodePort: 30706  # 自定义kiali 的 nodeport
    port: 20001
    protocol: TCP
    targetPort: 20001
  selector:
    app.kubernetes.io/instance: kiali
    app.kubernetes.io/name: kiali
  type: NodePort
status:
  loadBalancer: {}
---
# 在master01 
# 3.2. 获取登录 kiali web 控制台地址,访问地址: 节点IP:30706(如: 192.168.133.100:30706)
kubectl apply -f kiali-nodeport.yaml

[root@kube addons]#
[root@kube addons]# kubectl -n istio-system get svc|grep kiali-nodeport
kiali-nodeport                NodePort       10.233.21.93    <none>        20001:30706/TCP                              10m
[root@kube addons]#

4. 灰度发布(基于请求头及URI匹配)

# 在master01 
# 创建测试用的命名空间: demo
kubectl create ns demo

# 为目标命名空间demo启用 Istio 自动注入(仅需执行一次)
kubectl label namespace demo istio-injection=enabled --overwrite

4.1 创建测试应用

# 在master01 , 在/root/huidu目录 创建用于验证灰度的发布的文件
mkdir -p /root/huidu &&  cd /root/huidu
---
#  cat test-app-v1.yaml
apiVtestion: apps/v1
kind: Deployment
metadata:
  name: test-app-v1  # v1 版本
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app  # 两个版本应用的同名标签
      vtestion: v1   # v1 版本 特有的标签
  template:
    metadata:
      labels:
        app: test-app  # 两个版本应用的同名标签
        vtestion: v1  # v1 版本 特有的标签
    spec:
      containtest:
      - name: test-app
        image: 192.168.133.123/repo/nginx  # 用 nginx 简化测试,实际可替换为你的应用
        ports:
        - containerPort: 80
        # 自定义响应内容,支持 /test 路径访问,明确区分版本
        command:
        - sh
        - -c
        - |
          # 创建 /test 目录并写入版本内容
          mkdir -p /usr/share/nginx/html/test
          echo 'test-app-v1' > /usr/share/nginx/html/test/index.html
          # 覆盖 Nginx 默认配置,支持 /test 路径访问
          cat > /etc/nginx/conf.d/default.conf << EOF
          server {
              listen 80;
              server_name localhost;

              # 根路径重定向到 /test(可选,方便测试)
              location / {
                  return 301 /test/;
              }

              # 处理 /test 路径的请求
              location /test/ {
                  root /usr/share/nginx/html;
                  index index.html;
              }
          }
          EOF
          # 启动 Nginx 前台运行
          nginx -g 'daemon off;'

---

#  cat test-app-v2.yaml
apiVtestion: apps/v1
kind: Deployment
metadata:
  name: test-app-v2  # v2 版本
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app  # 两个版本应用的同名标签
      vtestion: v2    # v2 版本 特有的标签
  template:
    metadata:
      labels:
        app: test-app  # 两个版本应用的同名标签
        vtestion: v2   # v2 版本 特有的标签
    spec:
      containtest:
      - name: test-app
        image: 192.168.133.123/repo/nginx
        ports:
        - containerPort: 80
        # 自定义响应内容,支持 /test 路径访问,明确区分版本
        command:
        - sh
        - -c
        - |
          # 创建 /test 目录并写入版本内容
          mkdir -p /usr/share/nginx/html/test
          echo 'test-app-v2 (canary)' > /usr/share/nginx/html/test/index.html
          # 覆盖 Nginx 默认配置,支持 /test 路径访问
          cat > /etc/nginx/conf.d/default.conf << EOF
          server {
              listen 80;
              server_name localhost;

              # 根路径重定向到 /test(可选,方便测试)
              location / {
                  return 301 /test/;
              }

              # 处理 /test 路径的请求
              location /test/ {
                  root /usr/share/nginx/html;
                  index index.html;
              }
          }
          EOF
          # 启动 Nginx 前台运行
          nginx -g 'daemon off;'
---

# cat test-app-svc.yaml
apiVtestion: v1
kind: Service
metadata:
  name: test-app
  namespace: demo
spec:
  selector:
    app: test-app  # 匹配两个版本的 Pod
  ports:
  - port: 80
    targetPort: 80
    name: tcp-80
  type: ClusterIP
---

# 运行 v1 、v2 版本的应用以及 他们共同的 svc
kubectl apply -f test-app-v1.yaml -f test-app-v2.yaml -f test-app-svc.yaml

# test-app-v1 和 test-app-v2 的POD内部访问地址是: http://127.0.0.1/test/ (以下是示例参考,请按照实际pod为准)

[root@kube huidu]#
[root@kube huidu]# kubectl -n demo get po  -owide|grep test-app
test-app-v1-788c57499-v7qgd    2/2     Running   0               34m     10.233.106.75   kube   <none>           <none>
test-app-v2-5f6d998b6d-q58j6   2/2     Running   0               34m     10.233.106.77   kube   <none>           <none>
[root@kube huidu]#
[root@kube huidu]# kubectl -n demo exec -it test-app-v1-788c57499-v7qgd  -- sh
# curl http://127.0.0.1/test/
test-app-v1
# exit
[root@kube huidu]#
[root@kube huidu]# kubectl -n demo exec -it test-app-v2-5f6d998b6d-q58j6  -- sh
#  curl http://127.0.0.1/test/
test-app-v2 (canary)
# exit
[root@kube huidu]#

# 备注: 万一 test-app-v1 和 test-app-v2 的容器数量和上面的结果不一样(不是 2/2 ),需要执行以下命令重新注入sidecar容器

# 为目标命名空间demo启用 Istio 自动注入(仅需执行一次)
kubectl label namespace demo istio-injection=enabled --overwrite

# 重启 test-app-v1 和 test-app-v2 生效
kubectl -n demo rollout restart  deploy test-app-v1 test-app-v2

4.2 配置 灰度规则

​ 核心配置:通过 VirtualService 定义流量规则,根据 X-User 请求头分发流量和uri前缀实现灰度发布。

---
# cat test-app-dr.yaml
# 1. DestinationRule(服务子集定义)
apiVtestion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: test-app-dr
  namespace: demo
spec:
  host: test-app  # 填写 test-app service 名
  subsets:
  - name: v1  # 自定义子集名字
    labels:
      vtestion: v1  # 版本v1
  - name: v2  # 自定义子集名字
    labels:
      vtestion: v2  # 版本v2
---

# cat test-app-vs.yaml
# 2. VirtualService(核心路由:适配 Service 端口 80)
apiVtestion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: test-app-vs
  namespace: demo
spec:
  hosts:
  - "test.app.com"  # 访问域名
  gateways:
  - test-app-gateway  # gw 的名字
  http:
  - match:
    - headtest:
        X-User:
          exact: "test"  #
      uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v2 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
  - match:
    - uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v1      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 100     # 权重是 100,表示当请求头不匹配的时候,默认全部流量用于访问 v1 版本
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 0       #  待 基于 v2 的请求头验证正常后,将 该(v2)权重设置为100, v1 版本权重设置为 0,就可以实现流量切换。 
---

# cat test-app-gw.yaml
# 3. Gateway(适配 IngressGateway Pod 端口 80)
apiVtestion: networking.istio.io/v1
kind: Gateway
metadata:
  name: test-app-gateway # 自定义名称,与vs应用的保持一致
  namespace: demo
spec:
  selector:
    istio: ingressgateway  # 填写istio-ingressgateway应用的标签
  servtest:
  - port:
      number: 80   # 网关对外暴露的端口(外部访问用 80,和 Service 端口一致)
      name: http
      protocol: HTTP
    hosts:
    - "test.app.com"  # 允许你的域名访问
---
# 4. 生效灰度规则
[root@kube huidu]#
[root@kube huidu]# kubectl apply -f test-app-dr.yaml  -f test-app-vs.yaml -f test-app-gw.yaml -n demo
destinationrule.networking.istio.io/test-app-dr created
virtualservice.networking.istio.io/test-app-vs created
gateway.networking.istio.io/test-app-gateway created
[root@kube huidu]#
[root@kube huidu]# kubectl -n demo get gw,vs,dr
NAME                                           AGE
gateway.networking.istio.io/test-app-gateway   5s

NAME                                             GATEWAYS               HOSTS                 AGE
virtualservice.networking.istio.io/test-app-vs   ["test-app-gateway"]   ["test.app.com"]   5s

NAME                                              HOST       AGE
destinationrule.networking.istio.io/test-app-dr   test-app   5s
[root@kube huidu]#

4.3 生效灰度规则

# 1. 确认 istio-ingressgateway 的 对外端口
[root@kube huidu]#
[root@kube huidu]# kubectl -n istio-system  get svc|grep ingress
istio-ingressgateway          LoadBalancer   10.233.58.242   <pending>     15021:31219/TCP,80:30376/TCP,443:31543/TCP   20h
[root@kube huidu]#

# 从上面的内容可知 istio-ingressgateway 的访问地址是: http://IP:30376
# 本地节点IP是 192.168.133.100, istio-ingressgateway 的访问地址则是: http://192.168.133.100:30376/

# 2. 配置 hosts解析
[root@kube huidu]#
[root@kube huidu]# cat  /etc/hosts |grep test.app.com
192.168.133.100   test.app.com  other.app.com
[root@kube huidu]#

4.4 灰度验证过程

# 1. 无请求头 → 访问 v1 版本(默认路由)
curl http://test.app.com:30376/test/
# 预期输出:test-app-v1

[root@kube huidu]#
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#

# 2. 带 X-User: test 请求头 → 访问 v2 版本(灰度路由)
 curl -H "X-User: test" http://test.app.com:30376/test/
# 预期输出:test-app-v2 (canary)

[root@kube huidu]#
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]#
# 3. 不带 X-User: test 请求头 → 访问(走默认路由) (如: X-User: test01 和 X-User01: test )
 curl -H "X-User: test01" http://test.app.com:30376/test/
# 预期输出:test-app-v1 (请求头不匹配,走默认路由)

[root@kube huidu]#
[root@kube huidu]#  curl -H "X-User: test01" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User: test01" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User: test01" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#

 curl -H "X-User01: test" http://test.app.com:30376/test/
# 预期输出:test-app-v1 (请求头不匹配,走默认路由)

[root@kube huidu]#
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:30376/test/
test-app-v1
[root@kube huidu]#

# 3. 其他域名访问 → 被 Gateway 拦截,无法访问到 test-app应用(验证域名拦截了)
curl http://other.app.com:30376/test/  # 替换为 other.app.com 的域名(未在灰度规则配置的域名)
# 预期输出:既不是test-app-v1,也不是test-app-v2 (canary)--无结果

[root@kube huidu]#
[root@kube huidu]# curl http://other.app.com:30376/test/
[root@kube huidu]# curl http://other.app.com:30376/test/
[root@kube huidu]# curl http://other.app.com:30376/test/
[root@kube huidu]#

4.5 流量切换

​ 当基于请求头+URI 验证 v2 版本正常后,将全部流量切换到 v2 版本。

​ 切换流量的时候只需要调整 test-app-vs.yaml 文件,将流量全部指向 v2 版本,配置如下:

# cat test-app-vs-v2.yaml
# 1. VirtualService(核心路由:所有流量全量走v2版本)
apiVtestion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: test-app-vs   # 保持原名称,可直接覆盖替换原有VS
  namespace: demo
spec:
  hosts:
  - "test.app.com"  # 访问域名
  gateways:
  - test-app-gateway  # 关联的Gateway名字
  http:
  # 仅保留核心规则:所有/test/前缀的请求全量走v2
  - match:
    - uri:
        prefix: "/test/"   # 匹配/test/前缀的所有请求
    route:
    - destination:
        host: test-app  # 目标Service名
        subset: v2      # 仅保留v2版本
        port:
          number: 80    # 目标Service的80端口
      weight: 100       # 全量流量走v2(可省略,单目标时weight默认100)
# 2、此时 test-app-vs-v2.yaml 和 下文的test-app-vs.yaml 是等效的。

# cat test-app-vs.yaml
# 2. VirtualService(核心路由:所有流量全量走v2版本)
apiVtestion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: test-app-vs
  namespace: demo
spec:
  hosts:
  - "test.app.com"  # 访问域名
  gateways:
  - test-app-gateway  # gw 的名字
  http:
  - match:
    - headtest:
        X-User:
          exact: "test"  #
      uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v2 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
  - match:
    - uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v1      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 0     # 权重是 100,表示当请求头不匹配的时候,默认全部流量用于访问 v1 版本
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 100       #  待 基于 v2 的请求头验证正常后,将 该(v2)权重设置为100, v1 版本权重设置为 0,就可以实现流量切换。
---

# 将流量全部切换到 v2 版本
kubectl -n demo apply -f test-app-vs-v2.yaml
# 此时访问 全部流量都转到 v2 版本了 ;此时访问域名 http://test.app.com:30376/test/  所有流量切换到了 v2 版本

[root@kube huidu]#
[root@kube huidu]# kubectl apply -f test-app-vs-v2.yaml
virtualservice.networking.istio.io/test-app-vs configured
[root@kube huidu]#
[root@kube huidu]#
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:30376/test/
test-app-v2 (canary)
[root@kube huidu]#

4.6 灰度发布基本流程概述

# 第 4 章 创建的验证文件都保存在master01 的 /root/huidu 目录,并且都在该目录操作验证过程
初始状态(所有流量走 v1) -> 部署 v2 版本 -> 携带 X-User:test 请求头验证 v2(普通流量仍走 v1) -> v2 验证通过 -> 全量切换(v1=0%、v2=100%) -> 移除 v1 路由规则,完成灰度发布

5. 灰度发布+ingress(基于请求头及URI) – 依赖第 4 章的配置

1. 基于已有的ingress-nginx,将ingress-nginx的流量转发给istio-ingressgateway
2. 完全基于第 4 章 灰度发布(基于请求头及URI)的全部配置,配置内容完全一致
3. 在 demo 命名空间创建 ExternalName 服务和 ingress
# 登录master01 , 在 /root/huidu 目录操作
cd /root/huidu

5.1 创建 ExternalName 服务

# 创建 ExternalName 服务,实现ingress跨命名空间访问 istio-ingressgateway 服务
# cat istio-ingressgateway-proxy.yaml

---
apiVtestion: v1
kind: Service
metadata:
  name: istio-ingressgateway-proxy # service 名
  namespace: demo
spec:
  # 指向 istio-system 命名空间的 istio-ingressgateway 服务
  externalName: istio-ingressgateway.istio-system.svc.cluster.local
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  sessionAffinity: None
  type: ExternalName
status:
  loadBalancer: {}
---
[root@kube huidu]#
[root@kube huidu]# kubectl apply -f istio-ingressgateway-proxy.yaml
service/istio-ingressgateway-proxy changed
[root@kube huidu]#
[root@kube huidu]# kubectl get  -f istio-ingressgateway-proxy.yaml
NAME                         TYPE           CLUSTER-IP   EXTERNAL-IP                                           PORT(S)   AGE
istio-ingressgateway-proxy   ExternalName   <none>       istio-ingressgateway.istio-system.svc.cluster.local   80/TCP    85s
[root@kube huidu]#

5.2 创建 ingress 服务

# cat test-app-ing.yaml

---
apiVtestion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-app
  namespace: demo
spec:
  ingressClassName: nginx
  rules:
  - host: test.app.com
    http:
      paths:
      - backend:
          service:
            name: istio-ingressgateway-proxy # 指向 ExternalName 类型服务
            port:
              number: 80
        path: /test/
        pathType: Prefix
status:
  loadBalancer: {}
---

[root@kube huidu]#
[root@kube huidu]# kubectl create -f test-app-ing.yaml
ingress.networking.k8s.io/test-app created
[root@kube huidu]#
[root@kube huidu]# kubectl get  -f test-app-ing.yaml
NAME       CLASS   HOSTS             ADDRESS   PORTS   AGE
test-app   nginx   test.app.com             80      7s
[root@kube huidu]#

5.3 基于 ingress-nginx访问验证灰度发布

# 1. 确认 ingress-nginx 入口信息和 ingress 规则域名信息 (nodeport是: 32455; 域名是: test.app.com)

[root@kube huidu]#
[root@kube huidu]# cat /etc/hosts |grep demo
192.168.133.100   test.app.com  other.app.com
[root@kube huidu]#
[root@kube huidu]# kubectl -n ingress-nginx get svc
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.233.32.231   <pending>     80:32455/TCP,443:31420/TCP   119m
ingress-nginx-controller-admission   ClusterIP      10.233.17.90    <none>        443/TCP                      119m
[root@kube huidu]#
[root@kube huidu]# kubectl -n demo get ingress
NAME       CLASS   HOSTS             ADDRESS   PORTS   AGE
test-app   nginx   test.app.com             80      3m10s
[root@kube huidu]#
# 2. 无请求头 → 访问 v1 版本(默认路由)
curl http://test.app.com:32455/test/
# 预期输出:test-app-v1

[root@kube huidu]#
[root@kube huidu]# curl http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]#
# 3. 带 X-User: test 请求头 → 访问 v2 版本(灰度路由)
 curl -H "X-User: test" http://test.app.com:32455/test/
# 预期输出:test-app-v2 (canary)

[root@kube huidu]#
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:32455/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:32455/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com:32455/test/
[root@kube huidu]#

 curl -H "X-User01: test" http://test.app.com:32455/test/
# 预期输出:test-app-v1 (请求头不匹配,走默认路由)

[root@kube huidu]#
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com:32455/test/
test-app-v1
[root@kube huidu]#

5. 灰度发布+nginx (基于请求头及URI) – 依赖第 4 章的配置

1. 直接使用外部的 nginx 七层反向代理,将流量转发给 istio-ingressgateway
1. 完全基于第 4 章 灰度发布(基于请求头及URI)的全部配置,配置内容完全一致
# 登录 nginx 服务器,并配置 nginx 服务器 域名解析(验证环境,由于nginx 服务器和master01 是同一台服务器,域名解析如下)

#  配置nginx服务器 hosts解析
[root@kube huidu]#
[root@kube huidu]# cat  /etc/hosts |grep test.app.com
192.168.133.100   test.app.com  other.app.com
[root@kube huidu]#

5.1 创建 nginx 反向代理(七层)

# cat /usr/local/nginx/conf/nginx.conf

events {}
http {
    # istio-ingressgateway 的入口
    upstream ingressgateway {
        server 192.168.133.100:30376 max_fails=2 fail_timeout=10s;
    }

    # test.app.com 转发规则(核心)
    server {
        listen 80;
        server_name test.app.com;

        location / {
            proxy_pass http://ingressgateway;
            proxy_http_vtestion 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Cookie $http_cookie;
        }
    }

    # other.app.com 转发规则(核心)
    server {
        listen 80;
        server_name other.app.com;

        location / {
            proxy_pass http://ingressgateway;
            proxy_http_vtestion 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Cookie $http_cookie;
        }
    }
}

[root@kube conf]#
[root@kube conf]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.20.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.20.2/conf/nginx.conf test is successful
[root@kube conf]#
[root@kube conf]# /usr/local/nginx/sbin/nginx
[root@kube conf]#
[root@kube conf]# ps -ef |grep nginx|head -n 2
root     14471 14328  0 00:12 ?        00:00:00 nginx: master process nginx -g daemon off;
101      14483 14471  0 00:12 ?        00:00:00 nginx: worker process
[root@kube conf]#

5.2 基于 外部 nginx 访问验证灰度发布(无ingress-nginx)

# 1. 无请求头 → 访问 v1 版本(默认路由)
curl http://test.app.com/test/
# 预期输出:test-app-v1

[root@kube huidu]#
[root@kube huidu]# curl http://test.app.com/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com/test/
test-app-v1
[root@kube huidu]# curl http://test.app.com/test/
[root@kube huidu]#
# 2. 带 X-User: test 请求头 → 访问 v2 版本(灰度路由)
 curl -H "X-User: test" http://test.app.com/test/
# 预期输出:test-app-v2 (canary)

[root@kube huidu]#
[root@kube huidu]# curl -H "X-User: test" http://test.app.com/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com/test/
test-app-v2 (canary)
[root@kube huidu]# curl -H "X-User: test" http://test.app.com/test/
[root@kube huidu]#

 curl -H "X-User01: test" http://test.app.com/test/
# 预期输出:test-app-v1 (请求头不匹配,走默认路由)

[root@kube huidu]#
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com/test/
test-app-v1
[root@kube huidu]#  curl -H "X-User01: test" http://test.app.com/test/
test-app-v1
[root@kube huidu]#
# 3. 其他域名访问 → 被 Gateway 拦截,无法访问到 test-app应用(gateway验证域名限制)
curl http://other.app.com/test/  # 替换为 other.app.com 的域名(未在灰度规则配置的域名)
# 预期输出:既不是test-app-v1,也不是test-app-v2 (canary) ; 而是 404 (因为外部nginx不存在 /test/资源)

[root@kube huidu]# curl http://other.app.com/test/
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
[root@kube huidu]#

6. 使用istio-ingressgateway网关

# 1. istio-ingressgateway 部署在指定的节点
# 给指定的节点打上标签(如:kube 节点)
kubectl label nodes kube istio-ingressgateway=true
# 2. 以下文件是在已有的  istio-ingressgateway 应用的基础添加4点内容分别是:
# 1. 多副本运行 istio-ingressgateway 应用
# 2. 节点选择:仅调度到打了 istio-ingressgateway=true 标签的节点
# 3. Pod反亲和:尽量分散到不同带有指定标签的节点
# 4. 通过pod所在节点 IP+80 访问 istio-ingressgateway 应用

# 在 master01 ,获取 istio-ingressgateway 应用运行文件内容
cd /root/huidu
kubectl -n istio-system  get deploy istio-ingressgateway -oyaml > istio-ingressgateway-new.yaml
---
# 3. 以下文件是添加了 4 点内容的 参考yaml。按照实际环境参考一下文件修改,只需要在对应位置 添加以下标有 序号的 4点内容,其他部分的保证和原来一致即可
# istio-ingressgateway-new.yaml
apiVtestion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "3"
  creationTimestamp: "2025-12-03T05:10:25Z"
  generation: 3
  labels:
    app: istio-ingressgateway
    app.kubernetes.io/instance: istio
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: istio-ingressgateway
    app.kubernetes.io/part-of: istio
    app.kubernetes.io/vtestion: 1.28.0
    helm.sh/chart: istio-ingress-1.28.0
    istio: ingressgateway
    istio.io/dataplane-mode: none
    istio.io/rev: default
    operator.istio.io/component: IngressGateways
    operator.istio.io/managed: Reconcile
    operator.istio.io/vtestion: 1.28.0
    release: istio
  name: istio-ingressgateway
  namespace: istio-system
spec:
  progressDeadlineSeconds: 600
  replicas: 1  # 1. 建议副本数和节点选择数保持一致,并且不小于2(本文档环境以 一个副本为例)
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: istio-ingressgateway
      istio: ingressgateway
  strategy:
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
        istio.io/rev: default
        prometheus.io/path: /stats/prometheus
        prometheus.io/port: "15020"
        prometheus.io/scrape: "true"
        sidecar.istio.io/inject: "false"
      creationTimestamp: null
      labels:
        app: istio-ingressgateway
        app.kubernetes.io/instance: istio
        app.kubernetes.io/managed-by: Helm
        app.kubernetes.io/name: istio-ingressgateway
        app.kubernetes.io/part-of: istio
        app.kubernetes.io/vtestion: 1.28.0
        chart: gateways
        helm.sh/chart: istio-ingress-1.28.0
        heritage: Tiller
        install.operator.istio.io/owning-resource: unknown
        istio: ingressgateway
        istio.io/dataplane-mode: none
        istio.io/rev: default
        operator.istio.io/component: IngressGateways
        release: istio
        service.istio.io/canonical-name: istio-ingressgateway
        service.istio.io/canonical-revision: latest
        sidecar.istio.io/inject: "false"
    spec:
      # 2. 节点选择:仅调度到打了 istio-ingressgateway=true 标签的节点
      nodeSelector:
        istio-ingressgateway: "true"
       # 2. 节点选择:仅调度到打了 istio-ingressgateway=true 标签的节点  
       
      # 3. Pod反亲和:尽量分散到不同带有指定标签的节点
      affinity:
        nodeAffinity: {}
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - istio-ingressgateway
                - key: istio
                  operator: In
                  values:
                  - ingressgateway
              topologyKey: kubernetes.io/hostname
          # 3. Pod反亲和:尽量分散到不同带有指定标签的节点
          
      containtest:
      - args:
        - proxy
        - router
        - --domain
        - $(POD_NAMESPACE).svc.cluster.local
        - --proxyLogLevel=warning
        - --proxyComponentLogLevel=misc:error
        - --log_output_level=default:info
        env:
        - name: PILOT_CERT_PROVIDER
          value: istiod
        - name: CA_ADDR
          value: istiod.istio-system.svc:15012
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: spec.nodeName
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: metadata.namespace
        - name: INSTANCE_IP
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: status.podIP
        - name: HOST_IP
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: status.hostIP
        - name: ISTIO_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              divisor: "0"
              resource: limits.cpu
        - name: SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: spec.serviceAccountName
        - name: ISTIO_META_WORKLOAD_NAME
          value: istio-ingressgateway
        - name: ISTIO_META_OWNER
          value: kubernetes://apis/apps/v1/namespaces/istio-system/deployments/istio-ingressgateway
        - name: ISTIO_META_MESH_ID
          value: cluster.local
        - name: TRUST_DOMAIN
          value: cluster.local
        - name: ISTIO_META_UNPRIVILEGED_POD
          value: "true"
        - name: ISTIO_META_CLUSTER_ID
          value: Kubernetes
        - name: ISTIO_META_NODE_NAME
          valueFrom:
            fieldRef:
              apiVtestion: v1
              fieldPath: spec.nodeName
        image: 192.168.133.123/repo/istio/proxyv2:1.28.0
        imagePullPolicy: IfNotPresent
        name: istio-proxy
        ports:
        - containerPort: 15021
          protocol: TCP
        - containerPort: 8080
          hostPort: 8080  # 4. 保证可通过pod 所在节点的 IP+8080 端口访问 (前提是 节点不存在8080端口,避免端口冲突,可改成80端口)
          protocol: TCP
        - containerPort: 8443
          protocol: TCP
        - containerPort: 15090
          name: http-envoy-prom
          protocol: TCP
        readinessProbe:
          failureThreshold: 30
          httpGet:
            path: /healthz/ready
            port: 15021
            scheme: HTTP
          initialDelaySeconds: 1
          periodSeconds: 2
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: "2"
            memory: 1Gi
          requests:
            cpu: 100m
            memory: 128Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/run/secrets/workload-spiffe-uds
          name: workload-socket
        - mountPath: /var/run/secrets/credential-uds
          name: credential-socket
        - mountPath: /var/run/secrets/workload-spiffe-credentials
          name: workload-certs
        - mountPath: /etc/istio/proxy
          name: istio-envoy
        - mountPath: /etc/istio/config
          name: config-volume
        - mountPath: /var/run/secrets/istio
          name: istiod-ca-cert
        - mountPath: /var/run/secrets/tokens
          name: istio-token
          readOnly: true
        - mountPath: /var/lib/istio/data
          name: istio-data
        - mountPath: /etc/istio/pod
          name: podinfo
        - mountPath: /etc/istio/ingressgateway-certs
          name: ingressgateway-certs
          readOnly: true
        - mountPath: /etc/istio/ingressgateway-ca-certs
          name: ingressgateway-ca-certs
          readOnly: true
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      serviceAccount: istio-ingressgateway-service-account
      serviceAccountName: istio-ingressgateway-service-account
      terminationGracePeriodSeconds: 30
      volumes:
      - emptyDir: {}
        name: workload-socket
      - emptyDir: {}
        name: credential-socket
      - emptyDir: {}
        name: workload-certs
      - configMap:
          defaultMode: 420
          name: istio-ca-root-cert
        name: istiod-ca-cert
      - downwardAPI:
          defaultMode: 420
          items:
          - fieldRef:
              apiVtestion: v1
              fieldPath: metadata.labels
            path: labels
          - fieldRef:
              apiVtestion: v1
              fieldPath: metadata.annotations
            path: annotations
        name: podinfo
      - emptyDir: {}
        name: istio-envoy
      - emptyDir: {}
        name: istio-data
      - name: istio-token
        projected:
          defaultMode: 420
          sources:
          - serviceAccountToken:
              audience: istio-ca
              expirationSeconds: 43200
              path: istio-token
      - configMap:
          defaultMode: 420
          name: istio
          optional: true
        name: config-volume
      - name: ingressgateway-certs
        secret:
          defaultMode: 420
          optional: true
          secretName: istio-ingressgateway-certs
      - name: ingressgateway-ca-certs
        secret:
          defaultMode: 420
          optional: true
          secretName: istio-ingressgateway-ca-certs
---

# 4. 重建 istio-ingressgateway 应用 
cd /root/huidu

kubectl delete -f istio-ingressgateway-new.yaml  # 需要把旧的 istio-ingressgateway 应用先删掉再重新apply,直接apply 容易失败
kubectl create  -f istio-ingressgateway-new.yaml  # 重新生效 istio-ingressgateway 应用

# 以下是 重建应用操作参考
root@kube huidu]# 
[root@kube huidu]# kubectl delete -f istio-ingressgateway-new.yaml
deployment.apps "istio-ingressgateway" deleted
[root@kube huidu]#
[root@kube huidu]# kubectl create  -f istio-ingressgateway-new.yaml
deployment.apps/istio-ingressgateway created
[root@kube huidu]#
[root@kube huidu]# kubectl -n istio-system get po -owide |grep istio-ingressgateway
istio-ingressgateway-ff4f9889c-lmclj   1/1     Running   0               3m4s    10.233.106.82   kube   <none>           <none>
[root@kube huidu]#

# 5. 验证方法 
# 以下分别是 节点IP+端口80 以及 pod IP+8080 端口 两种验证方法

[root@kube huidu]# kubectl -n istio-system get po -owide |grep istio-ingressgateway
istio-ingressgateway-ff4f9889c-lmclj   1/1     Running   0               3m4s    10.233.106.82   kube   <none>           <none>
[root@kube huidu]#
[root@kube huidu]# telnet 10.233.106.82 8080  # 1. pod IP + 8080 端口验证
Trying 10.233.106.82...
Connected to 10.233.106.82.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
[root@kube huidu]#
[root@kube huidu]# telnet 192.168.133.100 80  # 2. 节点IP + 80 端口验证
Trying 192.168.133.100...
Connected to 192.168.133.100.
Escape character is '^]'.
^]
telnet> quit
Connection closed.
[root@kube huidu]#

# 通过以上两种方法验证,并且出现类似以上结果,说明 应用重建完成,并且生效了。

7. 附:基于访问源IP实现灰度发布

# 1. 基于访问源 IP 实现灰度发布的核心原理,是通过 Istio VirtualService(简称 VS)从请求头(X-Forwarded-For/X-Real-IP)中提取客户端真实源 IP,结合 IP 匹配规则实现差异化路由;同时要求 Istio IngressGateway 需通过hostPort方式将业务端口暴露至 Pod 所在的节点上(而非 Service 的 NodePort/LoadBalancer 等方式),即直接通过「节点 IP + 业务端口(第9章示例中IngressGateway在节点暴露的是 8080 端口,下文将使用8080端口演示)」访问 IngressGateway。
# 2. 登录master01节点,基于第4章节内容(前提是IngressGateway已按照第6章的步修改了IngressGateway 实现通过hostPort方式将业务端口暴露至 Pod 所在的节点 )修改文件 test-app-vs.yaml

cd /root/huidu  
vim test-app-vs.yaml
---
# cat test-app-vs.yaml
# 3. VirtualService(核心路由:适配 Service 端口 80)
apiVtestion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: test-app-vs
  namespace: demo
spec:
  hosts:
  - "test.app.com"  # 访问域名
  gateways:
  - test-app-gateway  # gw 的名字
  http:
  - match:
    - headtest:
       # X-User:
       #   exact: "test"  #
        x-forwarded-for:
          prefix: "192.168.140.101"  # prefix: "192.168.140." # 表示以 192.168.140. 开头的客户端IP
      uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v2 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
  - match:
    - uri:
         prefix: "/test/"   # uri 路径前缀匹配
    route:
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v1      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 100     # 权重是 100,表示当请求头不匹配的时候,默认全部流量用于访问 v1 版本
    - destination:
        host: test-app  # 匹配 test-app Service 名
        subset: v2      # v1 版本
        port:
          number: 80  # 匹配 test-app Service 的 80 端口
      weight: 0       #  待 基于 v2 的请求头验证正常后,将 该(v2)权重设置为100, v1 版本权重设置为 0,就可以实现流量切换。 
---
# 4 .生效 test-app-vs.yaml 文件规则
kubectl apply -f test-app-vs.yaml
# 5. 确认环境信息 (注意本节点是master01节点 IP为192.168.140.101,是灰度的源IP )
[root@master01 huidu]# 
[root@master01 huidu]# # 确认 ingressgateway 实例运行节点
[root@master01 huidu]# kubectl -n istio-system get po -owide |grep ingressgateway
istio-ingressgateway-668c45f865-2vztg   1/1     Running   0          132m   10.233.96.8     node03     <none>           <none>
[root@master01 huidu]# # 确认 node03 的IP
[root@master01 huidu]# kubectl get no -owide 
NAME                     STATUS   ROLES           AGE    VtestION    INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                              KERNEL-VtestION                 CONTAINER-RUNTIME
master01   Ready    control-plane   126d   v1.28.0   192.168.140.101   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
master02   Ready    control-plane   126d   v1.28.0   192.168.140.102   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
master03   Ready    control-plane   126d   v1.28.0   192.168.140.103   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
node01     Ready    <none>          126d   v1.28.0   192.168.140.104   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
node02     Ready    <none>          126d   v1.28.0   192.168.140.105   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
node03     Ready    <none>          126d   v1.28.0   192.168.140.106   <none>        Red Hat Enterprise Linux 9.5 (Plow)   5.14.0-570.12.1.el9_6.x86_64   containerd://1.6.9
[root@master01 huidu]# 
[root@master01 huidu]# # 域名解析到 ingressgateway 实例对应的节点IP
[root@master01 huidu]# cat /etc/hosts|grep demo
192.168.140.106 test.app.com 
[root@master01 huidu]# 
[root@master01 huidu]# 

# 6. 在 192.168.140.101 节点验证

# 1. 客户端IP(192.168.140.171) → 访问 v2 版本
curl http://test.app.com:8080/test/
# 预期输出:test-app-v2 (canary)

[root@master01 huidu]# # 域名解析到 ingressgateway 实例对应的节点IP
[root@master01 huidu]# cat /etc/hosts|grep demo
192.168.140.176 test.app.com 
[root@master01 huidu]# 
[root@master01 huidu]# 
[root@master01 huidu]# # 在 192.168.140.101 节点验证(灰度规则的IP)
[root@master01 huidu]# curl http://test.app.com:8080/test/
test-app-v2 (canary)
[root@master01 huidu]# curl http://test.app.com:8080/test/
test-app-v2 (canary)
[root@master01 huidu]# curl http://test.app.com:8080/test/
test-app-v2 (canary)
[root@master01 huidu]# 

# 7. 在 192.168.140.102 节点验证(非灰度规则的IP)

# 1. 客户端IP(192.168.140.102) → 访问 v1 版本(默认路由)
curl http://test.app.com:8080/test/
# 预期输出:test-app-v1

[root@master02 ~]# 
[root@master02 ~]# 
[root@master02 ~]# # 域名解析到 ingressgateway 实例对应的节点IP
[root@master02 ~]# 
[root@master02 ~]# cat /etc/hosts|grep demo
192.168.140.176 test.app.com
[root@master02 ~]# 
[root@master02 ~]# # 在 192.168.140.102 节点验证(非灰度规则的IP)
[root@master02 ~]# 
[root@master02 ~]# curl http://test.app.com:8080/test/
test-app-v1
[root@master02 ~]# curl http://test.app.com:8080/test/
test-app-v1
[root@master02 ~]# curl http://test.app.com:8080/test/
test-app-v1
[root@master02 ~]# 
[root@master02 ~]# 

根据验证结果可以得知,可以通过 从 Istio VirtualService(简称 VS)从请求头(X-Forwarded-For/X-Real-IP)中提取客户端源 IP来实现基于客户端IP的恢复发布规则。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐