k8s服务网格(Service Mesh)的istio灰度发布产品实战教程

Istio 是目前最流行的开源服务网格(Service Mesh)产品之一,主要用于解决 Kubernetes(k8s)等云原生环境中的微服务架构所面临的复杂性挑战。
在微服务架构中,服务数量众多,服务间的网络通信极其复杂。传统的做法是开发者需要在业务代码中编写大量的重试、超时、熔断、负载均衡等网络容错逻辑,这导致业务代码臃肿且与网络逻辑强耦合。Istio 的核心思想是“非侵入式”,它不需要修改应用程序的业务代码,就能为服务提供强大的治理能力。
以下是 Istio 的核心优势与主要功能:
1. 解耦业务逻辑与网络治理
Istio 通过在每个服务 Pod 旁注入一个轻量级的代理容器(称为 Sidecar)来实现这一魔法。这个 Sidecar 拦截了微服务之间的所有进出流量,所有的网络策略(如负载均衡、路由、安全加密等)都由 Istio 的控制平面统一下发和管理 。这使得开发团队可以专注于业务本身,而运维团队可以通过配置来管理网络行为 。
2. 精细化的流量管理
这是 Istio 最核心的功能之一,也是实现灰度发布的基础 :
-
智能路由与灰度发布:支持复杂的流量规则配置,可以轻松实现 A/B 测试、金丝雀发布(Canary Deployment)以及按百分比逐步切分的蓝绿部署 。
-
弹性容错机制:提供开箱即用的故障恢复特性,如自动重试、超时控制、熔断(防止故障级联传播)以及故障注入(用于测试系统韧性) 。
-
丰富的负载均衡:提供轮询、随机、最少请求等多种负载均衡策略 。
3. 默认零信任的安全通信
Istio 为微服务间的安全通信提供了底层保障 :
-
mTLS 加密:自动在服务和 Sidecar 之间建立双向 TLS 加密通道,确保数据传输的安全性 。
-
身份认证与授权:基于工作负载身份进行服务间的身份验证和访问控制,实现细粒度的权限管理 。
4. 强大的可观测性
在复杂的分布式系统中,排查网络问题是一大难点。Istio 自动收集网格内所有流量的遥测数据 :
-
全链路追踪(Tracing):能够追踪一个请求经过了哪些服务,帮助定位性能瓶颈。
-
监控与日志(Metrics & Logging):自动生成详细的流量指标(如 QPS、延迟、错误率)和日志记录,可与 Prometheus、Grafana 等工具无缝集成 。
5. 架构演进
Istio 在 1.5 版本做出了重大架构调整,将原本多个独立的控制平面组件(如 Pilot、Galley、Citadel、Mixer)整合成了一个统一的单体结构 istiod。这不仅简化了部署流程,也降低了运维的复杂度。
结合“灰度发布产品实战教程”的背景,Istio 正是通过其强大的流量管理能力,允许运维人员在不中断线上服务的情况下,将一小部分生产流量引导至新版本的服务实例上进行验证,从而实现安全、平滑的版本迭代。




这是一份关于 Istio 1.9.6 在 Kubernetes 1.20.4 集群上的离线安装实操步骤。
我为你整理了图片中的核心操作流程,并针对企业级实战场景补充了一些优化建议,方便你后续编写运维手册或向领导汇报。
📋 核心操作步骤梳理
图片展示了典型的离线环境部署 Istio 的流程,主要分为三个阶段:
1. 环境准备与镜像导入(Node节点)
-
检查集群:确认 K8s 集群版本为
v1.20.4,且节点状态正常。 -
离线解压:在 Node 节点(
k8s-n1,k8s-n2)上解压镜像包all_images.zip。 -
批量加载:使用
for循环命令将所有.tar镜像文件批量加载到 Docker 中,确保后续部署不再依赖外网下载。
2. Istio 软件包部署(Master节点)
-
上传与解压:在 Master 节点(
k8s-m)上传并解压istio-1.9.6-linux-amd64.tar.gz。 -
环境变量配置:将
istioctl命令行工具移动到/usr/bin/目录,以便全局调用。 -
查看配置 profiles:列出了
default、demo、minimal等多种预设配置。 -
执行安装:使用
istioctl install -y命令,基于default配置一键安装 Istio 控制平面(包括 Istiod 和 Ingress Gateways)。
3. 验证安装
-
使用
kubectl get ns查看命名空间,虽然截图中只显示了default,但通常此时会生成一个名为istio-system的新命名空间来容纳 Istio 组件。
💡 专家优化建议(针对实战场景)
这套教程步骤非常基础,适合学习。但在企业真实生产环境中,建议做以下补充和调整:
-
指定命名空间安装(最佳实践)
默认安装在
istio-system是没问题的,但为了更好的资源隔离和权限管理,建议显式指定命名空间:istioctl install --set profile=demo -y -n istio-system -
开启自动注入 Sidecar
安装完控制平面后,必须开启命名空间的自动注入功能,业务 Pod 进入该命名空间才会自动挂载 Envoy 代理:
kubectl label namespace default istio-injection=enabled -
镜像仓库优化
图片中使用
docker load加载镜像虽然可行,但后续升级和维护较繁琐。建议搭建私有镜像仓库(如 Harbor),将解压后的镜像重新打 Tag 并推送到私仓,然后在 K8s 中配置imagePullSecrets,实现完全自动化的拉取。 -
安全加固
教程中未提及 K8s 的 RBAC(基于角色的访问控制)。在生产环境中,应为 Istio 配置严格的 ServiceAccount 和 ClusterRole,避免权限过大。
-
这是一张显示 Istio 1.9.6 控制平面安装成功 的终端截图。
截图中的核心信息是:通过
kubectl get pod -n istio-system命令查询结果显示,Istio 的核心组件(如istiod和ingressgateway)均处于Running状态且就绪(Ready 1/1)。这意味着 Istio 的控制平面已经成功运行,具备了管理和治理微服务网格的能力。基于截图状态,建议进行以下验证与下一步操作:
1. 验证当前状态
当前安装的是
default配置,包含了 Ingress Gateway。可以通过以下命令进一步确认服务是否对外暴露:kubectl get svc -n istio-system确认是否有
istio-ingressgateway服务及其暴露的端口(通常是 80 和 443)。2. 建议的下一步实操
控制平面安装完成只是第一步,接下来需要部署数据平面(即业务应用)来验证整个网格是否工作正常:
-
部署官方示例(Bookinfo):
利用 Istio 自带的经典示例来测试流量管理和网关功能。
# 创建示例应用的命名空间并开启自动注入 kubectl create namespace bookinfo kubectl label namespace bookinfo istio-injection=enabled # 部署 Bookinfo 应用 kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfo -
配置网关路由:
为了让外部流量进入网格,需要创建一个 Gateway 和 VirtualService 资源。
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml -n bookinfo -
访问测试:
获取 Ingress Gateway 的 IP 地址,在浏览器中访问对应的页面,验证是否能看到 Bookinfo 的界面,以此证明 Istio 已完全生效。
-

这是一份非常经典的 Istio 服务网格数据平面(Data Plane)注入 实操教程。
图片内容清晰地展示了两种让应用程序接入服务网格的方式,重点演示了手动注入 Sidecar 代理的过程。
为了方便你复习或整理笔记,我将图中的核心步骤提取如下,并对关键的配置细节进行了补充说明:
1. 部署测试应用(以 httpbin 为例)
首先,进入 Istio 自带的示例目录,准备部署一个普通的 HTTP 测试服务。
cd /root/istio-1.9.6/samples/httpbin/
2. 方法一:直接部署(不带边车)
为了做对比,先不使用 Istio 治理,直接发布应用:
-
执行命令:
kubectl apply -f httpbin-nodeport.yaml -
观察结果:使用
kubectl get pod -o wide查看,此时 Pod 只有 1/1 个容器,即应用本身。 -
访问测试:
curl 10.233.81.11(此时能通,说明应用本身没问题)。
3. 方法二:手动注入 Sidecar(核心步骤)
这是图片中最关键的部分。通过 istioctl kube-inject命令,在部署前动态修改 YAML 文件,将 Envoy 代理容器注入进去。
-
执行命令:
kubectl apply -f <(istioctl kube-inject -f httpbin-nodeport.yaml) -
命令解释:
istioctl kube-inject会读取原始的 YAML 文件,在其基础上增加 Sidecar 相关的 Deployment 配置,然后输出一个新的 YAML,再由kubectl apply去执行。
4. 验证注入结果
再次查看 Pod 状态,验证边车是否成功注入:
-
查看 Pod:
kubectl get pod -o wide-
关键变化:Pod 变成了 2/2 (
Ready列)。这代表一个主应用容器 + 一个 Istio Proxy (Envoy) 边车容器。
-
-
访问测试:
curl 10.233.81.12-
依然可以访问,说明边车注入没有破坏原有业务逻辑。
-
-
查看详情:
kubectl describe pod httpbin-xxxx-
可以在输出中看到两个 Containers,一个是
httpbin(应用),另一个是istio-proxy(边车)。
-
💡 深度解读:第二张图的配置细节
第二张截图展示了 Pod 的 YAML 配置片段(Volume 部分)。
这里面包含了 Istio 边车容器运行所必需的关键挂载卷,非常值得了解:
-
istio-envoy(emptyDir):这是一个内存盘(Medium: Memory),用于 Envoy 存放配置和日志的临时文件。 -
istio-token(projected):这是特别重要的一项。它是一个投影卷,包含了带有特定 Audience (istio-ca) 的服务账号令牌。这使得 Pod 内的应用可以向 Istiod 进行安全的 mTLS 认证。 -
istio-podinfo:用于挂载 Pod 的标签(labels)和注解(annotations),供边车读取动态配置。 -
istiod-ca-cert:挂载 CA 证书,用于服务间的双向 TLS 加密。
📝 专家建议(进阶)
虽然手动注入(kube-inject)非常适合学习和单次调试,但在企业级生产环境中,更推荐使用自动注入:
只需要给命名空间打上一个标签(istio-injection=enabled),以后只要在这个命名空间下部署任何新的应用,Kubernetes 会自动调用 webhook 完成 Sidecar 的注入,无需人工干预。


这是一份关于 Istio 流量治理 的实操教程,重点展示了如何配置 Gateway(网关) 和 VirtualService(虚拟服务) 来将外部流量引入网格内部。
相比之前几步的环境准备,这一步标志着你正式开始掌握 Istio 的核心功能——控制微服务之间的通信流量。
为了方便你复习和整理笔记,我将图中的核心步骤提取如下:
1. 核心操作:配置 Ingress Gateway
为了让外部用户能够访问到集群内部的服务(如 httpbin),必须定义一个入口网关。
图中展示了 httpbin-gateway.yaml的核心内容:
-
kind: Gateway:定义一个网关资源。
-
selector: istio: ingressgateway:指定该网关使用哪个负载均衡器(即之前安装时创建的
istiod管理的 Ingress Gateway)。 -
servers: 监听 80 端口的 HTTP 流量,并允许所有主机(
hosts: ["*"])访问。
2. 核心操作:配置流量路由 (VirtualService)
仅仅定义了网关还不够,需要告诉网关收到请求后应该转发给哪个服务。
图中展示了 httpbin-virtualservice.yaml的核心内容:
-
kind: VirtualService:定义一个虚拟服务,用于配置路由规则。
-
hosts:
["*"]:表示这个规则对所有进入网关的流量生效。 -
gateways: 绑定到上面定义的
httpbin-gateway。 -
http.route: 定义了具体的路由动作,将流量转发到名为
httpbin的后端服务,目标端口为 8000。
3. 实操流程总结
这两个 YAML 文件(httpbin-gateway.yaml和 httpbin-virtualservice.yaml)通常需要配合使用:
-
应用配置:使用
kubectl apply -f命令将这两个配置文件应用到集群。 -
获取入口 IP:通过
kubectl get svc istio-ingressgateway -n istio-system获取网关的外部 IP 地址(通常是 LoadBalancer 分配的 IP)。 -
访问测试:在浏览器或使用
curl访问http://<INGRESS_IP>/get,如果能正确返回httpbin服务的响应,说明网关配置成功。
💡 专家建议(进阶)
虽然图中的配置足够启动一个简单的服务,但在生产环境中,建议注意以下几点:
-
安全性:图中使用的是 HTTP 协议。在实际生产环境中,强烈建议将
protocol: HTTP改为HTTPS,并配合 mTLS (Mutual TLS) 对进出网关的流量进行加密,防止数据窃听和篡改。 -
域名限制:
hosts: ["*"]允许任何域名访问。为了安全和明确路由,建议将其改为具体的域名,例如["api.example.com"],这样网关就能根据 Host 头区分不同的服务。 -
端口冲突:如果集群中已经有其他服务占用了 80 端口,或者你的 Ingress Gateway 已经配置了其他服务,需要注意端口冲突问题。可以通过修改
port.number来解决。
以下是基于您之前实操场景整理的 生产级 Istio 网关(Gateway)与虚拟服务(VirtualService)完整配置文件,包含 HTTP(基础版) 和 HTTPS(生产推荐版),并附带逐行详解。
方案一:HTTP 配置(学习与测试环境)
适用于快速验证连通性,对应您图片中的配置逻辑。
1. Gateway 配置:httpbin-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: httpbin-gateway
namespace: istio-system # 建议放在 istio-system 或业务所属命名空间
spec:
selector:
# 关键:选择 Istio 安装的 Ingress Gateway 负载均衡器
# 默认情况下,Istio 安装时会创建一个名为 istio-ingressgateway 的 Deployment
istio: ingressgateway
servers:
- port:
number: 80 # 监听端口
name: http # 端口名称(便于识别)
protocol: HTTP # 协议类型
hosts:
- "*" # 允许访问的主机名,* 代表所有域名/IP
# 生产建议:改为具体域名,如 "httpbin.example.com"
2. VirtualService 配置:httpbin-virtualservice.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: httpbin-route
namespace: default # 必须与后端服务(httpbin)在同一个命名空间
spec:
hosts:
- "*" # 匹配 Gateway 中定义的主机
gateways:
- httpbin-gateway # 绑定上面定义的 Gateway 名称
# 如果是跨命名空间引用,需写为:httpbin-gateway.istio-system.svc.cluster.local
http:
- match:
- uri:
prefix: /get # 匹配 URI 前缀为 /get 的请求
route:
- destination:
host: httpbin.default.svc.cluster.local # 后端服务的 FQDN
port:
number: 8000 # 后端服务的端口
方案二:HTTPS 配置(企业生产推荐)
包含 TLS 证书配置,更安全。
1. 创建 TLS 证书 Secret
首先,将您的证书文件存入 Kubernetes Secret(假设证书名为 tls.crt,密钥为 tls.key):
kubectl create secret tls httpbin-credential \
--key=tls.key \
--cert=tls.crt \
-n istio-system
2. Gateway 配置(HTTPS):httpbin-gateway-tls.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: httpbin-gateway-tls
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE # 单向 TLS 认证
credentialName: httpbin-credential # 引用上面创建的 Secret 名称
hosts:
- "httpbin.example.com" # 强制指定具体域名
📝 配置详解与参数说明
|
配置项 |
含义 |
注意事项 |
|---|---|---|
|
|
定义流量入口。相当于大楼的“大门”和“保安”。 |
只负责接收流量,不负责转发。 |
|
|
标签选择器。告诉 Istio:“请把这个网关配置应用到带有 |
必须与 Istio 安装时的 Ingress Gateway Pod 标签一致。 |
|
|
定义监听的端口和协议。 |
如果是 TCP 服务,协议改为 |
|
|
定义该网关处理哪些域名的请求。 |
在 Gateway 中设为 |
|
|
定义路由规则。相当于大楼里的“楼层索引”和“导航”。 |
|
|
|
绑定 Gateway。如果不写此项,默认为 |
|
|
|
流量匹配规则。可以根据 URI、Header、Query Params 等进行匹配。 |
支持 |
|
|
目标服务地址。必须是 Kubernetes 集群内的 Service FQDN。 |
格式通常为 |
🚀 部署与验证命令
-
应用配置
kubectl apply -f httpbin-gateway.yaml kubectl apply -f httpbin-virtualservice.yaml -
获取访问地址
# 获取 Ingress Gateway 的外部 IP 或域名 export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http")].port}') echo "访问地址: http://$INGRESS_HOST:$INGRESS_PORT/get" -
验证
curl "http://$INGRESS_HOST:$INGRESS_PORT/get"如果返回 JSON 格式的 HTTP 响应,说明配置成功。

这是一张展示 Istio 流量入口(Ingress Gateway)完整配置与验证 的终端操作截图。
截图中的操作流程非常标准,演示了如何将一个服务(httpbin)通过 Istio 网关暴露出去,并通过多种命令验证其网络状态。
为了方便你复习或整理实验报告,我将图中的核心操作步骤和关键配置提取如下:
1. 核心操作:应用网关配置
图中展示了如何部署 Gateway和 VirtualService资源,这是打通外部流量的关键两步:
-
部署 Gateway:
kubectl apply -f httpbin-gateway.yaml作用:定义了一个监听端口(80),并将外部流量引入网格。
-
部署 VirtualService:
kubectl apply -f httpbin-virtualservice.yaml作用:定义了具体的路由规则,将进入网关的流量转发给名为
httpbin的后端服务。
2. 验证步骤:层层递进的检查
图中演示了一套非常严谨的“由外到内”的验证流程:
-
第一步:检查资源配置
-
命令:
kubectl get pod,gateway -
目的:确认
httpbin-gateway资源是否成功创建。
-
-
第二步:检查服务(Service)暴露情况
-
命令:
kubectl get svc -n istio-system -
关键发现:图中显示
istio-ingressgateway的EXTERNAL-IP处于<pending>状态。这说明当前的 K8s 集群环境没有自动分配公网 IP(常见于裸金属服务器或没有配置云厂商 LB 插件的场景)。此时通常通过 NodePort 或 HostNetwork 方式访问。
-
-
第三步:检查数据平面(Pod)状态
-
命令:
kubectl get pod -n test(假设在 test 命名空间) -
关键发现:
httpbin的 Pod 状态为2/2 Running。这再次印证了边车代理(Sidecar)已成功注入,一个主容器 + 一个 Envoy 代理容器都在正常运行。 -
同时查看了
httpbin服务的 ClusterIP 和 Port(8000)。
-
💡 专家建议(针对截图中的 <pending>状态)
图中 istio-ingressgateway的 EXTERNAL-IP挂起是新手常遇到的问题,这里提供两种解决思路:
-
使用 NodePort 访问(推荐用于测试/裸金属环境)
既然没有 LoadBalancer IP,可以直接使用 Ingress Gateway 暴露的 NodePort 端口访问。
# 查看具体的 NodePort 端口(例如 30732) kubectl get svc istio-ingressgateway -n istio-system # 访问格式:http://<任意节点IP>:<NodePort>/get curl http://192.168.171.128:30732/get -
配置 HostNetwork(适用于高权限环境)
如果希望直接通过服务器的物理 IP 访问,可以修改
istio-ingressgateway的 Deployment,添加hostNetwork: true,这样 Pod 会直接使用宿主机的网络命名空间,端口直接绑定在宿主机网卡上。
📝 总结
这张截图完整地展示了 Istio 服务网格中 “控制平面下发配置 -> 数据平面承载流量” 的闭环。从配置文件的创建,到资源的部署,再到多维度(ConfigMap, Gateway, Service, Pod)的状态校验,是一个非常标准的微服务治理接入流程。

