Kubernetes 应用最佳实践 - 金丝雀发布
2023-05-01 Cloud Native Kubernetes Kubernetes 应用最佳实践
概述
金丝雀发布是一种软件发布策略,其中应用的新版本被发布到一小部分用户客户端进行测试,这些用户就像 “金丝雀” 一样,可以提供对新版本功能和性能的真实反馈。 新版本如果没有问题,可以逐步扩大到更多的用户客户端,如果出现问题时,可以迅速回滚或修复。金丝雀发布核心目的在于减少对已有系统的潜在影响,提高发布过程的可控性。
和灰度发布的差异
国内很多开发者直接把金丝雀发布和灰度发布混为一谈,这里简单说下两者的区别。
灰度发布是一种将应用新版本逐步更新到客户端的方式。
在灰度发布中,新版本会先提示一小部分用户进行更新,然后根据用户反馈和系统数据逐步扩大更新范围。 和金丝雀发布相同的是,灰度发布也是通过一小部分流量来获取真实数据反馈,和金丝雀发布不同的是,灰度发布的主要目的是逐渐引入新版本,而不仅仅是进行测试。
示例
我们经常看到 APP 的灰度更新提示,下面是两个国民级应用的截图。
Kubernetes 集成
我们可以利用 Kubernetes 中 Deployment + Ingress 两者的结合,简单平滑地将金丝雀发布流程集成到 Kubernetes 架构体系中。
具体的流程如下:
- 创建应用的金丝雀版本 (新版本) 镜像和 Deployment (资源请求和限制,副本数量,HPA 等)
- 配置服务发现,为金丝雀版本应用 Pod 配置专属的 Service
- 流量切分,将生产环境的一小部分流量切分到金丝雀版本中
- 持续监控 (业务稳定三板斧: 日志, 监控指标, 链路追踪)
- 渐进式更新 (逐步加大金丝雀版本的流量比例) 或及时回滚 (只需要将金丝雀版本流量比例重置为 0 即可)
- 删除旧的生产 Service 和 Deployment
业务流程
上面谈到了 Kubernetes 中金丝雀发布的技术方案,下面简单说一下业务方面需要哪些配合工作。
- 后台发放金丝雀版本的客户端 (用户) 名额,可以根据用户画像发放,也可以随机发放
- 客户端收到金丝雀版本更新后,提示当前用户是否更新到金丝雀版本
- 用户下载更新金丝雀版本应用 (例如 Android 绕过应用商城直接下载安装,IOS 通过官方 TestFlight 方案)
- 金丝雀版本应用访问时,流量被切分到金丝雀版本的 Pod, 产生对应的日志, 监控指标, 链路追踪数据
流量切分
上文中谈到的将生产环境的一小部分流量切分到金丝雀版本中,下面来介绍两种常见的业务金丝雀部署场景。 限于篇幅,本文仅讨论使用 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
开箱即用的方案
如果使用云计算服务商,那么只需要点几下鼠标,改几个配置参数就可以了 :-)
下面以笔者在工作中使用的阿里云为例演示。