蛮荆

Kubernetes 应用最佳实践 - 金丝雀发布

2023-05-01

概述

金丝雀发布是一种软件发布策略,其中应用的新版本被发布到一小部分用户客户端进行测试,这些用户就像 “金丝雀” 一样,可以提供对新版本功能和性能的真实反馈。 新版本如果没有问题,可以逐步扩大到更多的用户客户端,如果出现问题时,可以迅速回滚或修复。金丝雀发布核心目的在于减少对已有系统的潜在影响,提高发布过程的可控性

图片来源: https://www.amazon.com/Kubernetes-Patterns-Designing-Cloud-Native-Applications/dp/1492050288

和灰度发布的差异

国内很多开发者直接把金丝雀发布和灰度发布混为一谈,这里简单说下两者的区别。

灰度发布是一种将应用新版本逐步更新到客户端的方式

在灰度发布中,新版本会先提示一小部分用户进行更新,然后根据用户反馈和系统数据逐步扩大更新范围。 和金丝雀发布相同的是,灰度发布也是通过一小部分流量来获取真实数据反馈,和金丝雀发布不同的是,灰度发布的主要目的是逐渐引入新版本,而不仅仅是进行测试。

示例

我们经常看到 APP 的灰度更新提示,下面是两个国民级应用的截图。

支付宝版本升级提示

微信版本升级提示

Kubernetes 集成

我们可以利用 Kubernetes 中 Deployment + Ingress 两者的结合,简单平滑地将金丝雀发布流程集成到 Kubernetes 架构体系中。

具体的流程如下:

  1. 创建应用的金丝雀版本 (新版本) 镜像和 Deployment (资源请求和限制,副本数量,HPA 等)
  2. 配置服务发现,为金丝雀版本应用 Pod 配置专属的 Service
  3. 流量切分,将生产环境的一小部分流量切分到金丝雀版本中
  4. 持续监控 (业务稳定三板斧: 日志, 监控指标, 链路追踪)
  5. 渐进式更新 (逐步加大金丝雀版本的流量比例) 或及时回滚 (只需要将金丝雀版本流量比例重置为 0 即可)
  6. 删除旧的生产 Service 和 Deployment

Kubernetes 金丝雀请求流程

业务流程

上面谈到了 Kubernetes 中金丝雀发布的技术方案,下面简单说一下业务方面需要哪些配合工作。

  1. 后台发放金丝雀版本的客户端 (用户) 名额,可以根据用户画像发放,也可以随机发放
  2. 客户端收到金丝雀版本更新后,提示当前用户是否更新到金丝雀版本
  3. 用户下载更新金丝雀版本应用 (例如 Android 绕过应用商城直接下载安装,IOS 通过官方 TestFlight 方案)
  4. 金丝雀版本应用访问时,流量被切分到金丝雀版本的 Pod, 产生对应的日志, 监控指标, 链路追踪数据

Kubernetes 金丝雀日志示例


Kubernetes 金丝雀日志示例2

流量切分

上文中谈到的将生产环境的一小部分流量切分到金丝雀版本中,下面来介绍两种常见的业务金丝雀部署场景。 限于篇幅,本文仅讨论使用 Kubernetes 原生功能作为流量切分方案,不考虑 Istio, Linkerd 等 Service Mesh 方案。

REST 接口不同

这种实现方案是最简单的,我们可以直接使用 Ingress + Service 来实现,通过将不同版本的接口路由到不同的服务即可,例如:

  • 当前运行版本的路由 /api/v1/users/:id/profile 流量切分到生产应用版本的 Service
  • 金丝雀版本的路由 /api/v2/users/:id/profile 流量切分到金丝雀版本的 Service

其中,Deployment 对应的 yaml 声明式代码大致如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-app
  labels:
    version: prod # 标签 = prod
spec:
  # 定义生产版本的 Deployment

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-app
  labels:
    version: canary # 标签 = canary
spec:
  # 定义金丝雀版本的 Deployment

Service 对应的 yaml 声明式代码大致如下:

apiVersion: v1
kind: Service
metadata:
  name: user-app-prod-service
spec:
  selector:
    version: prod  # 将流量路由到标签为 prod 的 Pod
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

---

apiVersion: v1
kind: Service
metadata:
  name: user-app-canary-service
spec:
  selector:
    version: canary  # 将一部分流量路由到标签为 canary 的 Pod
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080 

Ingress 对应的 yaml 声明式代码大致如下 (这里以 Nginx Ingress Controller 为例):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: user-app-ingress
spec:
  rules:
    - host: dbwu.tech
    - http:
        paths:
          - path: /api/v1/users
            backend:
              service:
                name: user-app-prod-service
                port:
                  number: 80
              weight: 90 # 生产环境版本切分 90% 的流量
          - path: /api/v2/users
            backend:
              service:
                name: user-app-canary-service
                port:
                  number: 80
              weight: 10 # 金丝雀版本切分 10% 的流量

REST 接口版本相同

现实中更常见的场景是: 应用的生产版本和金丝雀版本使用相同的 REST 接口,但是需要将流量切分到不同版本中,同样可以使用 Ingress 来实现。

Nginx Ingress Controller 下列 canary-* Annotation 来支持金丝雀发布机制,下面是几个常用字段的描述。

字段 说明
nginx.ingress.kubernetes.io/canary true:启用 canary false:不启用 canary
nginx.ingress.kubernetes.io/canary-by-header 基于请求头的名称进行金丝雀发布
nginx.ingress.kubernetes.io/canary-by-header-value 基于请求头的值进行金丝雀发布
nginx.ingress.kubernetes.io/canary-by-header-value 基于请求头的值进行金丝雀发布
nginx.ingress.kubernetes.io/canary-weight 基于权重进行金丝雀发布 (0 - 100)

Service 对应的 yaml 声明式代码直接复用上文中的即可,这里不再赘述,Ingress 对应的 yaml 声明式代码大致如下:

# 生产版本 Ingress 配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: prod
  annotations:
spec:
  ingressClassName: nginx
  rules:
    - host: dbwu.tech
      http:
        paths:
          - path: /api/v1/users
            backend:
              service:
                name: user-app-prod-service # 生产环境版本
                port:
                  number: 80


---

# 金丝雀版本 Ingress 配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary
  annotations:
    # 启用金丝雀发布
    nginx.ingress.kubernetes.io/canary: "true"
    # 这里以 Request.Header 中的 app_release_version 作为金丝雀流量标识名称 
    nginx.ingress.kubernetes.io/canary-by-header: "app_release_version"
    #  这里以 v1.2.3 版本号作为金丝雀流量标识值
    nginx.ingress.kubernetes.io/canary-by-header-value: "v1.2.3"
    # 给金丝雀版本切分 10% 的流量
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
    - host: dbwu.tech
      http:
        paths:
          - path: /api/v1/users
            backend:
              service:
                name: user-app-canary-service # 金丝雀版本
                port:
                  number: 80

开箱即用的方案

如果使用云计算服务商,那么只需要点几下鼠标,改几个配置参数就可以了 :-)

下面以笔者在工作中使用的阿里云为例演示。

阿里云金丝雀发布演示 - 1

阿里云金丝雀发布演示 - 2

阿里云金丝雀发布演示 - 3

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。