这张图非常经典,它展示了 Istio 最核心的“控制平面”与“数据平面”的交互架构。结合我们之前的 Nginx 实操,我来带你把这张图里的逻辑彻底串起来,你就明白“你写的 YAML 到底是怎么让服务跑起来的”。
我们可以把这张图分为三个关键动作来看:
1. 控制流:Istiod 如何“遥控”整个集群?(上半部分)
-
注册能力(CRD):
-
图中最上面的
kube-apiserver和Etcd是 K8s 的大脑。 -
Istio 安装时,会向 K8s 注册一堆自定义的“菜单”(CRD,比如
VirtualService、Gateway)。这就是为什么你可以用kubectl apply直接下发 Istio 规则,K8s 认识它们并把它们存进 Etcd。
-
-
监听与下发(Istiod 的工作):
-
中间那个绿色的方块
Istiod(以前叫 Pilot)是整个系统的指挥官。 -
它一直在盯着
kube-apiserver(通过 List/Watch 机制)。 -
实战对应:当你创建了
Gateway或VirtualService,Istiod立刻就知道了。然后,它会把这些人类易读的规则,翻译成 Envoy 代理能听懂的底层配置语言(xDS 协议),并推送给所有安装了 Sidecar 的 Pod。
-
2. 数据流:Sidecar 是如何“偷梁换柱”的?(下半部分)
-
Envoy 注入:
-
看最下面的
Node节点里的Pod。每个 Pod 里都有两个容器:你的业务APP(比如 Nginx)和一个Envoy代理(Sidecar)。
-
-
流量劫持:
-
虽然图里没画 iptables,但这是实现拦截的关键。Istio 注入 Sidecar 时,会在 Pod 里写入 iptables 规则。
-
入站:所有发往 Nginx 80 端口的流量,都会被强行重定向到 Envoy 的 15006 端口。
-
出站:Nginx 如果想访问其他服务,流量也会先发给 Envoy。
-
-
执行策略:
-
Envoy 接收到流量后,查看
Istiod下发的配置。如果是灰度发布规则,它就按权重切流;如果是普通转发,它就直接发给目标服务。
-
3. 结合 Nginx 案例的“全景图”
现在我们可以把你之前的实操步骤和这张图完美对上了:
-
你敲的命令:
kubectl apply -f nginx-vs.yaml-
对应图中:左侧的
kubectl/kubecttl→kube-apiserver。
-
-
配置生效:
-
Istiod监听到变化,开始工作。 -
对应图中:中间的
Istiod方块。
-
-
流量进入:
-
外部请求通过 MetalLB VIP 访问
istio-ingressgateway(这也是一个装有 Envoy 的特殊 Pod)。 -
istio-ingressgateway里的 Envoy 根据Istiod的配置(你写的 VirtualService),把流量转发给你的nginx-demoPod。
-
-
拦截与处理:
-
流量进入
nginx-demoPod 时,被 iptables 劫持,先经过istio-proxy(Envoy),然后再到达 Nginx 进程。 -
对应图中:右下角的
Pod内部结构。
-
总结:这张图告诉我们的“核心秘密”
-
解耦:
Istiod(控制面)只负责下发配置,不直接处理业务流量;Envoy(数据面)只负责转发流量,不关心业务逻辑。它们通过 xDS 协议通信。 -
透明:你的 Nginx 根本不知道自己被“劫持”了,它以为请求是直接发过来的。这就是 Sidecar 模式的魅力——无侵入。
理解了这张图,你就掌握了 Istio 的精髓。接下来如果你要做更复杂的灰度发布或 mTLS 加密,只需要在这个框架下,调整 Istiod下发的配置即可。


这张图展示的是 Istio 流量管理的三大核心资源。结合之前的 Nginx 案例,它们的关系就像是一个“快递分拣中心”的运作流程:
-
Gateway(网关):是大门。决定哪个快递(外部请求)可以进仓库。
-
VirtualService(虚拟服务):是分拣规则。决定快递进来了之后,该往哪个方向送。
-
DestinationRule(目标规则):是配送策略。决定快递送到那个方向后,具体走哪条路(负载均衡)。
下面详细拆解它们的作用和关系:
1. Gateway(网关):守门员
-
作用:为网格内服务提供对外访问入口。它定义了哪些端口和协议对外开放,以及通过什么主机名(域名)访问。
-
实战对应:
-
在你的 Nginx 案例中,
istio-ingressgateway就是一个 Gateway。 -
它负责接收通过 MetalLB VIP 进来的流量。
-
-
关键点:Gateway 本身不关心具体的业务逻辑,只负责“开门”。
2. VirtualService(虚拟服务):导航员
-
作用:定义路由规则。它告诉网格内的流量应该怎么走。
-
实战对应:
-
你写的
nginx-vs.yaml就是 VirtualService。 -
它说:“如果收到的请求路径是
/,就转发给nginx-service。”
-
-
关键点:VirtualService 是连接 Gateway 和后端服务的桥梁。它可以指定多个路由规则,支持灰度发布、金丝雀发布等。
3. DestinationRule(目标规则):调度员
-
作用:定义负载均衡策略和子集。它在流量到达目标服务后,决定如何分发。
-
实战对应:
-
如果你有两个 Nginx 实例(v1 和 v2),你可以创建一个 DestinationRule。
-
它说:“如果有 10% 的流量去 v1,90% 去 v2。” 或者 “按权重分配”。
-
-
关键点:DestinationRule 是在流量已经确定目标服务后,才起作用的。
三者关系的流程图解
graph TD
A[外部用户] -->|请求| B(Gateway)
B -->|开门/过滤| C(VirtualService)
C -->|路由规则| D{DestinationRule}
D -->|负载均衡| E[后端服务 Pods]
举例说明
假设你有一个 Nginx 服务,有两个版本:nginx-v1和 nginx-v2。
-
Gateway:
-
开放 80 端口,域名是
example.com。
-
-
VirtualService:
-
如果 Host 是
example.com,且 Path 是/,则路由到nginx-service。
-
-
DestinationRule:
-
定义
nginx-service有两个子集:v1和v2。 -
设置负载均衡策略:90% 流量去
v1,10% 流量去v2。
-
总结
-
Gateway 是入口,VirtualService 是路由,DestinationRule 是负载均衡。
-
三者协同工作,构成了 Istio 强大的流量管理能力。
-
在实际使用中,通常先配置 Gateway,再配置 VirtualService,最后根据需要配置 DestinationRule。
希望这个解释能帮你理清它们的关系!如果有更多问题,随时问我。

太棒了!你已经完全抓住了 Istio 流量管理的核心逻辑:Gateway 管“进门”,VirtualService 管“分拣”,DestinationRule 管“配送”。
为了让你彻底吃透这个流程,我为你设计了一个基于 Nginx 的 90%:10% 灰度发布(金丝雀发布)实战案例。
这个案例将完全复刻你描述的场景:外部流量通过 Gateway 进入,由 VirtualService 决定“90% 走 v1,10% 走 v2”,最后由 DestinationRule 精准地把流量推送到对应的 Nginx 实例上。
前置假设(环境准备)
在开始之前,请确保你的集群里已经:
-
安装了 Istio(并且自动注入了 Sidecar)。
-
安装了 MetalLB(或者有任何 LoadBalancer 方式),并且
istio-ingressgateway已经拿到了一个 VIP(假设为192.168.171.240)。
第一阶段:部署双版本业务(造包裹)
首先,我们需要创建两个不同的 Nginx 版本 Deployment。关键点在于:我们要给它们的 Pod 打上不同的标签 version: v1和 version: v2。
创建一个名为 nginx-deployments.yaml的文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v1
template:
metadata:
labels:
app: nginx
version: v1 # 关键标签:v1版本
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
# 为了方便区分,我们在首页加点文字
command: ["sh", "-c"]
args: ["echo '<h1>Hello Nginx v1</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v2
template:
metadata:
labels:
app: nginx
version: v2 # 关键标签:v2版本
spec:
containers:
- name: nginx
image: nginx:1.23
ports:
- containerPort: 80
command: ["sh", "-c"]
args: ["echo '<h1>Hello Nginx v2 (Canary)</h1>' > /usr/share/nginx/html/index.html && nginx -g 'daemon off;'"]
---
# 统一的业务 Service,它是 VirtualService 的目标
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx # 注意:这里只选 app: nginx,所以它能选中 v1 和 v2 两个Deployment的Pod
ports:
- protocol: TCP
port: 80
targetPort: 80
应用配置:
kubectl apply -f nginx-deployments.yaml
第二阶段:Gateway(守门员 - 开门)
Gateway 定义了谁可以进来。我们开放 80 端口,并允许所有域名(*)访问。
创建 nginx-gateway.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: nginx-gateway
spec:
selector:
istio: ingressgateway # 使用默认的 istio-ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*" # 允许所有域名,或者你可以写成 "example.com"
说明:
-
这相当于在仓库门口立了个牌子:“80端口,啥域名都行,进来吧!”
应用配置:
kubectl apply -f nginx-gateway.yaml
第三阶段:DestinationRule(调度员 - 分货到仓库货架)
DestinationRule 定义了目标服务的“子集(Subsets)”。这是实现灰度发布最关键的一步,因为它告诉 Istio:“v1 版本的 Pod 长什么样(label),v2 版本的 Pod 长什么样”。
创建 nginx-destination-rule.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: nginx-destination-rule
spec:
host: nginx-service # 目标 K8s Service 名称
subsets:
- name: v1 # 定义一个子集名字叫 v1
labels:
version: v1 # 匹配带有 label version: v1 的 Pod
- name: v2 # 定义一个子集名字叫 v2
labels:
version: v2 # 匹配带有 label version: v2 的 Pod
说明:
-
这里的
subsets就像是给仓库里的货物贴上了“货架号”。Envoy 代理必须知道 v1 在哪,v2 在哪,才能去执行后续的指令。
应用配置:
kubectl apply -f nginx-destination-rule.yaml
第四阶段:VirtualService(导航员 - 按规则分拣)
最后,VirtualService 出场。它引用上面定义的 Gateway 和 DestinationRule 的子集,按照 权重(Weight) 进行分流。
创建 nginx-virtual-service.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx-virtual-service
spec:
hosts:
- "*" # 应用到所有主机(要和 Gateway 保持一致)
gateways:
- nginx-gateway # 绑定到我们刚才创建的 Gateway
http:
- route:
- destination:
host: nginx-service
subset: v1 # 指向 DestinationRule 里定义的 v1 子集
weight: 90 # 90% 的流量去 v1
- destination:
host: nginx-service
subset: v2 # 指向 DestinationRule 里定义的 v2 子集
weight: 10 # 10% 的流量去 v2
说明:
-
这是真正的“分拣规则”。它说:“看好了,进来的包裹,90% 扔给 v1 货架,10% 扔给 v2 货架。”
应用配置:
kubectl apply -f nginx-virtual-service.yaml
验证与测试(看看快递到底去哪了)
1. 获取入口 IP (VIP)
kubectl get svc istio-ingressgateway -n istio-system
# 找到 EXTERNAL-IP,假设是 192.168.171.240
2. 连续访问测试
打开终端,连续执行 curl命令 20 次:
for i in {1..20}; do curl http://192.168.171.240; echo ""; done
预期结果:
你会看到大约 18 次返回 <h1>Hello Nginx v1</h1>,大约 2 次返回 <h1>Hello Nginx v2 (Canary)</h1>。
3. 查看日志确认
-
看 v1 Pod 日志:
kubectl logs deploy/nginx-v1 -c nginx(会有大量的访问记录)
-
看 v2 Pod 日志:
kubectl logs deploy/nginx-v2 -c nginx(只有少量的访问记录,大约占总数的 10%)
总结:全链路流程图解
让我们把之前的“快递中心”比喻和现在的配置对应起来:
graph TD
User[外部用户] -->|访问 VIP:80| GW(Gateway: nginx-gateway)
GW -->|开门/放行| VS(VirtualService: nginx-virtual-service)
VS -->|规则: 90%去v1, 10%去v2| DR(DestinationRule: nginx-destination-rule)
subgraph "K8s 集群内部"
direction LR
DR -->|Subset v1| PodV1(Nginx v1 Pod)
DR -->|Subset v2| PodV2(Nginx v2 Pod)
end
-
Gateway 负责暴露端口。
-
VirtualService 负责路由决策(加权、匹配 Header 等)。
-
DestinationRule 负责定义目标群体(Subsets)。
这三个资源必须像齿轮一样咬合在一起,才能完成复杂的灰度发布。你现在不仅有了理论,还有了一套可以跑通的实战代码!
要实现“只有特定 Header 的请求才能访问 v2 版本”,我们需要利用 VirtualService中的 match 字段。
这是灰度发布中非常高级且实用的功能(通常称为“金丝雀发布”或“条件路由”)。
修改思路
-
match条件:在http块下添加match规则。指定只有当请求头(Headers)中包含特定键值对时,才触发这条路由规则。 -
优先级:Istio 的路由规则是顺序执行的。一旦某个请求匹配了
match条件,就会执行对应的路由,剩余的路由规则将被忽略。
为了实现“只有带 Header 的进 v2,其他的进 v1”,我们可以采用两种写法:
-
写法 A(推荐,更清晰):第一条规则检查 Header,命中则去 v2;第二条规则(无 match)作为兜底,去 v1。
-
写法 B(利用权重):在原有的权重基础上叠加匹配条件。
下面我提供 写法 A 的完整配置,因为它逻辑最严密,不易出错。
修改后的 nginx-virtual-service.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx-virtual-service
spec:
hosts:
- "*"
gateways:
- nginx-gateway
http:
# === 规则 1:特定 Header 的请求,100% 走 v2 ===
- match:
- headers:
x-canary: # Header 的 Key
exact: "true" # Header 的 Value(严格匹配)
# 也可以用 prefix: "true" 或 regex: "t.*" 等进行模糊匹配
route:
- destination:
host: nginx-service
subset: v2
weight: 100 # 这里必须是 100,确保命中的请求全部去往 v2
# === 规则 2:所有其他请求(兜底),走 v1 ===
- route:
- destination:
host: nginx-service
subset: v1
weight: 100
详细配置说明
1. match字段详解
match:
- headers:
x-canary:
exact: "true"
-
headers: 表示我们要检查 HTTP 请求头。 -
x-canary: 这是我们自定义的 Header Key。你可以随便起名,比如version,x-user-id等。 -
exact: 表示完全匹配。即请求头必须精确等于x-canary: true。-
如果需要更复杂的条件,可以用
regex(正则)或prefix(前缀)。
-
2. 执行逻辑
-
场景 1:用户发送请求
curl -H "x-canary: true" http://<VIP>-
Istio 检查到 Header 符合
match条件。 -
结果:强制转发给
subset: v2(Nginx v2)。
-
-
场景 2:用户发送普通请求
curl http://<VIP>-
Istio 检查不到
x-canary: trueHeader,跳过第一条规则。 -
Istio 继续往下走,匹配到第二条兜底规则。
-
结果:转发给
subset: v1(Nginx v1)。
-
实战测试步骤
第一步:应用新配置
kubectl apply -f nginx-virtual-service.yaml
第二步:测试普通用户(应该看到 v1)
curl http://<你的VIP地址>
# 预期输出:<h1>Hello Nginx v1</h1>
第三步:测试灰度用户(应该看到 v2)
curl -H "x-canary:true" http://<你的VIP地址>
# 预期输出:<h1>Hello Nginx v2 (Canary)</h1>
进阶玩法
如果你想做A/B 测试(比如让带有特定 ID 的用户去 v2),只需修改 match条件:
- match:
- headers:
user-id: # 假设前端会带上 user-id
regex: "^123.*" # 正则匹配:以 123 开头的用户
route:
- destination:
host: nginx-service
subset: v2
这样,只有 user-id是 123 开头的请求才会看到新版页面。
这是个非常好的生产级问题,但必须先把一个常见误区拆开:
Istio 能做到“把 v2 里不健康实例踢出去(熔断/离群驱逐)”,但做不到“检测 v2 整体错误率 >10% 后,自动把 VirtualService 的流量权重从 v2 切回 v1”。
后者需要一个 Metrics → 判断 → 自动变更(kubectl/pipeline/控制器) 的闭环。
下面给你一套可落地的做法:先让 v2“异常时自己熔断(保护系统)”,再把“错误率超阈值 → 自动回退权重”用自动化脚本/流水线补齐。
1)Istio 侧:给 v2(或 nginx-service)配 OutlierDetection(实例级熔断/驱逐)
在 DestinationRule 里加 outlierDetection,作用是:
-
对 subset v2 的后端实例做连续错误/高延迟统计
-
一旦满足“ ejection 条件”,就把那个 Pod 实例短期踢出负载均衡(不是删 Pod,只是 Envoy 不转给它)
-
过
baseEjectionTime后尝试放回(可多次,maxEjectionPercent 控影响面)
推荐的 DR 配置(你可直接替换/合并)
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: nginx-destination-rule
spec:
host: nginx-service
trafficPolicy:
# 连接级熔断(可选但很建议,防雪崩)
connectionPool:
tcp:
maxConnections: 50
http:
http1MaxPendingRequests: 20
maxRequestsPerConnection: 10
# 可选:per-try 超时(让“慢失败”更快失败)
perTryTimeout: 2s
# 离群实例驱逐(你问的“自动熔断”主要在这里)
outlierDetection:
consecutive5xxErrors: 5 # 连续 5xx 达到 5 次就标记 ejections
interval: 10s # 统计窗口
baseEjectionTime: 30s # 最短踢出时间(会按倍数增长)
maxEjectionPercent: 50 # 最多踢掉 v2 实例的 50%(防全灭)
minHealthPercent: 50 # 低于这个健康比例,干脆停止驱逐(保底可用)
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
# 也可以只给 v2 更激进的熔断(常见)
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50
minHealthPercent: 50
这段话怎么理解(白话)
-
v2 里某几个 Pod 在发疯(一直 5xx) → Envoy 自动把它们隔离 → 剩余健康 v2 继续扛
-
v2 大部分都挂了(健康比跌破
minHealthPercent)→ Istio 会保守一点,避免把流量全堵死 -
但它不会自动把 VS 的
weight: 10改回weight: 0;那是“路由策略回退”,不在 outlierDetection 职责内
✅ 所以这能解决:v2 异常时别拖垮整个 v2 子集(实例级自愈)
❌ 但不能解决:“v2 整体错误率超 10%,就回退到 v1”(那是上层编排)
2)真正“按错误率自动回退权重”:需要 Metrics + 自动执行器(推荐架构)
最简可落地的闭环长这样
Prometheus 采集 Istio/Envoy 指标
→ 计算:v2 错误率 = 5xx(v2) / 总(v2)
→ 超阈值(>10%)→ Alertmanager / 定时脚本 / GitOps 控制器
→ 执行回退:把 VirtualService 权重改成 v2=0, v1=100
2.1 你用来算错误率的关键指标(Istio 默认就有)
最常用(基于 destination_service/ destination_version或 subset):
-
请求总数(可按 subset 分):
sum(rate(istio_requests_total{ destination_service_name="nginx-service", destination_service_namespace="default" }[1m])) -
5xx 数:
sum(rate(istio_requests_total{ destination_service_name="nginx-service", destination_service_namespace="default", response_code=~"5.*" }[1m])) -
错误率 PromQL(示例,建议按 subset 分组看 v2):
( sum(rate(istio_requests_total{ destination_service_name="nginx-service", destination_service_namespace="default", destination_subset="v2", response_code=~"5.*" }[1m])) / clamp_min( sum(rate(istio_requests_total{ destination_service_name="nginx-service", destination_service_namespace="default", destination_subset="v2" }[1m])) , 1e-9) ) > 0.10当这个表达式持续为真 N 分钟 → 触发“回退动作”。
3)给你一条“马上能用”的半自动回退脚本(最务实)
你可以先不搭复杂控制器,用 cron/定时任务/CI step 跑一个小脚本,检测到异常就自动 patch VS。
3.1 先把 VS 改成“可快速回退”的写法(带 weight)
# nginx-virtual-service.yaml(当前是金丝雀权重 90/10)
spec:
http:
- route:
- destination: { host: nginx-service, subset: v1 }
weight: 90
- destination: { host: nginx-service, subset: v2 }
weight: 10
3.2 一个回退脚本:rollback-to-v1.sh
#!/usr/bin/env bash
# 用法:bash rollback-to-v1.sh
# 前提:kubectl 有权限 patch resources;Prometheus 可访问
PROM_URL="http://prometheus.istio-system.svc:9090" # ← 改成你的 Prometheus 地址
QUERY='(sum(rate(istio_requests_total{destination_service_name="nginx-service",destination_service_namespace="default",destination_subset="v2",response_code=~"5.*"}[1m])) / clamp_min(sum(rate(istio_requests_total{destination_service_name="nginx-service",destination_service_namespace="default",destination_subset="v2"}[1m])),1e-9))'
THRESHOLD=0.10
VAL=$(curl -s --data-urlencode "query=$QUERY" "$PROM_URL/api/v1/query" \
| python3 -c "
import sys,json; d=json.load(sys.stdin)
vs=d.get('data',{}).get('result',[])
if not vs: exit()
print(vs[0]['value'][1])
")
echo "[info] v2 error-rate estimate = $VAL"
python3 - <<PYEOF
import sys
val="$VAL"
if val in ("None","null","","nan"):
sys.exit(0)
try:
v=float(val)
if v > $THRESHOLD:
print("ALERT: error-rate %.2f%% > $(THRESHOLD*100)%%, rollback to v1 only")
exit(1)
else:
print("OK: error-rate within threshold")
except:
pass
PYEOF
if [ $? -eq 1 ]; then
echo "[action] patching VirtualService -> v1=100,v2=0"
kubectl patch vs nginx-virtual-service -n default --type merge -p '
spec:
http:
- route:
- destination: { host: nginx-service, subset: v1 }
weight: 100
- destination: { host: nginx-service, subset: v2 }
weight: 0
'
fi
这只是“可运行的最小闭环”。生产里通常会把 patch VS 改成:提交到 Git → ArgoCD/Flux 同步(更审计友好)。
4)你该怎么给用户/领导总结这句话(很加分)
“Istio 的 OutlierDetection 负责 v2 内部实例级熔断(坏实例短期隔离,保护调用方);
而 v2 整体错误率超阈值自动回退路由权重,我们通过 Prometheus 指标 + 自动执行器 完成,避免依赖单一流量层做策略型回滚。”
如果你愿意,你把这两条打出来:
kubectl get vs nginx-virtual-service -o yaml
kubectl get dr nginx-destination-rule -o yaml
我可以把 OutlierDetection 参数按你现在的 v1/v2 副本数给你“算一组更稳妥值”,并帮你把 PromQL 的 subset/source_labels对准你集群里实际产生的指标名(不同 Istio 小版本字段略有差异)。
验证 90/10 权重分配的完整观测方案
下面按从"最快能看到"到"最严谨可量化"递进,全是基于你现有 Istio 1.9.6 环境,不需要额外装东西(最多启用自带组件)。
一、最快路径:Kiali 直接看流量百分比(推荐首选)
Kiali 是 Istio 官方标配的服务网格可视化 UI,Graph 视图里直接显示每条边的请求占比。
1. 打开 Kiali
# 用 istioctl 内置端口转发(最简单,不需要 expose)
istioctl dashboard kiali
浏览器会自动弹出:http://localhost:20001
2. 看 Graph
在 Kiali 左侧菜单 → Graph → 选择命名空间(比如 default)→ 右上角刷新间隔选 30s→ Display 下拉勾选:
|
选项 |
作用 |
|---|---|
|
✅ Traffic Animation |
实时流动小箭头 |
|
✅ Response Time |
边上看延迟 |
|
✅ Throughput |
边上显示 req/s |
|
✅ Requests % |
直接显示 % 分布(关键) |
你会看到类似拓扑:
istio-ingressgateway ──90%──→ nginx-service (v1)
└──10%──→ nginx-service (v2)
每个箭头(edge)上会标注
v1: 89.7% / v2: 10.3%这种实时值,完全对应你的权重分配。
3. 确认你在看对的东西
Kiali 默认聚合维度可能是 Service 级别。要看到 subset(v1/v2)拆分,顶部有个 Display → Show → 选 "Service Nodes" 或 "Workload Nodes",并在 Edge Labels 里把 Requests percentage打开。
如果你看到 v1 和 v2 两个独立节点从 gateway 连出来,且百分比稳定在 9:1 附近,就证明权重生效了。
二、用 Prometheus 指标精确算(最严谨,可写进报告)
Istio 的每个 Envoy 都会暴露指标,其中 istio_requests_total 就是算权重的黄金指标。
1. 打开 Prometheus
istioctl dashboard prometheus
浏览器打开:http://localhost:9090
2. 核心 PromQL:按 subset 算请求数
看 v1 和 v2 各自的总请求数(按 1 分钟速率)
sum by (destination_subset) (
rate(istio_requests_total{
destination_service_name="nginx-service",
destination_service_namespace="default",
reporter="destination"
}[1m])
)
预期输出类似:
|
destination_subset |
value |
|---|---|
|
v1 |
8.91 |
|
v2 |
0.98 |
比值 ≈ 9:1 ✅
直接算百分比(一条表达式看完)
round(
sum(rate(istio_requests_total{
destination_service_name="nginx-service",
destination_service_namespace="default",
reporter="destination",
destination_subset=~"v1|v2"
}[1m])) by (destination_subset)
/
clamp_min(
sum(rate(istio_requests_total{
destination_service_name="nginx-service",
destination_service_namespace="default",
reporter="destination",
destination_subset=~"v1|v2"
}[1m])),
1e-9
) * 100
, 1)
结果会直接告诉你 v1=90%, v2=10%(浮点波动 ±3% 完全正常)。
3. 按响应码拆开看(排除 5xx 干扰)
sum by (destination_subset, response_code) (
rate(istio_requests_total{
destination_service_name="nginx-service",
destination_service_namespace="default",
reporter="destination"
}[1m])
)
如果 v2 返回大量 5xx 但你 VS 还是 90/10,说明权重分配本身是对的,但 v2 在"错误熔断"那层——这时候就要回到上一轮讲的 outlierDetection和错误率告警了。
三、Grafana 看现成面板(如果你启用了)
有些部署方式会自带 Grafana:
istioctl dashboard grafana
进 Istio Mesh Dashboard / Service Dashboard → 找 nginx-service→ 看 Inbound Services 里按 subset 分的流量堆叠图。
但如果你的环境没预装 Grafana 也没关系——Prometheus 的 Graph 页 + Kiali 的 Graph 页已经足够验证了。
四、一个超实用的"打流量 + 实时看"脚本
把下面保存成 smoke-test.sh,跑起来后切回 Kiali/Prometheus 看曲线:
#!/usr/bin/env bash
VIP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
PORT=80
[ -z "$VIP" ] && { echo "VIP not ready"; exit 1; }
echo "==> Hitting http://$VIP:$PORT (Ctrl+C to stop)"
COUNT=0
while true; do
curl -s -o /dev/null -w "%{http_code}\n" http://$VIP:$PORT
COUNT=$((COUNT+1))
[ $((COUNT % 20)) -eq 0 ] && sleep 0.5 # 稍微喘口气
done
然后在 Kiali Graph 里你肉眼就能看到箭头粗细按 9:1 的比例稳定下来。
五、你现在应该看到的"健康基线"长什么样
|
观察点 |
权重 90/10 正常表现 |
|---|---|
|
Kiali Edge % |
v1≈88–93%, v2≈7–12%(波动是正常的,权重是统计意义不是精确每10个必切1个) |
|
Prometheus 比值 |
|
|
单个请求级 |
你 curl 里随机看到 v2(Canary)页面 ≈ 每10次闪现1次 |
|
v2 5xx 上升时 |
OutlierDetection 会把坏实例踢出 → v2 的有效流量下降但不是权重自动归零 |
快速定位 Checklist(如果 Kiali 看不到 subset 拆分)
# 1. 确认 sidecar 注入成功(2/2)
kubectl get pods -l app=nginx -o wide
# 2. 确认 DR 的 subsets 存在
kubectl get dr nginx-destination-rule -o yaml | grep -A3 subsets
# 3. 确认 VS 引用了 subset(不是裸 host)
kubectl get vs nginx-virtual-service -o yaml | grep -A5 destination
# 4. 看原始指标里有没有 subset label
kubectl exec -n istio-system deploy/prometheus-server -- \
promtool query instant 'up' 2>/dev/null || \
echo "直接用浏览器开的 Prometheus → Status → Targets 看 istio-telemetry"
一句话总结:想"验证 90/10"——Kiali 看拓扑百分比最快,Prometheus 算 istio_requests_total按 destination_subset分组最硬。 如果你把 Kiali 截图里 Graph 那一坨节点(特别是 nginx-service 有没有分出 v1/v2 两个叉)描述一下,我可以告诉你点哪个 Display 开关能把百分比精确亮出来。
[](@replace=0)
自己部署的 Prometheus 采集 Istio 指标:完整实操手册
核心原理:Istio 的所有指标都暴露在 Pod 的固定端口上,Prometheus 通过 K8s 服务发现 找到这些 Pod,然后抓取
/stats/prometheus或/metrics端点。
一、先确认 Istio 指标暴露在哪里(端口/路径)
在 Istio 1.9.x 里,关键端口是:
|
组件 |
端口 |
路径 |
端口名 |
|---|---|---|---|
|
istio-proxy (sidecar/gateway) |
|
|
|
|
istiod (pilot) |
|
|
— |
|
ztunnel (你 1.9 没有) |
— |
— |
— |
快速验证(任选一个注入了 sidecar 的 Pod):
# 看 istio-proxy 的指标端口是否可达(从 Pod 内)
kubectl exec deploy/nginx-v1 -c istio-proxy -- curl -s 127.0.0.1:15090/stats/prometheus | head -20
能看到一大段 # TYPE envoy_...就说明指标活着。
二、让你“自己部署的 Prometheus”能看见 K8s(RBAC 先做)
假设你这台 Prometheus 也是跑在 K8s 里的(最常见)。
2.1 给 Prometheus 一个能 list/watch Pods/Services 的 ServiceAccount
# prom-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: monitoring # ← 改成你 Prometheus 所在 ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources: [pods, services, endpoints, namespaces]
verbs: [get, list, watch]
- apiGroups: ["extensions", "apps"]
resources: [deployments, replicasets]
verbs: [get, list, watch]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
kubectl apply -f prom-rbac.yaml
然后把 Prometheus Deployment/Pod 的 ServiceAccount 指过去:
spec:
template:
spec:
serviceAccountName: prometheus # ← 关键
三、Prometheus 抓取配置(核心:两段 scrape)
打开你自己的 prometheus.yml(或 ConfigMap),在 scrape_configs:里追加——
3.1 抓 istio-proxy(sidecar + ingress-gateway)★ 最重要
scrape_configs:
# ---------- Istio Proxy (envoy sidecar / gateway) ----------
- job_name: 'istio-proxy'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# ① 只抓容器名叫 istio-proxy 的 Pod
- source_labels: [__meta_kubernetes_pod_container_name]
action: keep
regex: istio-proxy
# ② 用 pod_ip:15090 拼出 __address__
- source_labels: [__meta_kubernetes_pod_ip]
target_label: __address__
replacement: '${1}:15090'
# 更严谨写法(如果上面替换语法被你 yml 引擎吃掉,用 action replace):
# 你也可以用下面这种等价写法:
- source_labels: [__meta_kubernetes_pod_ip]
regex: (.+)
target_label: __address__
replacement: '${1}:15090'
# ③ 指标路径
- source_labels: []
target_label: __metrics_path__
replacement: /stats/prometheus
# ④ 顺手把 ns/app/pod 等标签写进时序(方便 PromQL 用)
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
说明:这里完全不依赖
prometheus.io/scrape注解,靠的是 “容器名 = istio-proxy” 这个 Istio 一定会打的标识,最稳。
3.2 抓 istiod(控制面,可选但推荐)
# ---------- Istiod (pilot) ----------
- job_name: 'istio-pilot'
kubernetes_sd_configs:
- role: pod
namespaces:
names: [istio-system]
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: istiod
- source_labels: [__meta_kubernetes_pod_ip]
regex: (.+)
target_label: __address__
replacement: '${1}:15014'
- source_labels: []
target_label: __metrics_path__
replacement: /metrics
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
四、重启 Prometheus 让配置生效
# 如果你是用 Deployment 跑的
kubectl rollout restart deployment prometheus -n monitoring
# 等 Ready
kubectl get pods -n monitoring -l app=prometheus -w
五、验证:Prometheus 里能见到 istio 指标
进你自己的 Prometheus UI → Status → Targets
你应该看到:
istio-proxy https://10.244.x.x:15090/stats/prometheus UP
istio-pilot https://10.244.x.x:15014/metrics UP
然后切到 Graph 页,输:
istio_requests_total
能看到 destination_service=nginx-service/ subset=v1|v2这类时序 → ✅ 采集成功。
六、如果你发现 Targets 一直 DOWN:最常见 3 个坑
坑1:15090在本机 127.0.0.1 上,Pod IP 访问不到
Istio 1.9 默认 sidecar 指标监听 127.0.0.1(安全考量),所以 podIP:15090 会拒绝。
解法(测试/内网可信环境最快):让 envoy 指标监听 0.0.0.0
kubectl edit cm istio -n istio-system
找 meshConfig:/ defaultConfig:加一行(yaml 注意缩进):
meshConfig:
defaultConfig:
proxyStatsBindHost: 0.0.0.0
然后重启所有 sidecar 让它生效:
kubectl rollout restart deployment nginx-v1
kubectl rollout restart deployment nginx-v2
kubectl rollout restart deployment istio-ingressgateway -n istio-system
再验证:
kubectl exec deploy/nginx-v1 -c istio-proxy -- ss -lnt | grep 15090
# 应该看到 0.0.0.0:15090 或 :::15090
⚠️ 生产提示:如果你是跨 AZ/不可信二层,别用 0.0.0.0,改用网络策略锁 15090 只允许 Prometheus IP 段。
坑2:Prometheus SA 没权限 → Targets 页报 401/403
确认第 二 步的 SA + ClusterRoleBinding 真绑定到 Prometheus 的 Pod(很多情况是 yml 写了 serviceAccountName但用的还是 default)。
坑3:你 Prometheus 不在 K8s 里(裸机/VM)
那就用 Node IP:NodePort 或 Ingress 暴露指标很麻烦,更推荐:在集群里放一个“中转 exporter”或用 kubectl port-forward临时,但长期方案是把 Prometheus 也放进集群。
七、你现在就能跑的验证命令(闭环)
# 1) Targets 必须 UP
# Prometheus UI → Status → Targets → istio-proxy 全绿
# 2) 按 subset 看请求数(证明采集对了标签)
sum by (destination_subset) (
rate(istio_requests_total{
destination_service_name="nginx-service",
destination_service_namespace="default"
}[1m])
)
只要这条 PromQL 能出 v1 / v2两条线,你的 Prometheus ↔ Istio 管线就完全通了,后面 90/10、错误率、熔断全部可量化。
如果你把两点信息发我,我可以帮你把 replacement: '${1}:15090'那段写成你 Prometheus 版本最兼容的格式(不同版本对 ${1}插值支持不一致):
-
你 Prometheus 是 Deployment/StatefulSet 跑在 K8s,还是 VM 外置?
-
执行:
kubectl get cm istio -n istio-system -o yaml | grep -A2 proxyStatsBindHost

