istio-test-greenblue
蓝绿双槽本地模拟实验手册(不走流水线版)
目标:在你自己的测试集群里,从 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 清掉端口约定:本实验用 NodePort31080(公网入口)和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质量门 G1:istiod 和 istio-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=blue3.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 analyze、kubectl -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: blue6.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-green6.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-blue7.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-system10. 本实验 ↔ 生产方案对照表
| 本实验(手工模拟) | 生产方案(平台自动化) |
|---|---|
| 手写 Deployment×2 / Service / DR / VS | generic-service chart ≥1.1.2 渲染,values 开 blueGreen |
| 你手动 patch VS + CM 模拟 switch | 手动 job switch:test 改 net.subset |
| 你手动改 CM 的槽 tag | deploy.sh --inactive-slot 自动算非活跃槽并更新 |
| NodePort + curl Host 头模拟域名 | 真实 Ingress Gateway + 真实 .net/.com 域名 |
| ConfigMap 挂页面当"按槽配置" | APPLICATION_YAML File 变量按槽拆(生产要自己设计) |
| nginx 无状态随便切 | 真服务要考虑库表只加不改、HPA/PDB 按槽、优雅退出 |
| istioctl demo profile | VKE 生产网格(Istio 1.30 系,可能托管) |
| 注入标签 istio-injection(默认版 istiod) | 生产要先查 webhook namespaceSelector 确认标签体系 |
11. 实验自查题(做完能答才算会)
- 为什么 Service 的 selector 不能写 version?
- 直连 Service(31081)为什么是轮询,开了 mesh 之后探针访问为什么就固定颜色了?(提示:谁拦下了流量)
- DR 和 VS 各负责什么?为什么 subset 用 version 标签而不用镜像 tag?
- 为什么
kubectl exec -c istio-proxy -- curl测不出 mesh 分流? - 合并 VS 后故障现象为什么是"域名正常、内部全绿"?
- myapp-slots CM 里 activeSlot 和 net.subset 分别被谁读?不同步会怎样?