蓝绿双槽本地模拟实验手册(不走流水线版)

目标:在你自己的测试集群里,从 Istio 安装开始,纯手工 kubectl 搭出一套和生产方案同构的蓝绿双槽环境,把"域名分流 + 网格同色"完整跑通。
不走流水线、不用 Helm chart,所有资源手写 —— 目的是让你看懂每个资源在干什么。
全程可随时推倒重来:kubectl delete ns blue-green 一键清场。

0. 你将搭出来的东西(最终形态)

公网域名分流(NodePort 模拟 Ingress)
  myapp.net  → blue 槽(页面 hello-world)
  myapp.com  → green 槽(页面 hello-green)
  带 x-test-routing: green 头的请求 → 强制预览 green

集群内同色调用(开 mesh 后)
  蓝调用方 Pod → 永远到 blue
  绿调用方 Pod → 永远到 green
  没颜色的调用方 → 兜底到 activeSlot(blue)

资源清单(全部手写)
  ns blue-green(打注入标签)
  Service myapp(selector 同时选中两槽)
  Deployment myapp-blue / myapp-green(version 标签 + 按槽 ConfigMap)
  ConfigMap myapp-config-blue / myapp-config-green(页面内容差异)
  ConfigMap myapp-slots(activeSlot / blue.tag / green.tag / net.subset)
  DestinationRule myapp(subsets blue/green)
  VirtualService myapp(公网)+ VirtualService myapp-mesh(网格)

1. 前提检查

kubectl get nodes                        # 集群可用(你 master + 2 node 的测试集群即可)
kubectl get ns blue-green 2>/dev/null    # 报 NotFound 最好;存在的话先 delete 清掉
端口约定:本实验用 NodePort 31080(公网入口)和 31081(内部直连,教学用)。如果 31080 被占了,全文统一换成别的空闲端口(30000-32767 之间)。检查:ss -tlnp | grep 31080(在 node 上执行,无输出=空闲)。

2. 安装 Istio(istioctl 方式)

# 2.1 下载 istioctl(版本选 1.22.x,和生产 1.30 同系行为一致;如想对齐生产可换 1.30.x)
cd ~
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.5 sh -
cd istio-1.22.5
export PATH=$PWD/bin:$PATH
echo "export PATH=$PWD/bin:\$PATH" >> ~/.bashrc

# 2.2 验证
istioctl version          # client version 能显示即可(control plane 还没装,报连不上正常)

# 2.3 安装控制面(demo profile:含 istiod + ingressgateway,资源占用小)
istioctl install --set profile=demo -y

# 2.4 观察安装过程(约 1-3 分钟)
kubectl -n istio-system get pods -w
# 期望最终:
#   istiod-xxxxxxxxxx-xxxxx              1/1  Running
#   istio-ingressgateway-xxxxxxxxxx-x    1/1  Running

# 2.5 确认 istiod 是"默认版"(无修订名 → 用 istio-injection 标签体系)
kubectl -n istio-system get deploy | grep istiod
# 显示 istiod(而不是 istiod-1-22-5 这种带后缀的)= 默认版 ✓

# 2.6 把入口网关改成 NodePort(本地没有云 LB,这是必要改造)
kubectl -n istio-system patch svc istio-ingressgateway --type='json' -p='[
  {"op":"replace","path":"/spec/type","value":"NodePort"},
  {"op":"add","path":"/spec/ports/1/nodePort","value":31080}
]'
# ports[1] 一般是 80 端口项;改完验证:
kubectl -n istio-system get svc istio-ingressgateway
# 期望看到 80:31080/TCP

# 2.7 卸载方法(不需要执行,备查):
# istioctl uninstall --purge -y && kubectl delete ns istio-system

质量门 G1istiodistio-ingressgateway 都 Running,svc 显示 80:31080。不满足不要往下走。


3. 部署双槽应用(纯手工 YAML)

3.1 命名空间 + 按槽配置 + 记事本

kubectl create ns blue-green
# 注意:现在先不打注入标签(阶段 6 才开 mesh,对照实验)

kubectl -n blue-green create configmap myapp-config-blue \
  --from-literal=index.html='hello-world (BLUE)'

kubectl -n blue-green create configmap myapp-config-green \
  --from-literal=index.html='hello-green (GREEN)'