这张图对 Istio 中的 Gateway(网关) 概念讲解得非常清晰,特别是它把复杂的流量入口逻辑简化成了几个核心模块。
结合你之前做的 Nginx 实验,我帮你梳理一下这张图里的关键点,以及它和你实战的关系:
1. 核心角色区分:Ingress vs. Egress
图中明确指出了两种网关,它们的分工不同:
-
IngressGateway(入口网关)
-
作用:这是集群的“大门”,负责接客。外部流量(如图中的“User”或“Browser”)必须通过它才能进入网格。
-
实战对应:你之前配置的
istio-ingressgateway就是它。所有的外部用户访问你的 Nginx 服务,都得先敲开这扇门。
-
-
EgressGateway(出口网关)
-
作用:这是集群的“出口”,负责出去。当网格内的服务需要访问外部服务(如访问外部的数据库、第三方 API)时,可以通过它管控流出流量。
-
实战对应:你之前的 Nginx 案例没用到这个,但在企业级应用中很重要,比如用来监控服务调用了哪些外部接口,或者防止敏感数据外泄。
-
2. 为什么要用 Istio Gateway 而不是 K8s Ingress?
图中特意花篇幅对比了 Gateway 和 K8s Ingress 的区别,这也是面试常考点:
-
K8s Ingress:更像是一个简单的路由器,主要管“域名+路径”转发到不同的 Service。功能相对单一。
-
Istio Gateway:不仅包含了 Ingress 的功能(L4/L7 负载均衡),还扩展了微服务治理的能力。
-
图中提到的关键点:支持流量镜像(把流量复制一份去测试环境)、熔断(防止雪崩)、以及安全协议(HTTPS/mTLS)。
-
3. 关于 mTLS 的安全机制
图底部解释了 HTTPS 和 mTLS 的区别,这对于理解服务间安全通信很重要:
-
HTTPS(单向认证):只认证服务端。就像你访问银行网站,你确信网站是真的,但银行不关心你是谁。
-
mTLS(双向认证):客户端和服务端互相认证。在微服务架构中,这叫“零信任”,即服务 A 调用服务 B 时,双方都要出示“身份证”(证书),确保对方是自己人,防止恶意服务混入。
总结
这张图其实就是在告诉你:Istio 的 Gateway 不仅仅是用来“收流量”的,它是一个集成了高级路由、安全认证(mTLS)、流量控制(镜像/熔断)的智能大门。
你之前写的 Gateway和 VirtualServiceYAML 文件,本质上就是在 Kubernetes 里把这个“大门”建起来,并给它定下“分拣快递”的规则。

这是一个“服务访问外部数据库”的经典案例。通过这个案例,你会明白 EgressGateway 到底解决了什么问题。
### 场景设定
假设你有一个运行在 Istio 网格内的 Order Service(订单服务),它需要访问公司外部的第三方支付平台(比如 `pay.alipay.com`)。
---
### 一、没有 EgressGateway 时的问题(痛点)
在没有 EgressGateway 的情况下,Order Service 直接访问外网:
```
Order Service Pod (Sidecar: istio-proxy)
↓
直接访问 pay.alipay.com
```
会出现三个致命问题:
1. 看不见(黑盒):
◦ 网格管理员不知道 Order 服务到底访问了哪些外部网站。如果有恶意程序偷偷把数据传到境外,你完全不知道。
2. 拦不住(失控):
◦ 你无法在网格层面禁止 Order 服务访问除了支付平台以外的其他网站(比如 `github.com` 或 `baidu.com`)。
3. 不安全(明文):
◦ Order 服务和外部支付平台之间通常是 HTTPS(单向认证)。如果你要求双向认证(mTLS),Sidecar 无法直接处理这种复杂的外部 TLS 握手。
---
### 二、引入 EgressGateway(解决方案)
我们创建一个 EgressGateway,强制所有外部流量必须先经过这个“出口安检口”。
#### 1. 架构图(核心逻辑)
```
Order Service Pod
↓
Sidecar (istio-proxy)
↓
EgressGateway (专用出口 Pod)
↓
pay.alipay.com (外部世界)
```
关键变化:
• Order 服务不再知道外网长什么样,它只知道“把请求发给 EgressGateway”。
• EgressGateway 负责处理所有复杂的外部通信(TLS 证书、SNI 路由等)。
---
### 三、实战配置(一步步来)
#### 1. 部署 EgressGateway(特殊 Deployment)
EgressGateway 也是一个 Istio 组件,但它是专门为“出站”设计的。
```yaml
# egress-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: egress-gateway
namespace: istio-system
spec:
selector:
istio: egressgateway # 使用 Istio 自带的 egressgateway 组件
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- pay.alipay.com # 只允许访问这个外部域名
tls:
mode: PASSTHROUGH # 透传 TLS,让 EgressGateway 只负责路由,不解密内容(安全合规)
```
#### 2. 配置路由规则(VirtualService for Egress)
告诉 Sidecar:“凡是访问 `pay.alipay.com` 的流量,统统送去 EgressGateway。”
```yaml
# egress-vs.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: direct-egress-through-egressgateway
spec:
hosts:
- pay.alipay.com # 匹配外部域名
gateways:
- mesh # 应用于网格内的所有 Sidecar
- egress-gateway # 也应用于 EgressGateway 本身
http:
- match:
- gateways:
- mesh # 来自网格内部的请求
port: 443
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
port:
number: 443
- match:
- gateways:
- egress-gateway # 来自 EgressGateway 的请求
port: 443
route:
- destination:
host: pay.alipay.com
port:
number: 443
```
#### 3. 开启双向认证(mTLS,Egress 的杀手锏)
这是 EgressGateway 最厉害的地方。你可以强制 Order 服务与 EgressGateway 之间使用 mTLS 通信,确保内部流量不被窃听。
```yaml
# egress-dr.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: egressgateway-mtls
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL # 开启 Istio 自动的双向认证
```
---
### 四、验证效果(你看到了什么)
1. 流量管控:
现在 Order 服务想访问 `github.com` 会失败,因为 EgressGateway 的 `hosts` 列表里只允许 `pay.alipay.com`。
2. 安全审计:
你可以在 Kiali 或 Prometheus 里清晰地看到:
◦ Source: Order Service
◦ Destination: EgressGateway
◦ External: pay.alipay.com
◦ 这形成了一条完整的审计链条。
3. TLS 分层:
◦ 内部(Pod ↔ EgressGateway):使用 Istio 的 mTLS(加密且认证)。
◦ 外部(EgressGateway ↔ 支付平台):使用标准的 HTTPS(互联网标准)。
---
### 总结:EgressGateway 到底干了什么?
| 维度 | 没有 EgressGateway | 有 EgressGateway |
| :--- | :--- | :--- |
| 可见性 | 黑洞,不知道服务去了哪 | 白盒,所有出口流量经过安检 |
| 安全性 | 明文或单向 HTTPS | 双向 mTLS(内部)+ 外部 HTTPS |
| 合规性 | 无法阻止数据泄露 | 可以强制禁止访问违规网站 |
| 运维 | 每个 Pod 自己管证书 | 集中管理出口证书和策略 |
一句话记住:
IngressGateway 是“看门的”(防坏人进来),EgressGateway 是“门禁卡”(管好人出去能干嘛)。

