想象一下,您需要向用户部署一个新增了功能的v2版本。🧐 如果您一次性将所有流量都发送到v2,会发生什么?一旦出现意想不到的bug,所有用户都可能受到影响,这将是一个可怕的局面。😱
为了解决这个问题,我们采用了金丝雀部署(Canary Deployment)、蓝绿部署(Blue/Green Deployment)等策略。通过将特定用户组或部分流量发送到新版本,来验证其稳定性。
在Kubernetes环境中,使用服务网格(Service Mesh)的代表性产品Istio,可以非常优雅且强大地控制这一过程。今天,我们将深入探讨Istio如何将流量发送到特定版本的服务,并剖析其核心概念!

🤔 问题的开端:Kubernetes Service的局限性
首先,我们需要理解Kubernetes的基本操作。假设有一个名为my-service的Service,如下所示。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app # 查找所有带有 'app: my-app' 标签的 Pod。
ports:
- protocol: TCP
port: 80
targetPort: 8080
这个Service会将所有带有`app: my-app`标签的Pod作为其端点。如果3个v1版本的Pod和1个v2版本的Pod都带有`app: my-app`标签,那么进入`my-service`的请求将默认以轮询(Round Robin)方式分散到这4个Pod上。
- my-app Pods
- pod-v1-a (labels: app: my-app, version: v1)
- pod-v1-b (labels: app: my-app, version: v1)
- pod-v1-c (labels: app: my-app, version: v1)
- pod-v2-a (labels: app: my-app, version: v2) 🆕
在这种情况下,无法实现“我只想将请求发送到v1”或“我只想将总流量的10%发送到v2”这样的精细控制。😥
✨ Istio的解决方案二人组:VirtualService和DestinationRule
为了克服这些局限性,Istio提供了两个强大的CRD(Custom Resource Definition):VirtualService和DestinationRule。
- VirtualService (虚拟服务) 👮
- 作用:“去哪里?”定义流量路由规则。
- 说明:它就像一个交通警察,根据特定条件(头部、URI、来源等)决定将进入`my-service`的流量发送到哪里。例如,定义“如果URI以`/api/v2`开头,则发送到v2服务”或“将90%的流量发送到v1,10%发送到v2”之类的规则。
- DestinationRule (目标规则) 📖
- 作用:“可以到达的地方是哪里?”定义实际的目标(端点组)。
- 说明:在VirtualService指路之前,DestinationRule就像一本地址簿,定义了目标所具有的特性。今天的主角子集(Subsets)就在这里登场。
这两者必须始终协同工作。即使VirtualService大喊“去v2!”,如果DestinationRule中没有定义名为v2的目标组,那也无济于事。
🎯 核心概念:DestinationRule的子集(Subsets)
DestinationRule的核心功能就是定义子集(Subsets)。子集意味着根据特定标签(label)将服务的Pod逻辑地划分为不同的组。
用代码来看比用文字描述更容易理解。让我们为`my-service`定义一个DestinationRule。
DestinationRule 原始数据 (YAML)
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-service-dr
spec:
# 此规则将应用于的服务主机名
host: my-service.default.svc.cluster.local
# 将 Pod 逻辑地划分为组。
subsets:
- name: v1 # 此组的名称是 'v1'。
labels:
version: v1 # 只有带有 'version: v1' 标签的 Pod 属于此组。
- name: v2 # 此组的名称是 'v2'。
labels:
version: v2 # 只有带有 'version: v2' 标签的 Pod 属于此组。
上述YAML文件所做的事情很简单。
- 通过`host`字段声明此规则将应用于`my-service`。
- 在`subsets`字段中定义两个组。
- v1子集:带有`version: v1`标签的Pod集合 🏠
- v2子集:带有`version: v2`标签的Pod集合 🏡
现在,Istio认识到`my-service`不仅仅是4个Pod的集合,而是由两个带有v1和v2标签的明确组构成。
🚀 利用子集:与VirtualService的联动
现在我们已经用DestinationRule创建了地址簿,接下来轮到VirtualService这个交通警察根据这个地址簿来指路了。
金丝雀部署示例:将90%的流量发送到v1,10%发送到v2
VirtualService 原始数据 (YAML)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-service-vs
spec:
hosts:
- my-service.default.svc.cluster.local # 对于所有进入 my-service 的流量
http:
- route:
- destination:
host: my-service.default.svc.cluster.local
subset: v1 # 发送到 'DestinationRule' 中定义的 'v1' 子集
weight: 90 # 权重为 90%
- destination:
host: my-service.default.svc.cluster.local
subset: v2 # 发送到 'DestinationRule' 中定义的 'v2' 子集
weight: 10 # 权重为 10%
请看VirtualService的`http.route`部分。`destination`中有一个`subset`字段。如果在这里准确填写DestinationRule中定义的子集`name`(v1, v2),Istio就会将流量仅传递给带有该标签的Pod组。
通过这种方式组合这两个资源,您无需更改一行代码,只需修改YAML文件,即可完美控制服务的流量流向。✨
❌ 为什么这不是正确答案?
Istio有多种资源,可能会让人感到困惑。让我们明确Subsets与其他概念的区别。
- ServiceEntry:用于注册不包含在服务网格中的外部服务,以便Istio能够识别它们。例如,当您希望像调用内部网格服务一样调用外部云数据库API时使用。它的目的与划分内部网格服务的版本完全不同。
- Gateway:管理网格边缘(edge)的进出流量(Ingress/Egress)。也就是说,它充当一个网关,定义集群外部的请求如何进入内部。一旦流量进入,再根据内部服务版本进行路由,这与Subsets的角色位置不同。
- PodSelector:Kubernetes的基本概念,用于选择带有特定标签的Pod。尽管DestinationRule的`subsets`内部使用了标签选择器,但在Istio DestinationRule中,逻辑上命名和定义特定版本Pod组的Istio官方概念是子集(Subsets)。
💡 总结
如果您想在不中断服务的情况下安全地部署新版本,理解Istio的DestinationRule和VirtualService是必不可少的。
- DestinationRule通过`subsets`将服务的Pod定义为有意义的组,例如`version: v1`和`version: v2`。(创建地址簿 📖)
- VirtualService使用这些定义的`subsets`作为目标,设置根据权重分配流量或根据特定条件路由的规则。(指挥交通 👮)
通过这两者的完美结合,您现在拥有了能够自信处理任何复杂部署场景的强大武器!
发表回复