# 店长记事本(模拟 deploy.sh 维护的状态)
kubectl -n blue-green create configmap myapp-slots \
  --from-literal=activeSlot=blue \
  --from-literal=blue.tag=v1 \
  --from-literal=green.tag=v1 \
  --from-literal=net.subset=blue

3.2 一个 Service + 两套 Deployment

保存为 myapp.yaml 然后 kubectl apply -f myapp.yaml

# ── Service:selector 只认 app,同时选中蓝绿 ──
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: blue-green
spec:
  selector:
    app: myapp            # 故意不写 version
  ports:
  - port: 80
    targetPort: 80
---
# ── 蓝槽 ──
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
  namespace: blue-green
spec:
  replicas: 1
  selector:
    matchLabels: { app: myapp, version: blue }
  template:
    metadata:
      labels:
        app: myapp
        version: blue     # 颜色标签:DR 分组和 mesh 同色都靠它
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports: [{ containerPort: 80 }]
        volumeMounts:
        - { name: page, mountPath: /usr/share/nginx/html }
      volumes:
      - name: page
        configMap: { name: myapp-config-blue }
---
# ── 绿槽:与蓝槽只有三处不同(名字/version/挂的 CM)──
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
  namespace: blue-green
spec:
  replicas: 1
  selector:
    matchLabels: { app: myapp, version: green }
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports: [{ containerPort: 80 }]
        volumeMounts:
        - { name: page, mountPath: /usr/share/nginx/html }
      volumes:
      - name: page
        configMap: { name: myapp-config-green }

验证:

kubectl -n blue-green get pods -o wide
# myapp-blue-xxx    1/1 Running   (READY 1/1:还没注入,正常)
# myapp-green-xxx   1/1 Running

# 教学时刻:Service 现在同时选中两槽 → 内部访问是蓝绿轮询的
kubectl -n blue-green expose svc myapp --type=NodePort --name=myapp-direct --port=80 --node-port=31081
NODE1=<node1的内网IP>
curl -s http://$NODE1:31081/   # 多刷几次,hello-world 和 hello-green 交替出现
# 这就是文档说的"内部无 sidecar 时在蓝绿 Endpoint 间 round-robin"——记住这个现象
kubectl delete svc myapp-direct -n blue-green   # 看完就删,别留着干扰实验

质量门 G2:两 Pod Running;直连 Service 轮询现象已亲眼确认。


4. 公网分流:Gateway + DR + 公网 VS

保存为 istio-public.yaml 然后 kubectl apply -f istio-public.yaml

# ── Gateway:入口开 80 端口,接受两个域名 ──
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: myapp-gw
  namespace: blue-green
spec:
  selector:
    istio: ingressgateway        # 选中 istio-system 里的入口网关 Pod
  servers:
  - port: { number: 80, name: http, protocol: HTTP }
    hosts: ["myapp.net", "myapp.com"]
---
# ── DestinationRule:只声明两条路(subset),不写域名不写 Gateway ──
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: myapp
  namespace: blue-green
spec:
  host: myapp.blue-green.svc.cluster.local
  subsets:
  - name: blue
    labels: { version: blue }
  - name: green
    labels: { version: green }
---
# ── 公网 VS:只挂 Ingress Gateway ──
# 规则顺序:Header 预览 → .net authority → .com authority
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
  namespace: blue-green
spec:
  hosts: ["myapp.net", "myapp.com"]
  gateways: ["myapp-gw"]
  http:
  - name: preview                # VIP 卡:优先级最高
    match:
    - headers: { x-test-routing: { exact: green } }
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: green
  - name: net                    # .net 传单 → net.subset 记的槽(初始 blue)
    match:
    - authority: { exact: "myapp.net" }
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: blue
  - name: com                    # .com 传单 → 写死 green(模拟生产"com 不动")
    match:
    - authority: { exact: "myapp.com" }
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: green

验收(<node1内网IP> 换成你的):

NODE1=<node1的内网IP>

curl -s -H "Host: myapp.net" http://$NODE1:31080/    # 期望 hello-world (BLUE)
curl -s -H "Host: myapp.com" http://$NODE1:31080/    # 期望 hello-green (GREEN)
curl -s -H "Host: myapp.net" -H "x-test-routing: green" http://$NODE1:31080/
# 期望 hello-green (GREEN):Header 预览压过域名规则

质量门 G3:三条 curl 结果如上。失败排查:istioctl -n blue-green analyzekubectl -n istio-system logs deploy/istio-ingressgateway --tail=50