这张图讲解的是 ServiceEntry(服务入口),它是 Istio 网格化管理的“破壁机”。
结合你之前学习的 EgressGateway,我帮你把这页 PPT 的核心逻辑拆解一下,你就明白它在实际架构里是干什么用的了。
### 1. 核心痛点:微服务“出不去”
在微服务架构中,你的服务(比如订单服务)经常需要调用外部服务(比如支付接口、短信接口)。
• 默认情况:这些外部服务在网格之外(External Services)。
• 问题:Istio 的 Envoy Sidecar 代理只认识网格内的服务。如果不做特殊处理,内部服务发出的请求会绕过 Sidecar,导致 Istio 无法记录日志、无法做流量治理(比如给外部调用加超时重试)。
### 2. 什么是 ServiceEntry?(PPT 核心翻译)
PPT 上的那句定义非常精准:“将网格外部服务添加到网格内,像网格内其他服务一样管理。”
这句话怎么理解?
• “添加到网格内”:并不是真的把外部服务搬进 Kubernetes 集群,而是在 Istio 的控制平面里注册一个“户口”。
• “像网格内一样管理”:一旦注册了这个户口,内部服务去调用它时,必须经过 Sidecar 代理。这样一来,Istio 就能看到流量走向,并能对它应用各种策略。
### 3. 结合 PPT 架构图的实战解读
看图里的那条连线:内部服务 -> ServiceEntry -> 外部服务
这个流程在实际中发生了什么?
1. 注册(黄色方块):你写一段 YAML,告诉 Istio:“虽然 `api.payment.com` 不在我的 K8s 集群里,但我承认它是我的体系一部分,请给我放行并监管。”
2. 引流(蓝色箭头):内部服务想访问 `api.payment.com`。
3. 被劫持(必经之路):因为有了 ServiceEntry,K8s 的 iptables 规则会把流量劫持到本地的 Envoy Sidecar。
4. 出网(绿色箭头):Sidecar 拿到流量后,根据规则把数据包发出去。
### 4. 为什么配合 VirtualService?(PPT 下方虚线框)
PPT 右下角画了一个 VirtualService。
• 作用:有了 ServiceEntry 只是“让流量走 Sidecar”,但如果你想对这段外部流量做更多精细化的控制(比如:把 10% 的流量切到沙箱环境测试、设置全局 3 秒超时、或者做故障注入),就需要用 VirtualService 来定义这些路由规则。
### 5. 总结与类比
为了让你更容易记住,我们用一个生活场景打比方:
• 没有 ServiceEntry:就像你公司(网格)的员工要去外面买咖啡(外部服务)。保安(Sidecar)不认识咖啡店,直接把人拦在门外,员工只能FQ出去(流量绕过网格,无法监控)。
• 有了 ServiceEntry:保安手里拿到了一份“合作商户名单”(ServiceEntry)。员工说去名单上的店,保安就放行并记录时间。如果名单上某家店总是出错,保安还能根据规定(VirtualService)限制员工去那家店的频率。
一句话总结:
ServiceEntry 就是给 Istio 的一张“白名单”,让外部服务也能享受 Istio 的“微服务治理待遇”。
这里以“允许网格内的服务访问外部的天气查询 API”为例,演示如何编写 YAML 配置文件,并列举几个最常见的实战场景。
### 一、 如何设置 ServiceEntry?(实战 YAML)
假设我们要调用外部的一个公共天气 API:`api.weather.com`,端口是 443(HTTPS)。
#### 1. 基础版:仅仅让流量“出得去”
这是最简单的配置,作用是告诉 Istio:“虽然这个服务在网格外,但允许内部服务访问它,并且请帮我拦截流量。”
```yaml
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: external-weather-api # 名字随便起
spec:
hosts:
- api.weather.com # 外部服务的域名
ports:
- number: 443 # 端口
name: https
protocol: HTTPS # 协议
resolution: DNS # 解析方式:通过 DNS 查 IP
```
• `resolution: DNS`:这是最关键的字段。它表示每次发起请求时,Envoy 代理会通过 DNS 查询获取最新的 IP,而不是在启动时死板地绑定一个 IP。
#### 2. 进阶版:像管理内部服务一样治理它
结合之前的 PPT 内容,我们加上 `VirtualService`,给这个外部调用加一个 3 秒超时。
```yaml
# 1. 定义 ServiceEntry (把外部服务纳入网格)
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: weather-api-se
spec:
hosts:
- api.weather.com
ports:
- number: 443
name: https
protocol: HTTPS
resolution: DNS
---
# 2. 定义 VirtualService (施加治理规则)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: weather-api-vs
spec:
hosts:
- api.weather.com # 目标必须是上面的 ServiceEntry
http:
- timeout: 3s # 核心诉求:3秒超时
retries:
attempts: 3 # 核心诉求:失败重试3次
route:
- destination:
host: api.weather.com
```
这样配置后,网格内的服务调用天气 API 时,如果 3 秒没响应,Istio 会自动触发重试或报错,就像调用内部服务一样可控。
---
### 二、 ServiceEntry 的典型使用场景
除了上面提到的“调用外部 API”,ServiceEntry 还有以下几个高频场景:
#### 场景 1:遗留系统迁移(混合云/混合集群)
• 痛点:你有一个老的单体应用(Monolith)跑在物理机上,没有容器化。现在的微服务都在 K8s 网格里。它们需要互相调用。
• 解法:给物理机上的老应用配一个 `ServiceEntry`。
• 效果:网格内的新服务以为老应用也是个普通的 K8s Service,直接通过服务名调用,无需关心底层网络差异。
#### 场景 2:访问云服务(云厂商 SDK)
• 痛点:你的服务需要调用阿里云 OSS、AWS S3 或腾讯云的短信服务。这些 SDK 内部通常会并发建立很多连接。
• 风险:如果不加 ServiceEntry,SDK 可能会绕过 Sidecar,导致监控缺失;或者因为 Sidecar 默认的连接池限制导致报错。
• 解法:添加 ServiceEntry 并配置 `ConnectionPool`(连接池)。
```yaml
spec:
hosts:
- oss-cn-hangzhou.aliyuncs.com
ports:
- number: 443
protocol: HTTPS
resolution: DNS
location: MESH_EXTERNAL
connectionPool:
tcp:
maxConnections: 100 # 限制最大连接数,防止打爆本地资源
```
#### 场景 3:灰度发布中的“金丝雀”外部依赖
• 痛点:你想测试一个新的外部支付接口(v2 版),但不想影响主业务。
• 解法:利用 `VirtualService` 配合 `ServiceEntry` 做流量切分。
```yaml
# 假设 ServiceEntry 里定义了 weather-api.com
http:
- match:
- headers:
x-canary:
exact: "true" # 只有带特定头的请求才走新版
route:
- destination:
host: api-v2.weather.com # 指向新的外部端点
```
*(注:这需要外部接口支持多域名或根据 Host 头路由)*
### 总结
ServiceEntry 就是 Istio 网格的“边界门卫”。 无论是出去上网(API),还是接待访客(遗留系统),都需要它在门口登记造册,这样网格里的“监控摄像头”(Telemetry)才能拍到所有的进出流量。

看来你已经学到了 Istio 最核心的实战技能——服务发布与部署策略。这正是微服务架构中运维和 DevOps 每天打交道最多的场景。
PPT 里首先提到的是 蓝绿发布(Blue-Green Deployment),我来帮你把这个概念拆解清楚,并结合你之前配置的 Nginx 环境,告诉你用 Istio 怎么做。
一、 什么是蓝绿发布?(生活化类比)
想象一下,你开了一家奶茶店,现在要装修升级店面(发布新版本软件):
-
蓝店(Blue):现在的老店面,正在正常营业,接待老客户(线上运行的老版本服务)。
-
绿店(Green):隔壁刚装修好的新店面,准备就绪,万事俱备,只等开业(部署好的新版本服务)。
-
马路(Gateway/LoadBalancer):顾客进店的必经之路。
发布过程(切换流量):
-
准备期:你在隔壁开好了绿店,测试没问题。此时蓝店还在营业。
-
切换:中午 12 点,你把马路的指示牌从“蓝店”改成“绿店”。顾客瞬间全部流向了新店。
-
观察:如果没有客人投诉(系统报错),那就保持现状;如果新店出事了(Bug),你立刻把指示牌改回蓝店,顾客又瞬间回到老店。
这就是蓝绿发布:两套环境随时待命,通过网关一键切换流量。
二、 Istio 如何实现蓝绿发布?(实战配置思路)
结合你之前做的实验,实现蓝绿发布不需要改你的 Nginx 代码,只需要改 Istio 的配置。
假设你有两个 Nginx 部署:nginx-v1(蓝)和 nginx-v2(绿)。
1. 定义两个版本的服务(Deployment)
这里假设你已经有了 nginx-v1和 nginx-v2的 Deployment。
2. 配置 Service(关键点:Selector 要“广撒网”)
你的 Kubernetes Service 不能只指向 v1,要让它能选中所有的 Pod。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx # 只要标签是 nginx 的 pod 都算数,不管它是 v1 还是 v2
ports:
- protocol: TCP
port: 80
targetPort: 80
3. 配置 Gateway(大门还是那个大门)
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: my-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
4. 核心:配置 VirtualService(红绿灯控制器)
这是 PPT 里说的“负载均衡摘除/加入”的魔法发生地。
阶段一:全部流量去蓝店(v1)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-blue-green
spec:
hosts:
- "*"
gateways:
- my-gateway
http:
- route:
- destination:
host: nginx-service
subset: v1 # 100% 流量去 v1
阶段二:一键切到绿店(v2)
只需要修改上面 YAML 中的 subset为 v2,或者使用权重(Weight):
http:
- route:
- destination:
host: nginx-service
subset: v2
weight: 100 # 100% 流量切到 v2
注:配合 DestinationRule 定义 v1和 v2这两个子集(Subset)会更规范。
三、 蓝绿发布的优缺点(PPT 核心提炼)
PPT 里总结得很到位,我帮你补充一下在实际工作中的体感:
优点(为什么用它):
-
极速回滚:这是最大的爽点。一旦新版本(绿店)出问题,你不需要做任何回滚操作(Rollback Code),只需要把网关流量切回老版本(蓝店)即可。回滚速度是秒级的。
-
发布过程简单:逻辑清晰,运维只需要操作一次网关配置。
缺点(代价是什么):
-
资源消耗大(双倍资源):你必须同时启动两套一模一样的环境(蓝+绿)。如果你的微服务内存占用很大(比如 8G),那发布期间集群里相当于有 16G 的资源在被这两套环境占用。
-
状态管理复杂:如果你的应用里有“购物车”这种需要保存用户状态的,切到绿店后,用户的购物车数据如果还在蓝店的内存里,用户就会觉得“我刚才加的商品怎么没了”。所以蓝绿发布最适合无状态服务(Stateless)。
总结
蓝绿发布就像是“双管齐下,瞬间切换”。
在你的 Istio 学习路径中,掌握 VirtualService 的路由权重配置,你就已经掌握了蓝绿发布的精髓。接下来 PPT 里应该会讲到 灰度发布(金丝雀),那是更省资源、更细腻的玩法。

这张图继续讲解了另外两种主流的服务发布策略:滚动发布和灰度发布(金丝雀发布)。
结合你之前学习的 Istio 知识和这张 PPT 的内容,我来帮你把这几种发布策略的区别和应用场景彻底理清楚。
一、 滚动发布 (Rolling Update)
PPT 里对滚动发布的定义是:“先创建出一个新的服务,新的服务正常后,加入生产环境后,再删除旧的服务。”
1. 通俗理解:排队换岗
想象一家餐厅有 10 个服务员(旧版本 Pods)。现在要换新制服(新版本)。
-
滚动发布的做法是:解雇 1 个旧服务员,招 1 个穿新制服的服务员进来。等新人就绪了,再解雇下一个旧的。
-
过程:新旧版本会在一段时间内同时存在,随着时间推移,新版本的比例逐渐增加,直到所有旧版本都被替换。
2. 技术特点(PPT 核心提炼)
-
机制:这是一种渐进式的发布。Kubernetes 默认的策略就是滚动发布。
-
优点:
-
平滑过渡:用户基本无感知,体验好。
-
资源利用率高:不需要像蓝绿发布那样同时准备双倍资源,边滚动边释放旧资源。
-
-
缺点(重点笔记):
-
不易回滚:PPT 里提到“一口气升级完所有”,如果新版本在滚动过程中发现严重 Bug,你无法通过简单的“一键切换”回滚。因为此时部分旧实例已经被销毁了,系统状态已经改变。通常需要重新触发一次发布流程回退到旧镜像。
-
影响范围不可控:在滚动过程中,系统里既有旧版本也有新版本,如果新版本有问题,实际上已经影响了部分用户的请求。
-
二、 灰度发布 / 金丝雀发布 (Canary Release)
PPT 定义:“只升级部分服务,即让一部分用户继续使用老版本,一部分用户开始使用新版本...逐步扩大范围。”
1. 通俗理解:试探水温
“金丝雀”是煤矿工人以前用来检测有毒气体的小鸟。意思是先把新版本给一小撮“小白鼠”用户试用。
2. 技术特点(PPT 核心提炼)
-
机制:这是一种非均匀的流量分配。通过 Istio 的
VirtualService来精确控制流量比例。 -
优点:
-
安全性极高:这是它最大的优势。一旦发现新版本有问题,只需将流量权重切回 0% 即可,回滚极其迅速且无损。
-
可观测性强:可以在真实流量下,对比新老版本的监控数据(如响应时间、错误率)。
-
-
缺点:
-
自动化要求高:需要配合复杂的监控告警系统和自动化脚本。如果人工盯着看,成本太高。
-
实现复杂:需要 Istio 这种强大的流量治理能力,单纯靠 K8s 原生比较难做到细粒度的金丝雀控制。
-
三、 三种发布策略对比总结
为了让你在面试或工作中能脱口而出,我整理了一个对比表:
|
发布策略 |
资源占用 |
回滚速度 |
风险程度 |
适用场景 |
|---|---|---|---|---|
|
蓝绿发布 |
高 (双倍资源) |
极快 (秒级切换) |
中 (发布时需谨慎) |
对稳定性要求高,可接受资源浪费的场景 |
|
滚动发布 |
低 (渐进替换) |
慢 (需重新部署) |
中高 (新旧共存期有风险) |
大多数常规微服务,追求资源利用率 |
|
灰度发布 |
低 (按比例扩容) |
极快 (调权重) |
低 (风险隔离在小范围) |
大型分布式系统,核心业务,需要数据验证 |
四、 在 Istio 中怎么做?(实战落地)
既然你已经学了 Istio,这两种发布在 Istio 里其实就是改几行 YAML:
1. 滚动发布(K8s 原生)
你不需要写 Istio 配置,直接修改 K8s 的 Deployment 镜像版本,K8s 会自动帮你滚动更新。
kubectl set image deployment/my-app my-app=my-app:v2
2. 灰度发布(Istio 核心玩法)
通过修改 VirtualService的 weight(权重)来实现。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app-vs
spec:
hosts:
- my-app-service
http:
- route:
- destination:
host: my-app-service
subset: v1
weight: 90 # 90% 流量去老版本
- destination:
host: my-app-service
subset: v2
weight: 10 # 10% 流量去新版本(金丝雀)
随着观察结果良好,你可以逐步修改权重:v1:80, v2:20-> v1:50, v2:50-> v1:0, v2:100。
总结建议:
-
如果你是初学者,先搞懂 蓝绿发布(逻辑最简单)。
-
如果你要上线核心业务,灰度发布是最佳实践(结合 Istio 流量治理)。
-
滚动发布是 K8s 的默认动作,虽然简单,但在遇到线上事故时,回滚能力是最弱的。

这张图详细讲解了灰度发布(Gray Release),也就是大名鼎鼎的金丝雀发布(Canary Release)。这是目前大型互联网公司(如淘宝、抖音)最核心的发布策略。
### 1. 核心概念:为什么叫“金丝雀”?
“金丝雀”是以前煤矿工人用来检测毒气的。因为金丝雀对毒气极其敏感,一旦有危险就会先倒下报警。
在技术中,它的逻辑是一样的:先把新版本(V2)推送给一小部分“小白鼠”用户(如图中的 20%)。 如果发现新版本有问题,立刻切回老版本,大部分用户(80%)完全无感知;如果没有问题,再逐步扩大新版本的范围。
### 2. 图解 PPT 示意图
PPT 下方的图非常直观地展示了这个过程,分为两个阶段:
• 第一阶段(分流期):
◦ LB(负载均衡/网关) 接收到所有用户请求。
◦ 80% 的流量(绿色箭头):继续流向 V1(老版本),保证绝大多数用户的稳定。
◦ 20% 的流量(紫色箭头):被分流到 V2(新版本),这 20% 的用户正在帮我们“试毒”。
• 第二阶段(全量期):
◦ 底部的进度条展示了时间轴的推进。
◦ 随着时间的推移,运维人员观察到 V2 运行稳定,没有报错。
◦ 于是逐步调整流量比例:20% -> 80% -> 100%。
◦ 最终,所有用户都迁移到了 V2,老版本 V1 退役。
### 3. 优缺点分析(对应 PPT 文字)
• 最大优点:稳如泰山。相比于“滚动发布”一旦升级完就无法瞬间回滚,灰度发布在发现问题时的回滚速度极快(只需在网关把权重改回 0 即可),极大保证了系统稳定性。
• 缺点:自动化要求高。这需要非常精细的流量控制(如 Istio 的权重配置)和完善的监控体系。如果靠人工去改配置文件,稍微慢一点,小范围的故障可能就会扩散。
### 4. 结合你之前学习的 Istio
你之前学习了 VirtualService 和 ServiceEntry。在这个灰度发布的过程中,VirtualService 就是那个“调度员”。
通过修改 VirtualService 中关于 `destination` 的 `weight`(权重)字段,你就可以完美复现 PPT 里这张图的逻辑,轻松实现不停止服务的情况下完成版本迭代。

