⚠️ 如何在不中断服务的情况下部署新版本,不懂这个你会后悔!

想象一下,您需要向用户部署一个新增了功能的v2版本。🧐 如果您一次性将所有流量都发送到v2,会发生什么?一旦出现意想不到的bug,所有用户都可能受到影响,这将是一个可怕的局面。😱

为了解决这个问题,我们采用了金丝雀部署(Canary Deployment)、蓝绿部署(Blue/Green Deployment)等策略。通过将特定用户组或部分流量发送到新版本,来验证其稳定性。

在Kubernetes环境中,使用服务网格(Service Mesh)的代表性产品Istio,可以非常优雅且强大地控制这一过程。今天,我们将深入探讨Istio如何将流量发送到特定版本的服务,并剖析其核心概念!

image


🤔 问题的开端: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。

  1. VirtualService (虚拟服务) 👮
  • 作用:“去哪里?”定义流量路由规则。
  • 说明:它就像一个交通警察,根据特定条件(头部、URI、来源等)决定将进入`my-service`的流量发送到哪里。例如,定义“如果URI以`/api/v2`开头,则发送到v2服务”或“将90%的流量发送到v1,10%发送到v2”之类的规则。
  1. 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文件所做的事情很简单。

  1. 通过`host`字段声明此规则将应用于`my-service`。
  2. 在`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`作为目标,设置根据权重分配流量或根据特定条件路由的规则。(指挥交通 👮)

通过这两者的完美结合,您现在拥有了能够自信处理任何复杂部署场景的强大武器!


Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注