5. 模拟 switch / rollback(手动扮演 deploy.sh)

生产的 switch:test 改的是 slots CM 的 net.subset + 同步改 VS;这里你手动做同样的事:

# ── switch:.net 切到 green ──
kubectl -n blue-green patch configmap myapp-slots --type merge -p '{"data":{"net.subset":"green"}}'
kubectl -n blue-green patch vs myapp --type json \
  -p='[{"op":"replace","path":"/spec/http/1/route/0/destination/subset","value":"green"}]'

curl -s -H "Host: myapp.net" http://$NODE1:31080/    # 现在 → hello-green (GREEN)
curl -s -H "Host: myapp.com" http://$NODE1:31080/    # 不受影响,还是 GREEN(它本来就指 green)

# ── rollback:切回 blue ──
kubectl -n blue-green patch configmap myapp-slots --type merge -p '{"data":{"net.subset":"blue"}}'
kubectl -n blue-green patch vs myapp --type json \
  -p='[{"op":"replace","path":"/spec/http/1/route/0/destination/subset","value":"blue"}]'

curl -s -H "Host: myapp.net" http://$NODE1:31080/    # 回到 hello-world (BLUE)
思考:注意 CM 里的 activeSlot 全程没动过——这就是"问作者 5 条"里那个问题的实物版:switch 只改了 net.subset,activeSlot 没同步。如果 deploy.sh 靠 activeSlot 算非活跃槽,下次发版会发生什么?(答:发到正在接 .net 流量的 green 头上。)

质量门 G4:switch/rollback 即时生效,.com 全程无感。


6. 开 mesh:注入 + 网格 VS + 同色验收

6.1 打注入标签并重启

kubectl label ns blue-green istio-injection=enabled --overwrite
kubectl -n blue-green rollout restart deploy/myapp-blue deploy/myapp-green

kubectl -n blue-green get pods
# READY 2/2 = 应用 + istio-proxy 注入成功 ✓
# (如果还是 1/1:检查标签打对没有、istiod 是否 Running)

6.2 部署网格 VS(只挂 mesh,和公网 VS 分开)

保存为 istio-mesh.yaml 然后 kubectl apply -f istio-mesh.yaml

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp-mesh
  namespace: blue-green
spec:
  hosts: ["myapp.blue-green.svc.cluster.local"]
  gateways: ["mesh"]               # 保留字:只管 sidecar 间流量
  http:
  - name: from-blue                # 蓝调用方 → 蓝
    match:
    - sourceLabels: { version: blue }
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: blue
  - name: from-green               # 绿调用方 → 绿
    match:
    - sourceLabels: { version: green }
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: green
  - name: default                  # 无颜色调用方 → 兜底 activeSlot(blue)
    route:
    - destination:
        host: myapp.blue-green.svc.cluster.local
        subset: blue

6.3 验收一:sidecar 按颜色过滤路由

kubectl -n blue-green exec deploy/myapp-blue -c istio-proxy -- \
  curl -s localhost:15000/config_dump | grep -o '"name": "myapp-[^"]*"' | sort | uniq -c
# 期望只看到 from-blue(没有 from-green = 正常裁剪,不是漏配)

kubectl -n blue-green exec deploy/myapp-green -c istio-proxy -- \
  curl -s localhost:15000/config_dump | grep -o '"name": "myapp-[^"]*"' | sort | uniq -c
# 期望只看到 from-green

6.4 验收二:探针实测同色(建→测→删)

kubectl -n blue-green run mesh-probe-blue --image=curlimages/curl --restart=Never \
  --labels=version=blue --command -- sleep 3600
kubectl -n blue-green run mesh-probe-green --image=curlimages/curl --restart=Never \
  --labels=version=green --command -- sleep 3600
kubectl -n blue-green get pod -l run --field-selector=status.phase=Running -w   # 等两个 2/2 Running

kubectl -n blue-green exec mesh-probe-blue -- \
  curl -s http://myapp.blue-green.svc.cluster.local/     # 刷几次都应是 hello-world (BLUE)
kubectl -n blue-green exec mesh-probe-green -- \
  curl -s http://myapp.blue-green.svc.cluster.local/     # 刷几次都应是 hello-green (GREEN)

# 教学对照:无颜色探针 → 兜底到 activeSlot(blue)
kubectl -n blue-green run mesh-probe-plain --image=curlimages/curl --restart=Never \
  --command -- sleep 3600