这张图讲解的是 A/B Test 发布(A/B 测试发布)。这是灰度发布的一种更高级、更具目的性的形式。
如果说“灰度发布”是为了“保稳定”(先让小部分人用,没问题再全量),那么“A/B Test 发布”就是为了“做决策”(对比两种方案,看哪个更好)。
1. 核心概念:什么是 A/B Test 发布?
PPT 上的定义非常精准:“A/B Test 发布也是灰度发布的一种形式...用来测试应用功能表现的方法”。
-
核心区别:普通的灰度发布,新老版本的功能通常是一样的,只是代码升级了。而 A/B Test 发布,新老版本的功能往往是不同的(比如界面改版了、推荐算法换了、按钮颜色变了)。
-
目的:不是为了验证“新版本会不会崩”,而是为了验证“新版本是不是比老版本更受用户欢迎/更能赚钱”。
2. 结合 PPT 架构图的实战解读
看图里的那条连线和下方的时间轴,逻辑非常清晰:
-
分流(Load Balance):
网关(LB)在这里起到了分流的作用。它把用户分成了两组:
-
绿色箭头组(80%):继续访问 V1(老版本)。这是我们的“对照组”。
-
紫色箭头组(20%):被引导访问 V2(新版本)。这是我们的“实验组”。
-
-
数据反馈(PPT 文字提到):
这就是 A/B Test 的灵魂。系统会分别收集这两组用户的行为数据:
-
V1 组:点击率 5%,转化率 2%。
-
V2 组:点击率 8%,转化率 4%。
-
决策:如果 V2 的数据明显更好,就逐步加大紫色箭头的流量,直到 100%;如果 V2 数据很差,就直接关掉,所有人都回到 V1。
-
3. A/B Test 与普通灰度发布的对比
虽然图上看都是“分流量”,但它们的侧重点不同:
|
维度 |
普通灰度发布(金丝雀) |
A/B Test 发布 |
|---|---|---|
|
关注点 |
稳定性(Bug 多不多?会不会崩溃?) |
效果(哪个版本转化率更高?用户体验更好?) |
|
版本差异 |
通常是同一功能的迭代(如 V1.0 -> V1.1) |
通常是不同体验的设计(如 红色按钮 vs 蓝色按钮) |
|
决策依据 |
监控系统报警(Error Rate) |
业务数据分析(Click-through Rate, Conversion Rate) |
4. 在 Istio 中如何实现?(技术落地)
要实现图中这种按用户比例分流的效果,在 Istio 中主要依靠 VirtualService的 weight字段,配合 DestinationRule。
核心配置思路:
-
定义两个 Subset(v1 和 v2)。
-
在 VirtualService 中设置权重:
http: - route: - destination: host: my-service subset: v1 weight: 80 # 80% 的人去老版本 - destination: host: my-service subset: v2 weight: 20 # 20% 的人去新版本
5. 总结
-
蓝绿发布:像是“换车”,要么开旧车,要么开新车,二选一。
-
滚动发布:像是“修车”,一边开一边换零件,最后变成新车。
-
灰度发布(金丝雀):像是“试驾”,先开一小段路看看刹车灵不灵。
-
A/B Test 发布:像是“选车”,同时开两辆车,看哪辆跑得更快、更省油,然后决定以后开哪辆。
A/B Test 是目前互联网大厂(如 Netflix、Amazon、淘宝)用来驱动产品优化的核心技术手段。

这张图展示的是 Istio 官方提供的经典微服务演示项目 Bookinfo。这个应用是学习和测试 Istio 流量治理(特别是灰度发布/金丝雀发布)的“Hello World”级实战项目。
1. 核心内容解析:Bookinfo 是什么?
这是一个模拟“图书详情页”的 Web 应用,由 4 个微服务组成,麻雀虽小,五脏俱全,非常适合用来模拟复杂的微服务调用链。
-
productpage:前端页面服务。它会调用
reviews和details。 -
reviews:评论服务(这是核心服务,有多个版本)。它会调用
ratings。 -
details:图书详细信息服务。
-
ratings:评分服务。
2. 实战场景:为什么要拿它做灰度发布?
注意看图中 reviews服务的图标,它有 v1、v2、v3 三个不同颜色的版本。这正是为了模拟灰度发布中最常见的场景——多版本并存。
在实际业务中,reviews(评论)服务经常需要更新UI或逻辑,比如:
-
v1:黑色星星(老版本)。
-
v2:红色星星,但调用了外部的
ratings服务(可能比较慢)。 -
v3:红色星星,且不调用外部服务(优化版)。
你的实战目标就是:让 90% 的用户看到 v1(黑色星星),10% 的用户看到 v2(红色星星),观察没问题后,再把流量切到 v3。
3. 实战步骤解析
图中底部高亮的部分是第一步:
kubectl create ns bookinfo
这是在 Kubernetes 中创建一个名为 bookinfo的命名空间。
完整的实战流程通常是这样的:
-
打标签(注入 Sidecar):
在这个命名空间开启 Istio 自动注入,让每个微服务 Pod 里都住进一个 Envoy 代理(Sidecar)。
kubectl label namespace bookinfo istio-injection=enabled -
部署应用:
使用 Istio 自带的命令一键部署这个微服务。
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml -
配置网关(Gateway & VirtualService):
这是最关键的一步。你需要写一个
VirtualService配置,告诉 Istio 如何分配流量。# 伪代码示例 apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: reviews spec: hosts: - reviews http: - route: - destination: host: reviews subset: v1 weight: 90 # 90% 流量去 v1 - destination: host: reviews subset: v2 weight: 10 # 10% 流量去 v2
4. 总结
接下来的操作就是去修改那个 VirtualService文件,调整 weight(权重)数值,然后观察网页上星星的颜色变化,以此来完成整个金丝雀发布的实战演练。
这是 Istio 灰度发布(金丝雀发布)最核心的配置。它告诉 Envoy 代理:“所有访问 reviews服务的流量,90% 去老版本,10% 去新版本。”
下面我逐行给你拆解,并补充一个必须配套的配置(否则这个 VS 会报错)。
一、逐行详细解释
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
-
含义:这是 Istio 的 VirtualService(虚拟服务) 资源。
-
作用:定义路由规则。它不负责提供服务,只负责告诉流量“该往哪走”。
metadata:
name: reviews
-
含义:这个资源的名字叫
reviews。 -
注意:名字不重要,重要的是下面
spec.hosts里指定的目标服务。
spec:
hosts:
- reviews
-
这是最关键的一行。
-
含义:这条规则作用于谁?作用于
reviews这个服务。 -
对应:Kubernetes 里的 Service
reviews.default.svc.cluster.local。 -
效果:只要集群内有服务访问
http://reviews,就会被这条规则拦截。
http:
- route:
-
含义:针对 HTTP 流量(不是 TCP)进行路由。
-
-代表这是第一组路由规则(可以写多组 match 规则,但这里没写 match,所以是默认规则)。
- destination:
host: reviews
subset: v1
weight: 90
-
destination.host:目标服务还是
reviews。 -
subset: v1:这是子集。它不是一个 Pod,而是一个“标签分组”。
-
在 K8s 里,你的
reviews-v1Deployment 必须有这个 label:metadata: labels: app: reviews version: v1
-
-
weight: 90:权重。90% 的流量会被转发到带有
version=v1标签的 Pod。
- destination:
host: reviews
subset: v2
weight: 10
-
subset: v2:对应
version=v2的 Pod。 -
weight: 10:10% 的流量会被转发到 v2。
二、⚠️ 缺少的关键配置:DestinationRule
你贴出的这段 “伪代码” 是不能直接用的,因为它引用了 subset: v1和 subset: v2,但 Istio 并不知道 v1/v2 对应哪些 Pod。
必须配套一个 DestinationRule:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews # 对应 VirtualService 里的 host
subsets:
- name: v1 # 对应 VirtualService 里的 subset: v1
labels:
version: v1 # 筛选 Pod 的 label
- name: v2 # 对应 VirtualService 里的 subset: v2
labels:
version: v2
没有这个 DR,Istio 会报错:
subset v1 not found in destination rule reviews...
三、完整可运行的 Bookinfo 灰度配置
1️⃣ DestinationRule(定义版本分组)
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
2️⃣ VirtualService(定义 90/10 流量切分)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
四、流量走向示意图
用户请求
↓
istio-ingressgateway (如果外部访问)
↓
productpage 服务
↓
reviews 服务 (被 VirtualService 拦截)
↓
┌──────────────────────────────────┐
│ 90% 流量 → reviews-v1 Pod (黑色星星) │
│ 10% 流量 → reviews-v2 Pod (红色星星) │
└──────────────────────────────────┘
五、如何验证效果?
1. 刷新 Bookinfo 页面
访问 http://<INGRESS_IP>/productpage
反复刷新,你会看到:
-
大部分时候是 黑色星星(v1)
-
偶尔出现 红色星星(v2)
2. 用 Prometheus 验证比例
sum(rate(istio_requests_total{destination_service="reviews",destination_subset="v1"}[1m]))
/
sum(rate(istio_requests_total{destination_service="reviews"}[1m]))
结果应接近 0.9。
六、进阶:按用户 ID 切流(更真实)
如果你不想按百分比,而是想让 特定用户 看到 v2:
http:
- match:
- headers:
end-user:
exact: jason # 只有 jason 这个用户
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
总结一句话
VirtualService 决定“流量怎么分”,DestinationRule 决定“分去哪里”。
没有 DestinationRule,VirtualService 的
subset就是空中楼阁。
如果你愿意,我可以直接帮你把 Bookinfo 的 Gateway + VirtualService + DestinationRule 整理成一份 “复制粘贴即可运行” 的完整 YAML。

这张截图展示了使用 Istio 自带项目进行灰度发布实战的第二步:部署底层微服务应用。
简单来说,这一步是在 Kubernetes 集群里把 Bookinfo 这个“图书评论”系统跑起来,并为后续的“流量切分”准备好环境。
以下是逐行命令的详细解读:
1. 环境准备阶段
-
kubectl create ns bookinfo-
作用:创建一个名为
bookinfo的独立命名空间。 -
目的:将这次实战的所有资源(Pods, Services)隔离开,防止干扰集群里的其他应用。
-
-
kubectl label namespace bookinfo istio-injection=enabled-
作用:给刚才创建的命名空间打上一个特殊标签。
-
核心意义:这是接入 Istio 的关键开关。打了这个标签后,后面创建的 Pod 会自动“挂载”一个 Envoy 代理容器(即 Sidecar),让 Istio 能够接管和控制这些服务的网络流量。
-
2. 部署应用阶段
-
cd .../bookinfo/&kubectl apply -f platform/kube/bookinfo.yaml-
作用:进入 Istio 安装包的示例目录,并应用里面的
bookinfo.yaml配置文件。 -
结果:Kubernetes 会根据 YAML 文件,拉起 Bookinfo 的四个微服务(Productpage, Reviews, Details, Ratings)。
-
3. 状态验证阶段
-
kubectl get pod -n bookinfo -o wide-
作用:查看该命名空间下所有 Pod 的运行状态和 IP。
-
⚠️ 核心考点(注意图中的细节):
你会发现
reviews服务对应的 Pod 有三个:reviews-v1-...、reviews-v2-...、reviews-v3-...。-
这就是灰度发布的物理基础:同一个服务(
reviews),同时存在三个不同版本的实例在运行。 -
虽然它们都在运行,但如果没有下面的 Istio 配置,Kubernetes 的默认轮询机制会让用户随机看到 v1、v2 或 v3。接下来的实战就是通过 Istio 的
VirtualService来打破这种随机,精准控制谁看 v1,谁看 v2。
-
-
4. 下一步行动预测
截图底部的最后一行命令是 kubectl get svc ...,这是为了查看服务的 ClusterIP。看完 IP 之后,接下来的标准操作通常是:
-
配置 Gateway:让外部流量能进入集群访问这个服务。
-
配置 VirtualService:编写 YAML 文件,定义流量权重(例如 90% 去 v1,10% 去 v2),这才是真正的“灰度发布”开始。

这张截图展示了 Istio 控制平面(Control Plane) 和 入口网关(Ingress Gateway) 的健康状态,这是执行灰度发布前的“基础设施检查”。
结合之前的步骤,这意味着你已经完成了最关键的初始化,以下是详细解读:
1. 核心组件状态:一切正常
截图中的 kubectl get pod,svc -n istio-system命令验证了 Istio 的核心服务已经全部启动并处于 Running状态。这是进行流量治理的前提。
-
pod/istiod-xxxx(Pilot)-
作用:这是 Istio 的“大脑”。它负责下发配置给各个服务的 Sidecar 代理(Envoy),告诉它们流量该怎么转发。
-
状态:
1/1 Running。说明大脑正常运作。
-
-
pod/istio-ingressgateway-xxxx(Gateway)-
作用:这是 Istio 的“大门”或“海关”。外部用户(比如你的浏览器)必须通过这个网关才能访问到集群内部的 Bookinfo 应用。
-
状态:
1/1 Running。说明大门已经敞开。
-
2. 关键细节:为什么会一直转圈?
注意看 istio-ingressgateway的 EXTERNAL-IP显示为 <pending>。
-
原因:你的 K8s 集群是裸机或虚拟机环境(非云厂商如阿里云/腾讯云),没有内置的负载均衡器(LoadBalancer)服务。
-
影响:完全不影响使用。这只是意味着它没有分配一个公网 IP,但你依然可以通过服务器的内网 IP 或 NodePort 来访问服务。
-
解决:通常这种教程环境会使用
NodePort方式访问,或者手动修改 Service 类型为ClusterIP并通过端口映射访问。
3. 下一步行动预测
既然“大脑”和“大门”都准备好了,“房子”(Bookinfo 应用)也建好了,接下来的核心动作必然是 “装修门禁” —— 即配置路由规则。
预计接下来的步骤是:
-
创建 Gateway 资源:定义一个监听 HTTP 请求的网关入口。
-
创建 VirtualService 资源:编写那个决定流量走向(90%去v1,10%去v2)的 YAML 文件。
只要 kubectl apply -f xxx.yaml应用了这些配置,整个灰度发布的链路就彻底打通了。


这张截图展示的是 Istio 中用于暴露服务入口的 Gateway(网关) 配置文件。
简单来说,这份配置的作用是:在 Istio 的边界上开一个大门(端口),告诉外界如何把流量“接进来”。
结合之前的步骤(Bookinfo 应用已部署,Istio 控制平面已就绪),这是实现灰度发布的必经之路——没有这个网关,外部用户就无法访问到集群内的服务。
以下是对图中关键配置的详细解读:
1. 核心作用:为什么需要这个 Gateway?
在 Kubernetes 中,通常我们用 Ingress 来暴露服务。但在 Istio 体系中,Gateway 是专门负责“四层(TCP/UDP)”流量接入的资源对象。
-
它就像一个大门,独立于具体的业务逻辑。
-
它告诉
istio-ingressgateway(运行在集群里的那个代理 Pod):“如果有人访问你的 80 端口,请把流量交给内部服务处理。”
2. 逐行代码深度解析
A. 绑定执行者 (Selector)
selector:
istio: ingressgateway
-
含义:这行配置非常关键,它指定了“由谁来干活”。
-
原理:在 Istio 系统中,有一个名为
istio-ingressgateway的 Pod(之前截图中已验证它在运行)。这个selector就像一个标签选择器,Istio 会找到带有istio: ingressgateway标签的那个 Pod,把这里的端口监听规则下发给它。
B. 开启大门 (Servers)
servers:
- port:
number: 80 # 监听的端口
name: http # 端口名称(自定义,建议有意义)
protocol: HTTP # 协议类型
-
含义:定义了大门的具体属性。
-
实战意义:这里配置为
80端口。意味着当外部流量打到 Istio 网关的 80 端口时,Istio 会接管它。 -
注意:如果你的服务器是虚拟机且没有 LoadBalancer IP(之前截图中 External-IP 是 pending),你通常需要通过这个服务器的 IP + NodePort 或者直接访问 Pod IP 来测试这个 80 端口。
C. 允许访客 (Hosts)
hosts:
- "*"
-
含义:
"*"代表通配符,意思是“允许所有的域名/IP 访问”。 -
解释:这里不进行域名限制。只要流量是从 80 端口进来的,不管 Host 头是什么(不管是直接输 IP 还是访问某个域名),都放行进入内部。在实际生产环境中,这里通常会填入具体的域名,如
bookinfo.example.com,以实现多租户或精确路由。
3. 整体流程图解
结合这张图和之前的步骤,现在的流量走向是这样的:
-
外部用户:在浏览器输入
http://<你的服务器IP>/productpage。 -
Istio Gateway:流量击中了
istio-ingressgatewayPod 的 80 端口(由本图配置决定)。 -
VirtualService(下一步要做):Gateway 接收到流量后,会查找有没有配套的
VirtualService。如果有,就把流量转给具体的服务(比如 90% 给 v1,10% 给 v2)。 -
微服务:最终流量到达
productpage服务。
4. 下一步做什么?
单有这个 Gateway还不能完成灰度发布,它只是开了门。接下来必须创建 VirtualService,才能真正实现“流量切分”。
通常下一步的命令是:
kubectl apply -f networking/bookinfo-gateway.yaml
应用这个配置后,Istio 就会把规则推送到 istio-ingressgateway,大门就正式开启了。


这张截图展示的是 VirtualService 配置,它是 Istio 实现流量治理的核心配置之一。
如果说之前的 Gateway是开了大门,那么这个 VirtualService就是门内的导航系统。它告诉 Istio:“进来的人要去哪里,走哪条路”。
结合你之前的操作(部署了 Bookinfo,配置了 Gateway),这份配置的具体含义如下:
1. 核心作用:路由规则
这个配置定义了一个规则:根据不同的 URL 路径,将流量转发到不同的内部服务。
虽然截图里只写了一个 route指向 productpage,但这通常是 Bookinfo 示例的标准配置,用于处理前端的页面请求。
2. 逐段代码解析
-
hosts: ["*"]-
这里填
*,表示匹配所有域名。注意,这个hosts指的是下游客户端访问的主机名,而不是你要转发到的目标主机。它通常与Gateway中的hosts保持一致或兼容。
-
-
gateways: [- bookinfo-gateway]-
这行非常关键!它将这个路由规则与之前创建的
bookinfo-gateway绑定。这意味着,只有从 Gateway 大门进来的流量,才会走这套规则。如果没有这行,外部流量是进不来的。
-
-
http: [...]下的match部分-
这是 Istio 强大的匹配规则。它列出了多种 URL 路径:
-
exact: /productpage: 精确匹配根页面。 -
prefix: /static: 前缀匹配静态资源(如 CSS/JS 图片)。 -
exact: /login,/logout: 匹配登录登出接口。 -
prefix: /api/v1/products: 匹配 API 接口。
-
-
实战意义:这样配置后,用户访问网页、加载图片、点击登录,都能被正确识别并路由。
-
-
route: [...]部分-
destination: { host: productpage, port: 9080 } -
这是最终的落脚点。不管上面的
match匹配了哪个路径,流量最终都会被转发给名为productpage的服务,端口是 9080。
-
3. 为什么说它还没完?(关于灰度发布)
你现在的进度条卡在这里:
-
Gateway:大门开了(80端口)。
-
VirtualService:进门后,所有请求都指向了
productpage。
但真正的灰度发布在哪里?
在 productpage服务内部,它会去调用 reviews服务。要实现“90%看黑星,10%看红星”,你需要在 productpage到 reviews的调用链上做文章,或者在针对 reviews服务的 VirtualService 上做文章。
通常接下来的步骤是:
-
确认
productpage的代码中调用reviews的地址是reviews:9080。 -
编写一个针对
reviews服务的VirtualService(类似下面这样):
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews # 目标服务是 reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
4. 总结
你现在看到的这个 YAML 是基础路由配置,它保证了你能正常看到网页。
要完成“金丝雀发布”,下一步必须是针对具体微服务(如 reviews) 编写带有 weight(权重)的路由配置。








