You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
6.9 KiB
224 lines
6.9 KiB
3 years ago
|
|
||
|
# 什么是灰度发布
|
||
|
灰度发布又名金丝雀发布,
|
||
|
> 是指在黑与白之间,能够平滑过渡的一种发布方式。 在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。–维基百科
|
||
|
|
||
|
假定线上版本为A, 需要发布的版本为B,通常是新版本。
|
||
|
灰度发布可以让一定百分比的用户(比如10%)优先体验版本B,其余用户仍然使用版本A,并且慢慢扩大百分比,最终将所用用户迁移到版本B。
|
||
|
|
||
|
![](https://cdn.jsdelivr.net/gh/vinloong/imgchr@latest/notes/img/202201191023493.png)
|
||
|
|
||
|
|
||
|
# 为什么需要灰度发布
|
||
|
随着微服务架构的普及,服务数量激增,版本更新频繁,如果缺少灰度的能力,容易对现有的生产运行业务造成影响,并且新上线的系统和功能也需要灰度的能力来验证可行性和效果,简而言之,无论是对于系统运行稳定安全还是对于验证业务效果,灰度发布/验证的能力都是现代软件架构所必须的。
|
||
|
|
||
|
灰度发布有一个隐藏的前提条件,线上资源远大于生产团队的规模;
|
||
|
以至于生产团队没有足够的资源来应付使用新版本后带来的问题,包括用户不习惯和数据量所带来的不适应和线上bug等等。
|
||
|
|
||
|
而灰度发布可以通过控制发布面积,让使用新版本的用户控制在生产团队可以应付的范围内;将可能出现问题的用户圈定在“部分”用户,这个部分的范围由生产团队评估得出。
|
||
|
|
||
|
我们通过灰度发布,将一部分经过筛选后的用户纳入测试范围,由生产团队和用户共同完成对新版本的验收;主要的目的是检验了用户对于新版本的接受度,规避了因为产品决策冒失、冒进所带来的风险。
|
||
|
|
||
|
# 问题
|
||
|
## 用户标识
|
||
|
用于区分用户,辅助数据统计,并且保证灰度发布过程中用户体验的连贯性(不要在新老版本中跳来跳去)。
|
||
|
|
||
|
## 目标用户的选取策略
|
||
|
如何选取让哪些用户先行体验新版本,是让用户自己选择还是强制升级等。
|
||
|
|
||
|
## 数据
|
||
|
如何能够获取新版本的指标数据
|
||
|
|
||
|
## 回滚策略
|
||
|
新版本表现不佳,如何快速回滚到老版本。如果是app 回滚可能代价比较大。
|
||
|
|
||
|
|
||
|
# 灰度策略
|
||
|
## 按流量比例
|
||
|
|
||
|
|
||
|
![](https://cdn.jsdelivr.net/gh/vinloong/imgchr@latest/notes/img/202201191021433.png)
|
||
|
|
||
|
基于流量就是设置流量权重,实现上最简单,但是流量不稳定,会使用户使用有割裂感。
|
||
|
|
||
|
## 按请求内容
|
||
|
![[../Pasted image 20210604153710.png]]
|
||
|
这个分为两种:基于 cookie 和 请求 Header;
|
||
|
这种相对于前面一种就会相对复杂点,需要请求端设置 Header或cookie。这种用户体验会好很多,能够保证用户体验的连贯性。
|
||
|
|
||
|
|
||
|
> 注意:金丝雀规则具有以下优先级:
|
||
|
|
||
|
> `canary-by-header` - > `canary-by-cookie` - > `canary-weight`
|
||
|
|
||
|
# 灰度发布实践
|
||
|
我们商用环境k8s 管理是使用的 `KubeSphere` ,所以下面的实践都是利用 `KubeSphere` 来实现
|
||
|
|
||
|
简单自定义一个接口服务:
|
||
|
|
||
|
```js
|
||
|
const main = async function(ctx) {
|
||
|
|
||
|
ctx.body ="this is canary test";;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
const about = ctx => {
|
||
|
|
||
|
ctx.body = "this is version 2";
|
||
|
// ctx.body = "this is version 1";
|
||
|
|
||
|
};
|
||
|
```
|
||
|
|
||
|
|
||
|
分别构建了两个镜像:
|
||
|
```
|
||
|
# Production 版本
|
||
|
repository.anxinyun.cn/base-images/demo:0.1
|
||
|
|
||
|
# Canary 版本
|
||
|
repository.anxinyun.cn/base-images/demo:0.3
|
||
|
```
|
||
|
|
||
|
## 基于 `ingress-nginx`
|
||
|
### 1. 我们先部署一个 `Production` 版本的应用
|
||
|
```yaml
|
||
|
apiVersion: apps/v1
|
||
|
kind: Deployment
|
||
|
metadata:
|
||
|
name: gated-demo
|
||
|
namespace: fs-gtest
|
||
|
spec:
|
||
|
replicas: 1
|
||
|
selector:
|
||
|
matchLabels:
|
||
|
app: gated-demo
|
||
|
template:
|
||
|
metadata:
|
||
|
labels:
|
||
|
app: gated-demo
|
||
|
spec:
|
||
|
containers:
|
||
|
- name: gated-demo
|
||
|
image: repository.anxinyun.cn/base-images/demo:0.1
|
||
|
ports:
|
||
|
- containerPort: 5000
|
||
|
name: http-port
|
||
|
|
||
|
---
|
||
|
|
||
|
apiVersion: v1
|
||
|
kind: Service
|
||
|
metadata:
|
||
|
name: gated-demo
|
||
|
namespace: fs-gtest
|
||
|
labels:
|
||
|
app: gated-demo
|
||
|
spec:
|
||
|
ports:
|
||
|
- port: 15001
|
||
|
targetPort: 5000
|
||
|
protocol: TCP
|
||
|
name: http-port
|
||
|
selector:
|
||
|
app: gated-demo
|
||
|
```
|
||
|
|
||
|
```shell
|
||
|
$ kubectl appy -f production.yaml
|
||
|
|
||
|
deployment.apps/production created
|
||
|
service/production created
|
||
|
|
||
|
```
|
||
|
### 2. 创建 `Production` 版本的应用路由 (Ingress)
|
||
|
|
||
|
```yaml
|
||
|
kind: Ingress
|
||
|
apiVersion: extensions/v1beta1
|
||
|
metadata:
|
||
|
name: gated-demo
|
||
|
namespace: fs-gtest
|
||
|
annotations:
|
||
|
kubernetes.io/ingress.class: nginx
|
||
|
kubesphere.io/creator: admin
|
||
|
spec:
|
||
|
rules:
|
||
|
- host: gated-demo.fs-gtest.10.8.30.157.nip.io
|
||
|
http:
|
||
|
paths:
|
||
|
- path: /
|
||
|
pathType: ImplementationSpecific
|
||
|
backend:
|
||
|
serviceName: gated-demo
|
||
|
servicePort: 15001
|
||
|
```
|
||
|
|
||
|
### 3. 访问`Production` 接口
|
||
|
|
||
|
![](https://cdn.jsdelivr.net/gh/vinloong/imgchr@latest/notes/img/202201191024380.png)
|
||
|
|
||
|
### 4. 下面部署 `Canary` 版本
|
||
|
yaml 文件 参考上面的版本,在 `gated-demo` 加上 `-canary`
|
||
|
|
||
|
### 5. 设置 Ingress-Nginx Annotation 规则
|
||
|
> 要开启灰度发布机制,需设置 `nginx.ingress.kubernetes.io/canary: "true"` 启用 Canary
|
||
|
#### 基于权重
|
||
|
```yaml
|
||
|
kind: Ingress
|
||
|
apiVersion: extensions/v1beta1
|
||
|
metadata:
|
||
|
name: gated-demo-canary
|
||
|
namespace: fs-gtest
|
||
|
annotations:
|
||
|
kubernetes.io/ingress.class: nginx
|
||
|
kubesphere.io/creator: admin
|
||
|
nginx.ingress.kubernetes.io/canary: 'true'
|
||
|
nginx.ingress.kubernetes.io/canary-weight: "30"
|
||
|
spec:
|
||
|
rules:
|
||
|
- host: gated-demo.fs-gtest.10.8.30.157.nip.io
|
||
|
http:
|
||
|
paths:
|
||
|
- path: /
|
||
|
pathType: ImplementationSpecific
|
||
|
backend:
|
||
|
serviceName: gated-demo-canary
|
||
|
servicePort: 15002
|
||
|
```
|
||
|
> 上面 Ingress 示例的 Canary 版本使用了**基于权重进行流量切分**的 annotation 规则,将分配 **30%** 的流量请求发送至 Canary 版本
|
||
|
|
||
|
#### Request Header
|
||
|
```yaml
|
||
|
kind: Ingress
|
||
|
apiVersion: extensions/v1beta1
|
||
|
metadata:
|
||
|
name: gated-demo-canary
|
||
|
namespace: fs-gtest
|
||
|
annotations:
|
||
|
kubernetes.io/ingress.class: nginx
|
||
|
kubesphere.io/creator: admin
|
||
|
nginx.ingress.kubernetes.io/canary: 'true'
|
||
|
nginx.ingress.kubernetes.io/canary-by-header: version
|
||
|
nginx.ingress.kubernetes.io/canary-by-header-value: '2'
|
||
|
spec:
|
||
|
rules:
|
||
|
- host: gated-demo.fs-gtest.10.8.30.157.nip.io
|
||
|
http:
|
||
|
paths:
|
||
|
- path: /
|
||
|
pathType: ImplementationSpecific
|
||
|
backend:
|
||
|
serviceName: gated-demo-canary
|
||
|
servicePort: 15002
|
||
|
```
|
||
|
![](https://cdn.jsdelivr.net/gh/vinloong/imgchr@latest/notes/img/202201191025017.png)
|
||
|
当 header 'version' 设置 为 '2' 时,所有请求被转到 Canary 版本
|
||
|
|
||
|
# 总结
|
||
|
上面是简单的灰度测试,实际商用还是考虑灰度策略问题如何与用户结合起来控制,可以指定哪些用户参与灰度测试。
|
||
|
|