kubectl -n blue-green exec mesh-probe-plain -- \
  curl -s http://myapp.blue-green.svc.cluster.local/     # 期望 hello-world (BLUE)

# 测完即删
kubectl -n blue-green delete pod mesh-probe-blue mesh-probe-green mesh-probe-plain

质量门 G5:蓝探针只见蓝、绿探针只见绿、无色探针兜底蓝。至此全套双栈并行跑通。


7. 教学对照实验(选做但强烈建议)

7.1 从 sidecar 容器里 curl(演示"错误验收方式")

kubectl -n blue-green run mesh-probe-blue --image=curlimages/curl --restart=Never \
  --labels=version=blue --command -- sleep 3600

# 错误示范:从 istio-proxy 容器里发请求(绕过 Envoy,直连 Service 轮询)
kubectl -n blue-green exec mesh-probe-blue -c istio-proxy -- \
  curl -s http://myapp.blue-green.svc.cluster.local/
# 多刷几次:蓝绿随机出现!因为流量没进 Envoy 的规则体系
# 结论:mesh 验收永远从应用容器发请求

kubectl -n blue-green delete pod mesh-probe-blue

7.2 合并 VS 翻车(演示生产文档的头号坑)

把公网和 mesh 规则合并成一条 VS(错误示范):

kubectl -n blue-green delete vs myapp-mesh
kubectl -n blue-green patch vs myapp --type json -p='[
  {"op":"replace","path":"/spec/gateways","value":["myapp-gw","mesh"]},
  {"op":"add","path":"/spec/http/0","value":{
    "name":"from-green",
    "match":[{"sourceLabels":{"version":"green"}}],
    "route":[{"destination":{"host":"myapp.blue-green.svc.cluster.local","subset":"green"}}]
  }}
]'
# 然后重新建探针测试内部调用,观察是不是"内部全绿/颜色混乱"
# 域名分流(公网)可能依然正常 → 最容易误判成"没问题"
# 玩完恢复:kubectl delete ns blue-green 后从第 3 章重来(最快)

8. 观察与排错命令备查

istioctl -n blue-green analyze                          # 配置体检(第一排错工具)
kubectl -n blue-green get gw,vs,dr                      # 看网格资源
kubectl -n istio-system logs deploy/istiod --tail=50    # 控制面日志
kubectl -n istio-system logs deploy/istio-ingressgateway --tail=50   # 入口日志
kubectl -n blue-green exec deploy/myapp-blue -c istio-proxy -- \
  curl -s localhost:15000/config_dump | head -100       # sidecar 实际收到的配置

9. 清场

kubectl delete ns blue-green
# 连 Istio 也一起卸掉(不想留的话):
# istioctl uninstall --purge -y && kubectl delete ns istio-system

10. 本实验 ↔ 生产方案对照表

本实验(手工模拟)生产方案(平台自动化)
手写 Deployment×2 / Service / DR / VSgeneric-service chart ≥1.1.2 渲染,values 开 blueGreen
你手动 patch VS + CM 模拟 switch手动 job switch:test 改 net.subset
你手动改 CM 的槽 tagdeploy.sh --inactive-slot 自动算非活跃槽并更新
NodePort + curl Host 头模拟域名真实 Ingress Gateway + 真实 .net/.com 域名
ConfigMap 挂页面当"按槽配置"APPLICATION_YAML File 变量按槽拆(生产要自己设计)
nginx 无状态随便切真服务要考虑库表只加不改、HPA/PDB 按槽、优雅退出
istioctl demo profileVKE 生产网格(Istio 1.30 系,可能托管)
注入标签 istio-injection(默认版 istiod)生产要先查 webhook namespaceSelector 确认标签体系

11. 实验自查题(做完能答才算会)

  1. 为什么 Service 的 selector 不能写 version?
  2. 直连 Service(31081)为什么是轮询,开了 mesh 之后探针访问为什么就固定颜色了?(提示:谁拦下了流量)
  3. DR 和 VS 各负责什么?为什么 subset 用 version 标签而不用镜像 tag?
  4. 为什么 kubectl exec -c istio-proxy -- curl 测不出 mesh 分流?
  5. 合并 VS 后故障现象为什么是"域名正常、内部全绿"?
  6. myapp-slots CM 里 activeSlot 和 net.subset 分别被谁读?不同步会怎样?

标签: none

添加新评论