这两张截图展示了 Istio 灰度发布(金丝雀发布)的核心实战配置,特别是如何将流量全量切分到 reviews服务的 v1 版本(不带五角星的版本)。
这里包含了两个关键的 VirtualService配置:productpage和 reviews。
1. productpage的 VirtualService(基础路由)
-
配置看点:
route: - destination: host: productpage subset: v1 -
深度解析:
-
这个配置的作用是保底。它强制将所有访问
productpage(图书详情页)的流量都导向productpage服务的v1版本。 -
为什么这么做? 在 Bookinfo 示例中,
productpage是前端页面。为了确保整个实验环境纯净,通常先把前端锁定在稳定的 v1 版本,然后再去折腾后面的reviews服务。
-
2. reviews的 VirtualService(灰度发布核心)
这是实现“灰度发布”的真正开关。
-
配置看点:
hosts: - reviews http: - route: - destination: host: reviews subset: v1 # 关键点在这里 -
深度解析:
-
目标锁定:
hosts: - reviews表示这条规则只对发往reviews服务的流量生效。 -
全量切流:
subset: v1配合单一的route字段,意味着100% 的流量都会被转发到reviews的 v1 版本。 -
结果:这时候用户刷新页面,看到的将是没有星星的评论区(因为 v1 版本不显示星星)。即使你把 v2(红五星)和 v3(黑五星)的 Pod 都运行着,用户也绝对刷不出来。
-
3. 背后的“隐形功臣”:DestinationRule
虽然截图中没有展示,但根据 subset: v1这一行代码,必须存在一个对应的 DestinationRule才能让这个配置生效。
它大概长这样:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1 # 这里定义的名字,就是上面 VirtualService 里引用的 subset
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
4. 总结与下一步
目前的配置状态是:全量流量 -> reviews v1 (无星)。
如果要进行金丝雀发布(灰度测试),下一步的操作就是将截图中的 reviewsVirtualService 修改为类似下面这样(引入权重):
# 假设这是修改后的 reviews VirtualService
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90 # 90% 流量去 v1 (无星)
- destination:
host: reviews
subset: v2
weight: 10 # 10% 流量去 v2 (红星)
这样就实现了“先让 10% 的用户看到新版本,没问题再全量”的灰度发布流程。
这张截图展示的是 DestinationRule(目标规则) 配置文件。
在上一环节我们看了 VirtualService(路由规则),而这张图里的 DestinationRule可以看作是它的“搭档”或“底层支撑”。
以下是详细解读:
1. 核心作用:给服务“分门别类”
如果说 VirtualService是决定“走哪条路”,那么 DestinationRule就是定义“每条路通向哪里,以及这条路的特点是什么”。
具体来说,它的作用是:根据服务的标签(Labels),将同一个服务的不同版本(如 v1, v2, v3)定义为独立的“子集(Subsets)”。
2. 结合截图逐段分析
-
host: reviews-
这表示接下来的规则是针对
reviews这个服务的。Istio 会去匹配集群中标签为app: reviews的所有 Pod。
-
-
subsets:(重点!)-
这里定义了三个子集:
v1、v2、v3。这就是我们在 Kubernetes 里常说的标签选择器。 -
name: v1: 定义了一个名叫v1的子集。 -
labels: version: v1: 这是关键。Istio 会去寻找那些带有version: v1标签的 Pod。-
结合你之前的截图:你在部署 Bookinfo 时,
reviews的 v1 Pod 模板里肯定带了version: v1这个标签。Istio 通过这里的定义,就能准确地识别出哪些 Pod 属于 v1 版本。
-
-
3. 为什么必须有它?(与 VirtualService 的关系)
这就解释了上一环节中 VirtualService里的一行代码:
subset: v1
-
VirtualService说:把流量转发给reviews服务的v1子集。 -
DestinationRule说:哦,原来v1子集指的就是那些带有version: v1标签的 Pod!
如果没有这张图里的 DestinationRule,VirtualService里的 subset: v1就会失效,Istio 根本不知道 v1在哪里,会导致流量转发失败。
4. 实战总结
这两个文件(virtual-service-all-v1.yaml和 destination-rule-all.yaml)通常是成对使用的:
-
第一步(本图
DestinationRule):先“画地图”,告诉 Istio v1、v2、v3 各自的 Pod 在哪里(通过标签匹配)。 -
第二步(上一张图
VirtualService):再“发指令”,根据权重(如 90% 去 v1,10% 去 v2)把流量分配到上面定义好的子集上。
现在,Istio 的基础环境、网关入口、路由规则、目标规则都已经准备好了,只差最后一步应用这些 YAML 文件,灰度发布的环境就彻底搭建完成了。
结合你提供的所有截图和 Istio 实战逻辑,这里整理了完成灰度发布(金丝雀发布)所需的全套配置文件。
这些文件分别对应了我们之前分析的:入口网关、路由规则、以及底层的版本分流定义。
1. 入口网关定义 (Gateway)
文件路径:networking/bookinfo-gateway.yaml
作用:在集群边缘开一个 80 端口,接收外部流量。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # 绑定到默认的 Ingress Gateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*" # 允许所有域名访问
2. 基础路由规则 (VirtualService for productpage)
文件路径:networking/virtual-service-productpage.yaml
作用:将访问前端的流量直接转发给 productpage服务。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: productpage
spec:
hosts:
- productpage
http:
- route:
- destination:
host: productpage
subset: v1
3. 灰度发布规则 (VirtualService for reviews)
文件路径:networking/virtual-service-reviews-v1.yaml(初始状态)
作用:这是控制灰度的核心。初始状态下,强制所有流量走 v1(无星)版本。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
4. 目标规则定义 (DestinationRule)
文件路径:networking/destination-rule-all.yaml
作用:定义什么是 v1,什么是 v2。VirtualService 里的 subset: v1必须在这里有对应定义才能生效。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
下一步:如何做灰度测试?
当你把上面 4 个文件都 kubectl apply -f部署好之后,用户只能看到无星版本。
如果你想进行金丝雀发布(例如:90% 流量看 v1 无星,10% 流量看 v2 红星),你需要修改第 3 个文件 (reviews的 VirtualService):
修改后的 virtual-service-reviews-v1.yaml:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90 # 新增:90% 权重给 v1
- destination:
host: reviews
subset: v2
weight: 10 # 新增:10% 权重给 v2
保存并重新执行 kubectl apply -f networking/virtual-service-reviews-v1.yaml,灰度效果立即生效,无需重启任何 Pod。







这组截图展示了在 Kubernetes (K8s) 和 Istio 环境下,使用 Nginx 镜像 部署一个简单的双版本应用,并通过 Service 进行流量负载均衡的全过程。
这是一个比之前 Bookinfo 更轻量级的灰度发布模拟实验。以下是核心步骤和对应配置文件的整理:
1. 环境准备:命名空间与自动注入
首先需要创建一个独立的命名空间,并打上 Istio 自动注入标签,确保后续的 Nginx Pod 会自动被 Istio Sidecar 代理容器注入。
-
创建命名空间:
bash
kubectl create ns nginx
-
开启自动注入:
bash
kubectl label namespace nginx istio-injection=enabled
2. 部署应用:双版本 Deployment
这里利用了 K8s 的 Deployment机制,部署了两个版本的 Nginx 应用。关键在于通过 version标签区分它们。
配置文件:nginx-v1.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
version: v1 # 关键标签:标识为 v1 版本
spec:
containers:
-
image: nginx
name: nginx
同理,nginx-v2.yaml 只需将上面的 name 改为 nginx-v2,labels 中的 version 改为 v2
3. 暴露服务:创建统一的 Service
为了让外部或 Istio Gateway 能够访问到这两个版本的 Pod,创建了一个 ClusterIP 类型的 Service。它通过 app: nginx标签同时选中了 v1 和 v2 的 Pod,实现流量的初步汇聚。
-
配置文件:
service_nginx.yamlyaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx # 只要带有 app=nginx 标签的 Pod 都会接收流量
ports:
-
protocol: TCP
port: 80
targetPort: 80
-
-
验证 Endpoint:
执行
kubectl get svc,ep -n nginx可以看到 Endpoints 列表中同时包含了 v1 和 v2 的 Pod IP,说明 Service 已经成功将两者关联。
4. 区分流量:修改静态页面
为了直观验证灰度发布的效果,需要进入容器内部修改默认的 index.html,让 v1 和 v2 显示不同的内容。
-
修改 v1:显示 "111"
bash
kubectl exec -it nginx-v1-xxxxx bash -c nginx -n nginx
echo "111" > /usr/share/nginx/html/index.html
-
修改 v2:显示 "222"
bash
kubectl exec -it nginx-v2-xxxxx bash -c nginx -n nginx
echo "222" > /usr/share/nginx/html/index.html
5. 下一步操作预测
目前,v1 和 v2 已经同时运行,并且共享同一个 Service 入口。接下来的 Istio 灰度发布步骤通常是:
-
创建 Gateway:在 Istio 中开启 80 端口监听。
-
创建 VirtualService:编写路由规则,例如设定 90% 流量去 v1(看到 111),10% 流量去 v2(看到 222)。
这样就完成了一个最基础的基于 Nginx 的金丝雀发布环境搭建。
以下是基于你当前的 Nginx 双版本环境,实现 90% 流量到 v1(显示 111),10% 流量到 v2(显示 222) 的完整 Istio 配置。
一、创建 Gateway(开启 80 端口监听)
这个 Gateway 负责在 Istio 边界开启入口。
gateway-nginx.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: nginx-gateway
namespace: nginx
spec:
selector:
istio: ingressgateway # 绑定 istio-ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*" # 允许所有域名/IP访问
应用:
kubectl apply -f gateway-nginx.yaml
二、创建 DestinationRule(定义 v1 / v2 子集)
⚠️ 必须先创建这个,否则 VirtualService 里的 subset会报错。
destinationrule-nginx.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: nginx-destination
namespace: nginx
spec:
host: nginx.nginx.svc.cluster.local # 你的 Service FQDN
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
应用:
kubectl apply -f destinationrule-nginx.yaml
三、创建 VirtualService(90/10 灰度路由)
这是灰度发布的核心配置。
virtualservice-nginx.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-route
namespace: nginx
spec:
hosts:
- "*"
gateways:
- nginx-gateway # 绑定上面的 Gateway
http:
- route:
- destination:
host: nginx.nginx.svc.cluster.local
subset: v1
weight: 90 # 90% 流量 → v1(显示 111)
- destination:
host: nginx.nginx.svc.cluster.local
subset: v2
weight: 10 # 10% 流量 → v2(显示 222)
应用:
kubectl apply -f virtualservice-nginx.yaml
四、获取访问地址并验证
1️⃣ 获取 Ingress Gateway 地址
kubectl get svc istio-ingressgateway -n istio-system
-
LoadBalancer 环境:直接使用
EXTERNAL-IP -
NodePort / 裸机环境:
export INGRESS_HOST=$(kubectl get po -n istio-system -l istio=ingressgateway -o jsonpath='{.items[0].status.hostIP}') export INGRESS_PORT=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
2️⃣ 访问测试(关键验证)
for i in {1..20}; do
curl http://$INGRESS_HOST:$INGRESS_PORT
echo ""
done
✅ 预期结果:
-
大约 18 次 返回
111(v1) -
大约 2 次 返回
222(v2)
五、常见问题排查
❌ VirtualService 报错:subset v1 not found
✅ 解决:确认 DestinationRule已创建,且 labels.version与 Pod 一致:
kubectl get pods -n nginx --show-labels
❌ 访问返回 503
✅ 解决:
kubectl logs deploy/nginx-v1 -n nginx -c istio-proxy
kubectl logs deploy/nginx-v2 -n nginx -c istio-proxy
六、下一步(可选进阶)
如果你愿意,我可以继续帮你做:
-
✅ 基于 Header 的灰度(如
x-user: test→ 强制走 v2) -
✅ 基于 Cookie 的灰度(如
canary=true→ 走 v2) -
✅ 自动熔断(v2 错误率 >10% 自动切回 v1)
-
✅ Kiali 可视化验证流量比例
你现在这套环境已经是标准的企业级灰度发布模型了 👍
太棒了!你已经掌握了基础的灰度发布,接下来我们进入精细化流量治理阶段。
这四个需求分别对应了微服务架构中非常核心的四个治理场景。我会为你逐一提供可以直接使用的 YAML 配置和验证方法。
1️⃣ 基于 Header 的灰度发布
场景:内部测试人员想要体验新版本,但普通用户保持原样。我们可以通过浏览器插件(如 ModHeader)设置一个特定的 Header 来实现。
修改 virtualservice-nginx.yaml:
yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-route
namespace: nginx
spec:
hosts:
-
"*"
gateways:
-
nginx-gateway
http:
第一层规则:优先匹配 Header
-
match:
-
headers:
x-user: # Header 的 Key
exact: test # Header 的 Value
route:
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v2
注意:这里不需要写 weight,命中后 100% 走 v2
-
第二层规则:兜底策略(默认走 90/10)
-
route:
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v1
weight: 90
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v2
weight: 10
-
验证方法:
使用 curl模拟带 Header 的请求:
bash
curl -H "x-user: test" http://<你的网关IP>
👉 结果:无论权重怎么设,这条请求一定会显示 222 (v2)。
2️⃣ 基于 Cookie 的灰度发布
场景:这是最常用的金丝雀发布手段。让一部分“特定用户”(比如种子用户)自动看到新版本,其他用户无感。
修改 virtualservice-nginx.yaml:
yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nginx-route
namespace: nginx
spec:
hosts:
-
"*"
gateways:
-
nginx-gateway
http:
第一层规则:优先匹配 Cookie
-
match:
-
cookies:
canary: # Cookie 的 Key
exact: "true" # Cookie 的 Value
route:
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v2
-
第二层规则:兜底策略
-
route:
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v1
weight: 90
-
destination:
host: nginx.nginx.svc.cluster.local
subset: v2
weight: 10
-
验证方法:
bash
模拟携带 Cookie 的请求
curl --cookie "canary=true" http://<你的网关IP>
👉 结果:携带该 Cookie 的请求会稳定显示 222 (v2),非常适合前端在浏览器控制台设置 Cookie 进行自测。
3️⃣ 自动熔断(异常自动回退)
场景:v2 版本可能存在 Bug,当错误率达到阈值(例如 10%)时,Istio 自动停止向 v2 发送流量,全量切回 v1,保护整体服务可用性。
我们需要引入一个新的资源:DestinationRule中的 trafficPolicy和 outlierDetection。
创建/修改 destinationrule-nginx.yaml:
yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: nginx-dest
namespace: nginx
spec:
host: nginx.nginx.svc.cluster.local
subsets:
-
name: v1
labels:
version: v1
-
name: v2
labels:
version: v2
重点:针对 v2 设置熔断策略
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 1 # 连续 1 个 5xx 错误就触发剔除
interval: 10s # 检测间隔
baseEjectionTime: 30s # 剔除时间
maxEjectionPercent: 100 # 最大剔除比例
模拟故障并验证:
-
配置好上面的 YAML。
-
制造故障:进入 v2 的 Pod,停止 Nginx 服务。
bash
kubectl exec -it nginx-v2-xxxxx -n nginx -- nginx -s stop
-
观察:等待约 10-30 秒后,再次访问网关。
👉 结果:虽然 VirtualService 里写了 10% 流量去 v2,但因为 v2 挂了,Istio 会自动将这 10% 的流量也切回 v1,用户依然能正常看到 111,不会报错。
4️⃣ Kiali 可视化验证
场景:在复杂的微服务网状结构中,肉眼看 YAML 很难确认流量到底走了哪条路。Kiali 提供了图形化拓扑图。
操作步骤:
-
确保 Kiali 已安装(Istio 默认安装通常包含)。
-
查看 Token(用于登录 Kiali):
bash
kubectl -n istio-system get secret istio-kiali -o jsonpath='{.data.token}' | base64 -d
-
启动 Port-Forward:
bash
kubectl -n istio-system port-forward deployment/kiali 20001:20001
-
访问:浏览器打开
http://localhost:20001,输入上面的 Token 登录。 -
查看 Graph:
-
找到左侧的 Graph 菜单。
-
选择你的
nginx命名空间。 -
点击右上角的 Display 按钮,勾选 Requests percentage(请求百分比)。
-
此时你会看到一张图,网关指向两个版本的箭头宽度会直接反映当前的流量比例(比如 v1 很宽,v2 很细)。
-
💡 总结:企业级灰度发布组合拳
在实际生产中,通常会这样组合使用:
text
-
正常时期:VirtualService (90% v1 / 10% v2) -> 金丝雀发布
-
测试时期:Header (x-user: internal) -> 强制走新版
-
风控时期:OutlierDetection (错误率>5%) -> 自动熔断保护
-
监控时期:Kiali + Grafana -> 实时监控流量走向
你可以先把 Header和 Cookie的玩法跑通,这对你后续做 A/B 测试非常有帮助!需要我帮你出具体的 curl压测命令吗?





这组截图展示了“如何通过域名进行流量分流”的高级实战配置。
在前面的步骤中,我们实现了基于权重(90%/10%)的金丝雀发布。但在实际生产环境中,经常需要基于域名(Host)将不同的服务流量导向不同的应用,特别是当多个服务共享同一个 Istio Ingress Gateway 的 IP 和端口时。
以下是基于截图的详细配置解析:
1. 整体架构逻辑
截图顶部的架构图展示了这种“多层代理”模式:
-
外层 LB/Nginx:用户访问公网 IP,首先到达这台前置的 Nginx(相当于 LVS 或云负载均衡器)。
-
内层 Istio Gateway:前置 Nginx 将流量转发给 K8s 集群内的 Istio Ingress Gateway(通过 NodePort 暴露)。
-
分流逻辑:Istio 根据 HTTP Header 中的
Host字段(即域名),决定是将流量路由到nginx-v1还是nginx-v2。
2. 前置 Nginx 配置(外部 LB)
截图展示了如何配置这台前置的 Nginx,让它把特定域名的请求转发给 Istio 网关。
配置文件:/etc/nginx/nginx.conf
nginx
server {
listen 80;
server_name _; # 捕获所有未匹配的域名
location / {
# 关键配置:代理到 Istio Ingress Gateway 的 NodePort IP 和端口
proxy_pass http://192.168.171.129:32677;
# 必须指定 HTTP 版本,否则 Istio 可能拒绝(默认支持 1.1)
proxy_http_version 1.1;
# 必须将 Host 头透传下去,否则 Istio 无法识别 $host 变量进行域名分流
proxy_set_header Host $host;
}
}
-
proxy_pass:指向 Istio Ingress Gateway 的 NodePort 地址(即 K8s 节点的 IP + NodePort 端口)。 -
proxy_set_header Host $host:这是最关键的一行。它确保用户浏览器输入的域名(如nginx.test.com)能被原封不动地传递给后面的 Istio 服务网格。
3. Istio 配置(域名分流核心)
为了实现“不同域名进不同服务”,我们需要配置 Gateway和 VirtualService。
第一步:Gateway 定义(监听入口)
文件推测:gateway-nginx.yaml
yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: nginx-gateway
namespace: nginx
spec:
selector:
istio: ingressgateway # 绑定到默认的 Ingress Gateway
servers:
-
port:
number: 80
name: http
protocol: HTTP
hosts:
-
"nginx.test.com" # 只允许这个域名的流量进入(白名单机制)
-
第二步:VirtualService 定义(基于域名的路由)
文件推测:vs.yaml
yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx
namespace: nginx
spec:
hosts:
-
"nginx.test.com" # 客户端访问的域名
gateways:
-
nginx/nginx-gateway # 绑定上面的 Gateway
http:
-
route:
-
destination:
host: nginx # 对应 K8s Service 名
subset: v1
weight: 80 # 80% 流量给 v1
-
destination:
host: nginx
subset: v2
weight: 20 # 20% 流量给 v2
-
4. 验证与测试流程
按照截图底部的操作步骤,完整的测试闭环如下:
-
配置 Hosts:在本地电脑(或测试机)的
/etc/hosts文件中,将nginx.test.com指向前置 Nginx 的公网 IP。text
192.168.171.130 nginx.test.com
-
启动前置 Nginx:
bash
systemctl start nginx
-
访问测试:
打开浏览器访问
http://nginx.test.com。-
因为配置了 80/20 权重,你应该大部分时间看到 111 (v1),小部分时间看到 222 (v2)。
-
如果你把浏览器 URL 改成另一个不存在的域名(如
test2.com),由于 Gateway 的hosts限制了只允许nginx.test.com,Istio 会直接返回 404 错误。这证明了域名白名单机制生效了。
-
总结
这套配置完美模拟了公有云上的多租户场景:
-
你拥有一个固定的公网 IP(前置 Nginx)。
-
你通过这个前置 Nginx,利用不同的域名(
nginx.test.com,api.test.com等)将流量分发到 K8s 集群内完全不同的微服务应用中。













这组截图展示了 Istio 实战教程的最后两个核心环节:
-
域名分流(南北向网关):利用 Istio 的
Gateway和VirtualService实现基于域名的路由。 -
可视化监控(Kiali & Prometheus):部署并暴露监控组件,实现流量拓扑的可视化。
以下是这两个环节的详细配置和原理解析:
一、基于域名的流量分流(域名路由)
当多个微服务共用同一个 Istio Ingress Gateway 的 IP 和端口时,Host头(域名)是区分不同服务的唯一标识。
1. 核心配置文件
① Gateway(网关入口)
作用:在 Ingress Gateway 上监听 80 端口,并允许 bookinfo.test.com这个域名的流量进入。
yaml
文件名:gateway-bookinfo.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: bookinfo
spec:
selector:
istio: ingressgateway
servers:
-
port:
number: 80
name: http
protocol: HTTP
hosts:
-
"bookinfo.test.com"
-
② VirtualService(虚拟服务路由)
作用:将匹配到上述域名的流量,转发给后端的 productpage服务。
yaml
文件名:virtual-service-bookinfo.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
namespace: bookinfo
spec:
hosts:
-
"bookinfo.test.com" # 必须和 Gateway 中的 hosts 一致
gateways:
-
bookinfo/bookinfo-gateway # 绑定 gateway,格式:namespace/name
http:
-
route:
-
destination:
host: productpage # K8s service 名称
port:
number: 9080
-
(注:截图中未展示完整的 http路由段,这里补充了最基础的全量转发配置)
2. 客户端测试配置
为了让这个域名分流生效,必须在客户端机器(你的电脑或测试机)的 hosts文件中添加一条映射:
text
<你的Istio网关IP> bookinfo.test.com
访问时,浏览器会根据这个域名发送请求,Istio 网关收到后,比对 Host头,确认这是 bookinfo服务的流量,于是将其转发给 productpage。
二、服务网格可视化监控(Kiali & Prometheus)
Istio 自带了强大的监控能力,但这需要部署额外的组件(Addons)。
1. 核心配置文件
① CRD (Custom Resource Definition)
监控组件依赖一些新的资源类型,必须先安装。
bash
kubectl apply -f crds.yaml -n istio-system
② Kiali 配置(修改 NodePort)
默认情况下 Kiali 可能没有外部访问方式,需要手动修改。
修改点:在 spec.ports下增加 nodePort: 31686,固定监控页面的对外端口。
yaml
文件名:kiali.yaml (部分)
apiVersion: kiali.io/v1alpha1
kind: Kiali
metadata:
name: kiali
namespace: istio-system
spec:
server:
ports:
-
name: http-metrics
port: 9090
新增下面这一行,固定 NodePort
nodePort: 31686
-
name: http
port: 20001
nodePort: 31686
2. 部署与验证
执行以下命令一键部署 Prometheus(采集数据)和 Kiali(展示面板):
bash
kubectl apply -f grafana.yaml -n istio-system
kubectl apply -f prometheus.yaml -n istio-system
kubectl apply -f kiali.yaml -n istio-system
3. 如何访问
部署完成后,查看 Service 列表,找到 kiali的 NodePort:
bash
kubectl get svc -n istio-system
-
Kiali 访问地址:
http://<任意节点IP>:31686 -
Kiali 默认账号/密码:
admin/admin
💡 总结
至此,你已经完成了 Istio 最核心的实战技能闭环:
-
内部通信:服务间自动注入 Sidecar。
-
流量治理:灰度发布、A/B 测试、金丝雀发布。
-
外部暴露:通过域名将服务安全地暴露给公网。
-
观测运维:通过 Kiali 实时监控服务间的调用关系和流量分布。
以下是基于你提供的截图和实战流程整理的 Istio Bookinfo 全链路详细配置文件。请按顺序执行。
一、基础环境准备
1. 创建命名空间并开启自动注入
kubectl create ns bookinfo
kubectl label namespace bookinfo istio-injection=enabled
二、部署 Bookinfo 应用
2. Bookinfo 应用部署 (bookinfo.yaml)
来源:
samples/bookinfo/platform/kube/bookinfo.yaml作用:部署 Productpage, Reviews (v1/v2/v3), Details, Ratings 四个微服务。
apiVersion: v1
kind: Service
metadata:
name: details
namespace: bookinfo
spec:
ports:
- port: 9080
name: http
selector:
app: details
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: details-v1
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: details
version: v1
template:
metadata:
labels:
app: details
version: v1
spec:
containers:
- name: details
image: docker.io/istio/examples-bookinfo-details-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
# Reviews Service (v1, v2, v3)
apiVersion: v1
kind: Service
metadata:
name: reviews
namespace: bookinfo
spec:
ports:
- port: 9080
name: http
selector:
app: reviews
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v1
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v1
template:
metadata:
labels:
app: reviews
version: v1
spec:
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v2
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v2
template:
metadata:
labels:
app: reviews
version: v2
spec:
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v2:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: reviews-v3
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v3
template:
metadata:
labels:
app: reviews
version: v3
spec:
containers:
- name: reviews
image: docker.io/istio/examples-bookinfo-reviews-v3:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
# Productpage Service
apiVersion: v1
kind: Service
metadata:
name: productpage
namespace: bookinfo
spec:
ports:
- port: 9080
name: http
selector:
app: productpage
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: productpage-v1
namespace: bookinfo
spec:
replicas: 1
selector:
matchLabels:
app: productpage
version: v1
template:
metadata:
labels:
app: productpage
version: v1
spec:
containers:
- name: productpage
image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
应用:
kubectl apply -f bookinfo.yaml
三、Istio 流量治理配置(灰度发布)
3. Gateway 配置 (gateway-bookinfo.yaml)
作用:开启 80 端口,允许
bookinfo.test.com域名访问。
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: bookinfo
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "bookinfo.test.com"
应用:
kubectl apply -f gateway-bookinfo.yaml
4. DestinationRule 配置 (destination-rule-bookinfo.yaml)
⚠️ 必须配置:定义 v1, v2, v3 子集,否则 VirtualService 会报错。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews
namespace: bookinfo
spec:
host: reviews.bookinfo.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
应用:
kubectl apply -f destination-rule-bookinfo.yaml
5. VirtualService 配置 (virtual-service-bookinfo.yaml)
作用:实现 90% v1, 10% v2 的灰度发布。
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: bookinfo
namespace: bookinfo
spec:
hosts:
- "bookinfo.test.com"
gateways:
- bookinfo/bookinfo-gateway
http:
# 路由到 productpage
- match:
- uri:
exact: /productpage
route:
- destination:
host: productpage.bookinfo.svc.cluster.local
port:
number: 9080
# 默认路由(兜底)
- route:
- destination:
host: productpage.bookinfo.svc.cluster.local
port:
number: 9080
进阶:灰度发布(90/10)
若要对 reviews服务进行灰度,需单独配置:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
namespace: bookinfo
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews.bookinfo.svc.cluster.local
subset: v1
weight: 90
- destination:
host: reviews.bookinfo.svc.cluster.local
subset: v2
weight: 10
应用:
kubectl apply -f virtual-service-bookinfo.yaml
四、监控与可视化(Kiali & Prometheus)
6. Kiali 配置 (kiali.yaml)
作用:修改 Service 类型为 NodePort,固定端口 31686。
apiVersion: v1
kind: Service
metadata:
name: kiali
namespace: istio-system
spec:
type: NodePort
selector:
app: kiali
ports:
- name: http
port: 20001
targetPort: 20001
nodePort: 31686
7. Prometheus 配置 (prometheus.yaml)
作用:部署指标采集器(Istio 默认提供)。
# 使用 Istio 自带配置
kubectl apply -f samples/addons/prometheus.yaml
8. 部署监控组件
kubectl apply -f samples/addons/kiali.yaml
kubectl apply -f samples/addons/grafana.yaml
五、验证与访问
1. 配置本地 Hosts
在本地电脑的 /etc/hosts中添加:
<ISTIO_INGRESS_GATEWAY_IP> bookinfo.test.com
获取 Gateway IP:
kubectl get svc istio-ingressgateway -n istio-system
2. 访问应用
浏览器打开:http://bookinfo.test.com/productpage
3. 访问 Kiali
浏览器打开:http://<NODE_IP>:31686
账号/密码:admin/admin
六、总结配置清单
|
序号 |
文件 |
作用 |
|---|---|---|
|
1 |
|
部署微服务 |
|
2 |
|
入口网关 |
|
3 |
|
定义版本子集 |
|
4 |
|
路由与灰度 |
|
5 |
|
可视化监控 |
这套配置完全覆盖了你截图中的所有实战环节,可直接用于实验或生产环境参考。

Kiali 和 Prometheus 的关系:一句话版
Prometheus 是"指标的仓库",Kiali 是"看图说话的壳"。
Kiali 自己不产生也不存储任何指标,它只是一个Web UI + 分析引擎,它所有关于流量、错误率、拓扑图的数据,全部是通过 HTTP 查询 Prometheus 拿到的。
数据流向(核心)
Envoy Sidecar(每个 Pod 里)
│ 暴露 envoy 指标(计数器、直方图等)
│ 端口 15090 /stats/prometheus
▼
Prometheus
│ 定时 scrape(抓取)所有 Sidecar 的指标
│ 存进自己的 TSDB(时序数据库)
│ 提供 PromQL 查询接口 :9090/api/v1/query
▼
Kiali
│ 内部配置指向 Prometheus 的 URL
│ Kiali 用 PromQL 去 Prometheus 查询
│ 例:"给我过去 1 分钟内 reviews 服务的 5xx 率"
▼
浏览器(你看到的拓扑图 / 折线图 / 健康红绿灯)
所以关系是典型的 Consumer → Data Source 关系:
|
角色 |
干的事 |
不干的 |
|---|---|---|
|
Prometheus |
抓指标 → 存指标 → 答查询 |
不画好看的服务拓扑图 |
|
Kiali |
查 Prometheus → 画拓扑 → 标红绿灯 → 展示 VS/DR 配置 |
不存任何历史指标 |
配置层面:Kiali 怎么"认" Prometheus
Kiali 启动时会读自己的配置(kiali-config.yaml或 CR Kiali资源),里面有一节专门指向 Prometheus:
# Kiali 配置里的关键段落(概念性)
external_services:
prometheus:
url: http://prometheus.istio-system.svc.cluster.local:9090
这就是两者耦合的唯一纽带——一个 URL。Kiali 启动后,每隔一段时间(或你刷新页面时)会用 PromQL 去这个 URL 拉数据。
你可以验证:如果把 Prometheus 删了,Kiali 页面照样能打开,但:
-
拓扑图上 没有流量线
-
左上角会报 "Prometheus not reachable"
-
"Metrics" 标签页空白
他们各自"管"什么——职责拆解
Prometheus 管的
|
职责 |
说明 |
|---|---|
|
服务发现 |
通过 K8s API 找 Pod,找 |
|
Scrape(抓取) |
定时拉 |
|
存储 |
本地 TSDB(默认保留 15 天,可改) |
|
查询引擎 |
执行 PromQL,算速率、算百分比、算分位 |
Kiali 管的
|
职责 |
说明 |
|---|---|
|
拓扑图 |
从指标里反推出 "A → B" 的调用关系,画节点+边 |
|
健康判定 |
用 Prometheus 算 4xx/5xx 率 → 标 🔴🟡🟢 |
|
配置透视 |
直接读 K8s API 里的 VS/DR/Gateway 对象(不靠 Prometheus) |
|
操作入口 |
在 UI 里创建/修改 VS 权重(本质还是改 K8s 资源) |
注意:Kiali 画拓扑图用的是 Prometheus 的指标做"流量证据",但它读 VS/DR 配置是直接调 K8s API Server——这点很多人会混淆。
最常见的三个误解(避坑)
❌ 误解 1:"Kiali 替代 Prometheus"
不对。Kiali 只是 UI 层,指标底座永远是 Prometheus(或兼容 PromQL 的替代品,如 Thanos/Mimir)。关掉 Prometheus,Kiali 立刻变瞎。
❌ 误解 2:"Kiali 存了历史数据"
不对。你在 Kiali 里看到的历史曲线,数据源全是 Prometheus 的 TSDB。Kiali 自身连 SQLite 都没建。
❌ 误解 3:"Istio 把指标发给 Kiali"
不对。链路是:
Istio(Envoy)→ Prometheus → Kiali
Istio 根本不知道 Kiali 是谁。它只负责在 15090 暴露 /stats/prometheus,剩下的是 Prometheus 的 scrape 循环在干活。
用一句话回答你实操中看到的现象
当你执行:
istioctl dashboard kiali
# 或访问 http://nodeIP:31686
你打开的其实是一个只干"查询 + 渲染"的面板,背后真正出力的是 Prometheus 在那儿一刻不停地抓 Envoy 数据、算速率、算百分比——Kiali 只是把它包装成了你看得懂的服务拓扑图。
如果你想进一步看清楚"Kiali 到底对 Prometheus 发了哪些 PromQL 查询",在 Kiali 页面打开浏览器 DevTools → Network → 过滤 api请求,你能直接看到它发的查询语句,和你在 Prometheus 原生 UI 里敲的一模一样。44

Istio 自带 Prometheus 的采集配置文件(详细版 + 逐项抓取说明)
Istio 自带的 Prometheus 配置通常在 samples/addons/prometheus.yaml 里,其 prometheus.yml 实际存放在一个 ConfigMap 中,最终挂载到 Prometheus Pod 的
/etc/prometheus/prometheus.yml。
下面给你一份贴近 Istio 1.9 addon 风格、把“每个 job 在抓谁 / 怎么发现 / 怎么拼地址 / 抓哪个路径”讲清楚的配置。
0) 全局配置(Global)
global:
scrape_interval: 30s
evaluation_interval: 30s
# 让 prometheus 能访问 k8s apiserver 做服务发现
kubernetes_sd_configs:
- role: endpoints
说明
-
scrape_interval: 30s:每 30 秒去每个 target 拉一次指标(你看到的/targets里 Last Scrape 就由它决定)。 -
kubernetes_sd_configs: [{role: endpoints}]全局块只是声明“能用 k8s SD”,真正每个 job 自己再细化用pod还是endpoints发现。
1) envoy-stats:抓 Envoy sidecar / gateway 的原生指标
这是 Istio 里最重要的指标来源:
istio_requests_total、envoy_cluster_*、envoy_listener_*等绝大多数“流量指标”都从这里来。
- job_name: 'envoy-stats'
metrics_path: /stats/prometheus
kubernetes_sd_configs:
- role: pod
namespaces:
names: [] # [] 表示搜所有 ns;也可写成 [istio-system,bookinfo,nginx,...]
relabel_configs:
# ① 只保留:这个 Pod 里存在某个容器端口,其 port-name 以 "-envoy-prom" 结尾
# Istio 会给 istio-proxy 的 metrics 端口起名类似 http-envoy-prom / https-envoy-prom
- source_labels: [__meta_kubernetes_pod_container_port_name]
action: keep
regex: '.*-envoy-prom'
# ② 把抓取地址强行拼成 PodIP:15090
# envoy 原生指标端口默认是 15090
- source_labels: [__meta_kubernetes_pod_ip]
action: replace
regex: '(.*)'
target_label: __address__
replacement: '${1}:15090'
# ③ 把指标路径固定到 /stats/prometheus(其实 job 顶层已经写了,这里更稳)
- source_labels: []
action: replace
target_label: __metrics_path__
replacement: /stats/prometheus
# ④ 给时序打常用标签(方便 PromQL 按 ns/pod/app 查)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
- source_labels: [__meta_kubernetes_pod_label_app]
action: replace
target_label: app
- source_labels: [__meta_kubernetes_pod_label_version]
action: replace
target_label: version
这个 job 到底在“抓谁”、怎么抓?
-
发现对象:
role: pod→ Prometheus 枚举集群里所有 Pod。 -
过滤:
-
只看有
istio-proxy容器的 Pod(通过端口名*-envoy-prom间接保证)。
-
-
拼地址:
-
最终 scrape 地址变成:
http://<POD_IP>:15090/stats/prometheus
-
-
指标内容:
-
这是 Envoy 原生 stats,以
# TYPE envoy_为主,同时也包含 Istio 扩展的计数器(如istio_requests_total由 mixer/telemetry 时代遗留或 stats filter 产生/暴露)。
-
⚠️ 关键坑:15090 默认绑 127.0.0.1
很多环境里 envoy 的 metrics listener 是 127.0.0.1:15090,导致
PodIP:15090连不上(DOWN / connection refused)。
解法(demo/内网常用):让 envoy 把 metrics 绑到 0.0.0.0
kubectl edit cm istio -n istio-system
在 meshConfig.defaultConfig加:
meshConfig:
defaultConfig:
proxyStatsBindHost: 0.0.0.0
然后重启相关 workload / gateway:
kubectl rollout restart deployment istio-ingressgateway -n istio-system
kubectl rollout restart deployment/nginx-v1 deployment/nginx-v2 -n <你的ns>
2) istiod:抓 控制面(Pilot)自身指标
用来观察 istiod 健康状况、推送次数、xDS 延迟等(偏运维/排障)。
- job_name: 'istiod'
kubernetes_sd_configs:
- role: endpoints
namespaces:
names: [istio-system]
relabel_configs:
# 只保留 istiod 的 http-monitoring 端口
- source_labels: [__meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: istiod;http-monitoring
# 打标签
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
抓的是哪?
-
走的是 istiod Service 的
http-monitoring端口(通常 15014),路径默认/metrics。 -
Prometheus 通过 endpoint 发现找到 istiod Pod 的
<PodIP>:15014/metrics。
3) 应用 pod 的“注解驱动抓取”(含 Istio 的 metrics merge)
这一条负责抓 应用容器自己的
/metrics,并且在 Istio 开启enablePrometheusMerge=true(默认)时,istio-proxy 会帮你把应用指标 合并到 15020/stats/prometheus 一起暴露,让 Prometheus 只抓一个端点就能拿全。
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# 只抓声明了 prometheus.io/scrape: "true" 的 Pod
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
# 路径:默认 /metrics,允许 pod 用 prometheus.io/path 覆盖
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
replacement: ${1}
- source_labels: []
action: replace
target_label: __metrics_path__
replacement: /metrics
# 端口:允许 pod 用 prometheus.io/port 声明(通常是 15020 或应用端口)
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: (\d+)
target_label: __address__
replacement: __meta_kubernetes_pod_ip__:${1}
# 打标签
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
- source_labels: [__meta_kubernetes_pod_label_app]
action: replace
target_label: app
这里发生了什么(非常关键)
-
当 Istio sidecar 注入 + merge 开启时,istio-proxy 会给 Pod 自动补/覆盖注解:
prometheus.io/scrape: "true" prometheus.io/port: "15020" prometheus.io/path: "/stats/prometheus" -
于是 Prometheus 实际抓的是:
http://<POD_IP>:15020/stats/prometheus这个端口吐出来的文本里,既包含 Envoy/Istio stats,也可能包含应用 stats(前提:应用指标被正确 merge,且应用没跟 Istio 指标同名冲突)。
如果你“只想抓 Envoy 原生 15090”,可以不用这个
kubernetes-podsjob;如果你“想看到应用自定义 counters/gauges”,通常要走 15020 merge 端点 或单独给应用开一个 scrape job。
4) (可选)只抓 gateway 的 envoy-stats(更收敛)
如果你不想扫全集群 pod,也可以把 gateway 单独捞出来:
- job_name: 'istio-ingressgateway-envoy-stats'
metrics_path: /stats/prometheus
kubernetes_sd_configs:
- role: pod
namespaces:
names: [istio-system]
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: istio-ingressgateway
- source_labels: [__meta_kubernetes_pod_ip]
action: replace
regex: '(.*)'
target_label: __address__
replacement: '${1}:15090'
- source_labels: []
action: replace
target_label: __metrics_path__
replacement: /stats/prometheus
5) 怎么验证“到底抓到谁、抓的是什么接口”
# 看所有 target 状态
kubectl port-forward svc/prometheus -n istio-system 9090:9090
# 打开 http://localhost:9090/targets
# 直接验证 envoy 原生端点(podIP 要换成真实 IP)
kubectl get pods -n istio-system -o wide
curl http://<POD_IP>:15090/stats/prometheus | head -30
# 验证 merge 端点
curl http://<POD_IP>:15020/stats/prometheus | grep istio_requests_total | head
一句话把你之前所有疑惑串起来
-
envoy-stats job → 抓
15090/stats/prometheus(Envoy 原生,Istio 流量指标大本营) -
istiod job → 抓 istiod
15014/metrics(控制面健康) -
kubernetes-pods job → 靠 Pod 上的
prometheus.io/*注解找端口路径(通常落到15020/stats/prometheus的 merge 端点) -
Kiali ≠ 数据源:Kiali 只是读 Prometheus 查询 + 读 K8s API 里的 VS/DR 配置把它画成拓扑图。
如果你愿意把你们环境里 kubectl get cm prometheus -n istio-system -o yaml 里的 prometheus.yml原样贴出来(或只贴 scrape_configs:那段),我可以逐条按你“真实文件”帮你做红字批注版,精确到你集群里到底哪些 endpoint 会被 keep、为什么有的 DOWN。
一句话结论
|
端口 |
属于谁 |
暴露什么 |
有没有应用指标 |
|---|---|---|---|
|
15090 |
Envoy(pilot-agent 帮它起的 |
纯 Envoy/proxy 的 stats( |
❌ 没有 |
|
15020 |
pilot-agent(Istio sidecar 管理进程,不是 Envoy) |
agent 自己的 HTTP 服务: |
✅ 可通过 merge 把应用 metrics 合进来 |
简单记:
-
15090 = Envoy 的 Prometheus 指标口
-
15020 = Istio agent 的“运维口”,其中
/stats/prometheus是一个“汇总/合并出口”
1) 15090:Envoy Prometheus telemetry(只关心 proxy)
它到底是谁在监听?
在 sidecar 容器里你一般能看到类似:
# 进 istio-proxy 容器
kubectl exec -it <pod> -c istio-proxy -- sh
apt-get update && apt-get install -y net-tools 2>/dev/null; ss -lntp | grep -E "15090|15020|15000"
典型结果是:Envoy 进程(或者说 pilot-agent 代 Envoy 绑的)监听 127.0.0.1:15090(或 0.0.0.0:15090,取决于 proxyStatsBindHost)。
这个端点吐什么?
curl -s http://127.0.0.1:15090/stats/prometheus | head -40
你会看到 Prometheus text format,几乎全是:
# TYPE envoy_cluster_upstream_rq_xx counter
# TYPE envoy_listener_downstream_cx gauge
istio_requests_total{...
istio_request_duration_milliseconds_bucket{...
envoy_...
这些都是 Envoy stat store 里的计数器/直方图/gauges,描述的是:
-
listener / filter chain / cluster / upstream 的状态
-
请求计数、状态码分布、重试、熔断、连接池等
关键点:这里没有你的应用业务指标(除非你的应用碰巧也叫 istio_*之类,那也是撞名,不是设计)。
为什么会“从 Pod IP 连不上 15090”?
因为 Envoy 的 stats listener 默认绑 127.0.0.1(只给本 Pod 内用)。
你从别的 Node/Pod 用 PodIP:15090访问就会被拒。
Istio 控制这个绑址的字段是:
meshConfig:
defaultConfig:
proxyStatsBindHost: 0.0.0.0 # 允许外部 scrape(demo/内网可用;生产要评估)
2) 15020:pilot-agent 的 HTTP 端口(agent 的“前台”)
15020 不是 Envoy 的端口,而是 pilot-agent(也叫 Istio agent)启动的一个 Go HTTP server。
它承担三类事:
-
健康检查
-
/healthz/ready -
/app-health/<container>/ready(agent 替你探应用健康)
-
-
指标汇出口(重点)
-
/stats/prometheus(merge 就在这里发生)
-
-
少量 agent 自身 debug/状态
你可以验证“谁是监听者”:
# 在 Pod 里看(通常要在 istio-proxy 容器内)
ss -lntp | grep 15020
# 你大概率看到监听者是 pilot-agent(不是 envoy)
3) merge 端点(15020/stats/prometheus)是怎么“合并”的?
核心机制:文本级拼接(不是 metric 对象模型合并)
/stats/prometheus的处理逻辑本质是:
-
抓 Envoy 侧
-
agent 向
127.0.0.1:15090/stats/prometheus做一次 HTTP GET(或等价内部读取),拿到 Envoy stats 的 prometheus text
-
-
抓应用侧(如果启用 merge + 能找到应用的 metrics 端点)
-
agent 知道去哪抓应用指标,来源是:
-
meshConfig 启用了
enablePrometheusMerge=true(默认一般是 true) -
并且 Pod / 容器上有(或被 agent 写入了):
annotations: prometheus.io/scrape: "true" prometheus.io/port: "9080" prometheus.io/path: "/metrics"
-
-
agent 访问:
127.0.0.1:<app_port>/<app_path>拿应用的 prometheus text
-
-
拼接 + 冲突处理
-
把两段 text 拼在一起
-
对“应用端出现了 Istio 命名空间指标名”(典型如应用自己也叫
istio_requests_total,或应用恰好也跑了 Envoy 导致同名)做保护:-
通常做法:丢弃应用侧的同名 istio_系列*(或加前缀/标记),避免污染 Istio 的 control-plane 视角
-
-
-
输出
text/plain; version=0.0.4; charset=utf-8
官方口径也明确写了:
启用后,Envoy sidecar 将把 Istio 的指标与应用程序指标合并;合并后的指标将从
:15020/stats/prometheus 抓取。不适合的情况包括:你的应用暴露了与 Istio 指标名称相同的指标(例如应用自身也暴露
istio_requests_total)。
所以它不是“两套 Prometheus 融合”
它是 pilot-agent 在 HTTP handler 里临时 fetch + cat,输出一段干净的 prometheus text,让 Prometheus 只需要一个 target 一个端点就能一次取全。
4) 你可以用三组命令“亲眼看到区别”
假设你的 Pod 有 app container :9080/metrics且 annotations 已就位:
# 进 Pod
kubectl exec -it <pod> -c istio-proxy -- sh
A. 纯 Envoy(15090)
curl -s http://127.0.0.1:15090/stats/prometheus | grep -E "^(#|istio_requests_total|envoy_)" | head -20
→ 只有 proxy 指标。
B. 合并出口(15020)
curl -s http://127.0.0.1:15020/stats/prometheus | grep -E "^(#|istio_requests_total|envoy_|your_app_metric_)" | head -40
→ 你会多出来 your_app_metric_... 这种东西(如果应用确实有 /metrics)。
C. 看 agent 是否认为 merge 开启/可用
# 看你 Pod 上的注解(这些是 agent 决定是否去抓应用的依据)
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.containers[?(@.name=="istio-proxy")].env}' | grep -i merge || true
kubectl get pod <pod> -n <ns> -o jsonpath='{.metadata.annotations}' | python3 -m json.tool | grep prometheus
5) 生产抓哪个端口?(抓错就会导致“缺指标/多指标/冲突”)
|
你想要的 |
应该 scrape |
理由 |
|---|---|---|
|
只看 proxy 行为(流量/熔断/离群) |
|
干净、最小面 |
|
proxy + 应用一起拿,且希望一个端点解决 |
|
merge 设计点;但要防同名冲突 |
|
应用指标你自己另有 scrape job 抓应用 svc |
只抓 15090 |
解耦更干净(Istio 与应用互不打扰) |
如果你告诉我:
-
istioctl version(或 Istio 版本) -
kubectl get pod <pod> -n <ns> -o jsonpath='{.metadata.annotations}'里 prometheus 相关注解 -
meshConfig.enablePrometheusMerge现在是 true 还是 false
我可以把你这个 Pod 的 “15020/stats/prometheus 到底会去哪抓应用、为啥抓/为啥不抓” 按真实配置逐条推导出来(精确到它会访问 127.0.0.1:哪个port/path)。

这张图展示了 Kiali 图形化界面的“服务拓扑图”(Service Graph),它是 Istio 服务网格可视化的核心功能。
结合我们之前的讨论,这张图不仅仅是“画图”,它本质上是 Kiali 对 Prometheus 数据的实时翻译。以下是这张图的具体技术解读:
1. 核心元素与 Prometheus 的关系
-
节点(方块):代表 Application(应用)。
-
例如图中的
productpage、reviews、ratings。 -
技术点:Kiali 通过读取 Kubernetes 的 Label(如
app=productpage)来识别这些节点。
-
-
连线(箭头):代表 调用关系(Edges)。
-
箭头从调用方指向被调用方(例如
productpage->reviews)。 -
技术点:这些连线不是凭空画的。Kiali 会向 Prometheus 发送查询,寻找带有特定标签的请求指标(如
istio_requests_total),如果发现 A 服务有大量请求发往 B 服务,就会画出这条线。
-
-
边的粗细:代表 TPS (Transactions Per Second)。
-
右侧面板显示
Total: 0.53,对应图中较细的灰线,说明当前流量很小(每秒约 0.5 个请求)。
-
-
边的颜色:代表 HTTP 响应成功率。
-
绿色:100% 成功(右侧
%Success: 100.00)。 -
红色:代表有错误(5xx)。
-
黄色:代表重定向(3xx)。
-
2. 特殊节点:istio-ingressgateway
-
左侧紫色的菱形节点是 Ingress Gateway。
-
它表明外部流量是从这里进入网格的。图中显示
*->productpage,说明所有外部请求都先打到productpage服务。
3. 图中的细节:版本聚合(V1, V2, V3)
-
注意看
reviews节点,它旁边标有v1,v2,v3。 -
虽然图中它们看起来像一个整体,但这实际上是 多个 Pod 的集合。
-
关联之前的知识点:如果你配置了灰度发布(VirtualService 权重),Kiali 会通过查询 Prometheus 中不同子集(Subset)的指标,在图上用不同颜色或标注来显示流量的分配比例。目前图中显示为统一的浅蓝色,说明可能所有版本都在接收流量,或者权重配置尚未生效/未区分颜色。
4. 如何验证数据来源?
你可以回想一下我们在上一轮提到的 Prometheus 抓取。图中右侧的实时数据(0.53 TPS, 100% Success),正是来源于 Prometheus 中类似以下的查询结果:
sum(rate(istio_requests_total{destination_service="reviews.bookinfo.svc.cluster.local", response_code!~"5.*"}[1m])) by (source_app, destination_app)
总结:
这张图目前展示了一个 健康(全绿)但低流量(0.53 TPS) 的 Bookinfo 应用状态。如果你想要看到更复杂的流量分配(比如红色的故障注入效果,或者不同粗细的灰度分流线),需要在 productpage页面刷新几次,或者调整 VirtualService 的配置,图中的线条粗细和颜色会随之动态变化。

想要自己手搓一个类似 Kiali 的服务拓扑图,这是一个非常棒的学习目标!这项技术通常被称为 Service Dependency Graph(服务依赖图谱) 或 Topology Map(拓扑地图)。
要实现这个功能,不能只盯着前端画图,它的核心在于“数据驱动视图”。整个功能的实现可以分为三个步骤:底层数据采集、后端指标计算、前端可视化渲染。
以下是为你拆解的实现逻辑和对应的技术栈建议:
第一步:底层数据采集(Data Collection)
就像我们之前聊的,拓扑图不是凭空画出来的,它依赖于 Metrics(指标)。
-
数据源:你需要一个像 Prometheus 一样的时序数据库。
-
抓取内容:你需要抓取服务间的调用指标。关键的指标通常是
requests_total(请求总数)或者istio_requests_total。 -
标签(Labels):这是最重要的。指标必须包含以下标签,才能计算出拓扑关系:
-
source_service/source_app:流量发起方。 -
destination_service/destination_app:流量接收方。 -
response_code:响应码(用来算成功率,给连线变色)。 -
time:时间戳(用来算 QPS/TPS)。
-
第二步:后端逻辑计算(Graph Building)
前端不直接去查 Prometheus(太慢且频繁查询容易把 Prometheus 搞挂),而是由后端定时计算好,推给前端。
核心算法逻辑(伪代码思路):
-
获取所有服务列表:从 K8s API 获取当前的服务列表,或者在指标中发现有哪些服务。
-
构建节点(Nodes):每个服务就是一个节点。
-
构建连线(Edges):
-
后端执行 PromQL 查询:
sum(rate(requests_total[1m])) by (source_app, destination_app)。 -
遍历查询结果。
-
如果发现
source_app=A调用了destination_app=B,就在 A 和 B 之间画一条边。
-
-
计算边的权重和颜色:
-
粗细(Weight):取查询到的 QPS 数值,传给前端作为线的宽度。
-
颜色(Color):判断
response_code的分布。如果是 2xx/3xx,设为绿色;如果是 5xx,设为红色;如果是 4xx,设为黄色。
-
第三步:前端可视化渲染(Visualization)
这是你最关心的部分。前端需要一个强大的图形库来接管 DOM 操作。
推荐技术栈
-
框架:React 或 Vue。现代可视化面板的主流选择。
-
图表库(核心):强烈推荐 Cytoscape.js 。
-
为什么选它:Kiali 底层其实也是用的类似的库(Cytoscape.js 是一个基于图论的高度可定制的可视化引擎)。它专门用来画这种复杂的网络关系图、拓扑图。它支持节点的拖拽、连线的弯曲、点击事件绑定等。
-
-
UI 组件库:Ant Design 或 Material-UI,用于快速搭建左侧菜单、右上角的图例、时间范围选择器等外壳。
前端实现逻辑
-
API 接口定义:
-
前端通过
axios向后端请求数据,例如GET /api/topology?namespace=bookinfo。 -
后端返回一个 JSON 结构,包含
nodes数组和edges数组。
{ "nodes": [ {"id": "productpage", "name": "Product Page", "type": "app", "version": "v1"}, {"id": "reviews", "name": "Reviews", "type": "app", "version": "v1"} ], "edges": [ { "source": "productpage", "target": "reviews", "qps": 5.2, "rate": "100%", "color": "green" } ] } -
-
图谱初始化(以 Cytoscape.js 为例):
-
前端收到 JSON 后,将其转化为 Cytoscape 的 Elements 格式。
-
配置样式(Style):告诉图谱如何渲染。
const elements = [ // 节点 { data: { id: 'productpage', label: 'productpage' } }, { data: { id: 'reviews', label: 'reviews' } }, // 连线 { data: { id: 'edge1', source: 'productpage', target: 'reviews', qps: 5.2 // 透传数据用于tooltip提示 } } ]; const cy = cytoscape({ container: document.getElementById('graph-container'), // 挂载点 elements: elements, style: [ // 样式配置 { selector: 'node', style: { 'shape': 'rectangle', // 节点形状 'background-color': '#007bff', // 默认颜色 'label': 'data(label)' // 显示名称 } }, { selector: 'edge', style: { 'width': 'data(qps)', // 宽度由 qps 决定 'line-color': 'data(color)', // 颜色由 color 决定 'target-arrow-color': 'data(color)', // 箭头颜色 'target-arrow-shape': 'triangle', // 箭头形状 'curve-style': 'bezier' // 连线弯曲 } } ], layout: { name: 'cose' } // 布局算法,自动计算节点位置 }); -
-
交互功能:
-
Tooltip(悬浮提示):监听节点的
mouseover事件,弹出一个卡片显示该服务的 QPS、延迟、错误率。 -
点击钻取:监听节点的
click事件,跳转到该服务的详情页。 -
图例(Legend):如你图中右下角所示,用一个简单的 HTML 表格,绑定上面返回的统计数据(Total, %Success, %Error)。
-
总结你的开发路线图
-
MVP 1.0(画静态图):
-
先用假数据(Hardcode 一个 JSON)写一个 React + Cytoscape 的 Demo。
-
搞定节点的显示和连线的粗细、颜色。
-
-
MVP 2.0(接入真实数据):
-
写一个简单的 Python/Go 后端。
-
对接 Prometheus,解析出节点和边。
-
前端定时(比如每 15 秒)轮询一次后端接口刷新图谱。
-
-
MVP 3.0(完善体验):
-
加上时间范围选择器。
-
加上搜索框(Find/Hide 功能)。
-
加上图例和统计面板。
-
只要打通了 Prometheus 查询指标 -> 后端解析成图结构 -> Cytoscape 渲染 这一套闭环,你就完全掌握这类微前端监控面板的核心原理了。
这是一个非常有深度的架构问题。从 Prometheus(指标) 切换到 Jaeger(追踪) 来绘制拓扑图,不仅仅是换了一个数据源,而是从“统计聚合思维”变成了“图遍历思维”。
核心算法调整可以归纳为以下四个维度的根本性变化:
1. 数据模型的根本转变
|
维度 |
Prometheus (Metrics) |
Jaeger (Tracing) |
|---|---|---|
|
数据单元 |
时间序列点 (Time-series points) |
调用链 (Trace / Span) |
|
核心实体 |
Counter, Gauge, Histogram |
Span (操作单元) |
|
关系表达 |
隐式的(通过标签 |
显式的(Span 中有明确的 |
|
时效性 |
准实时(抓取周期决定) |
近实时(请求结束即上报) |
2. 核心算法逻辑的调整
这是最关键的部分。原来的算法是“查询-统计”,现在的算法是“解析-构建”。
调整前:基于 Prometheus 的算法(统计法)
-
查询:
sum(rate(istio_requests_total[1m])) by (source_app, dest_app) -
计算:得到一张二维表(A -> B, 10 QPS)。
-
绘图:把表中的行变成线。
调整后:基于 Jaeger 的算法(图构建法)
你需要实现一个 Dependency Graph Builder(依赖图构建器)。
核心逻辑伪代码:
# 1. 从 Jaeger 获取最近 N 分钟的 Trace 数据 (通过 Jaeger Query API)
traces = fetch_traces_from_jaeger(start_time, end_time)
# 2. 初始化图结构 (可以使用 networkx 或自行维护 map)
graph = defaultdict(lambda: {"in": 0, "out": 0, "edges": set()})
# 3. 遍历每一个 Trace
for trace in traces:
# 4. 遍历 Trace 中的每一个 Span
for span in trace.spans:
service = span.process.service_name
# 5. 提取父子关系 (关键步骤)
parent_span = find_parent(span)
if parent_span:
parent_service = parent_span.process.service_name
# 6. 构建边 (Edge)
edge_key = f"{parent_service}->{service}"
# 7. 统计权重 (不再是 QPS,而是调用次数 Call Count)
graph[edge_key]["call_count"] += 1
# 8. 统计错误率 (通过 Span Tag 判断)
if span.tags.get("error") == "true":
graph[edge_key]["error_count"] += 1
# 9. 统计延迟 (P99, P95)
duration = span.duration_ms
graph[edge_key]["latency_sum"] += duration
3. 关键的技术挑战与解决方案
|
挑战 |
原因 |
解决方案 |
|---|---|---|
|
数据量级爆炸 |
一个 Trace 可能包含几十上百个 Span,全量存储成本高。 |
采样(Sampling):只分析 1% 或 10% 的 Trace 数据来构建拓扑。 |
|
实时性 vs 准确性 |
拓扑图需要实时更新,但 Trace 数据量大,全量计算慢。 |
流式计算(Streaming):使用 Flink/Spark Streaming 消费 Jaeger 的 Kafka 数据流,实时更新图结构。 |
|
孤儿 Span |
有些 Span 可能没有 Parent ID,导致断链。 |
服务映射(Service Mapping):如果找不到父 Span,尝试通过 |
|
聚合粒度 |
一个服务可能调用下游很多个接口,拓扑图会太乱。 |
服务级聚合:忽略具体的 Operation Name(如 |
4. 前端渲染逻辑的微调
前端(如 Cytoscape.js)的代码基本不用改,但传入的数据含义变了:
-
边的粗细(Width):
-
原来是
QPS(每秒请求数)。 -
现在是
Call Count(总调用次数)或Calls per Minute。
-
-
边的颜色(Color):
-
原来是
HTTP Status Code(2xx/5xx)。 -
现在是
Error Rate(错误 Span 数 / 总 Span 数)或Latency(P99 延迟)。
-
-
节点大小(Size):
-
可以根据该服务产生的 Span 总数来决定节点大小,表示流量中心度。
-
5. 推荐的架构实现(生产级)
如果你想做一个健壮的系统,建议采用 双数据源互补 的方案:
-
Jaeger 负责“骨架”:用 Jaeger 的数据来确定 “谁调用了谁”(拓扑结构)。因为追踪数据是 100% 准确的调用关系。
-
Prometheus 负责“血肉”:用 Prometheus 的数据来确定 “调用有多快/多频繁”(QPS、CPU、内存、成功率)。因为指标数据聚合效率高,适合做实时的仪表盘。
开发建议:
如果你现在要动手开发,可以先用 Jaeger 的 dependenciesAPI(如果开启了 Spark Dependencies Job)。Jaeger 本身自带一个后台任务,会定时从 Trace 中计算依赖关系,并提供一个简单的 API 接口,你可以直接调用这个接口来获取现成的拓扑数据,省去自己写图构建算法的麻烦。
总结:
从 Prometheus 切换到 Jaeger,你的算法重心要从 “写 PromQL 查询” 转变为 “写图遍历和聚合逻辑”。你需要处理的是离散的事件流,而不是连续的时序流。
posted on 2026-05-22 16:40 luzhouxiaoshuai 阅读(13) 评论(0) 收藏 举报
浙公网安备 33010602011771号