- 本文作者:zhuoooo
背景
为什么做灰度发布,其实很好理解:在不影响用户正常使用工作的前提下,引入小部分的用户来使用新版本,帮助我们测试用户对于新版本的接受程度;避免决策失误所产生的问题影响到大部分甚至全量用户,保证了产品平滑过度。
灰度发布的优点:
- 提前收集用户使用意见,及时完善产品功能
- 控制未知异常只出现在小范围内,不影响大多数用户
- 发现产品是否存在外在问题(如合规),可及时回滚至已旧版本
需求分析
业务需求
-
能够对租户进行精细化操作:
-
能够对有问题租户进行下线处置;
-
标记租户,将租户流量切分成标准版本以及抢先体验版,未来可以分割成多个版本;
-
-
能够对微服务进行精细化控制:
- 熔断处理;
- 服务降级处理;
- 能够对微服务进行性能监控;
- 灰度用户策略
技术需求
- 域名
url不可变 - 不入侵现有的业务代码
- 流量的状态标记
- 对于用户第一次访问和第 n 次访问在规则不变和相对环境不变的情况下是不是应该保持一致。否则对于用户而言就变成一个不确定的页面,每一次刷新看到的东西都有可能不一样,看运气,这肯定不是灰度过程希望出现的,因此对于前端的灰度而言流量的状态非常重要,那回过头看前面和业务端沟通的一个很重要的结果,就是用户流量状态标记,确定了业务场景中需要支持某一堂课
lessonId或者某一个角色roleId某个设备deviceId等这样的自定义状态进行灰度同时需要支持url和cookie两种方式标记,另外对于没有状态的用户需要可以通过ua方式制定某些设备终端版本的方式进行灰度。
- 对于用户第一次访问和第 n 次访问在规则不变和相对环境不变的情况下是不是应该保持一致。否则对于用户而言就变成一个不确定的页面,每一次刷新看到的东西都有可能不一样,看运气,这肯定不是灰度过程希望出现的,因此对于前端的灰度而言流量的状态非常重要,那回过头看前面和业务端沟通的一个很重要的结果,就是用户流量状态标记,确定了业务场景中需要支持某一堂课
- 变更灰度规则的时候,能够及时生效
方案
由于前端代码依赖的是客户端浏览器的运行环境,基本上不依赖服务端的环境,加上前端入口一般是一个 html 文件,然后再去加载各种静态资源;因此通过对入口文件的控制,就可以产生许多方式做到前端灰度发布的效果。如下图所示:
我们不难发现,大概有三种实现的思路:
- 通过
Nginx这类 web 服务器将用户的访问分成不同流量,然后代理到不同的代码目录; - 通过后端的服务识别用户,读取不同的
index.html返回给浏览器; - 直接通过前端硬编码的形式,显示不同的版本;
Nginx 分流
方案大概实现:
- 前端打包好的两份代码(
stable或canary)分别部署到服务器上; - 当用户请求到达前端代理服务
nginx,获取用户标识; - 通过灰度策略,识别用户的类型;
- 根据用户的类型,将请求转发到
stable或canary版本上,最后服务器返回结果。
服务端渲染
方案大概实现:
-
前端打包好的两份代码(
stable或canary)分别部署到服务器上; -
金丝雀平台设置灰度策略;
-
客户端访问服务端,服务端根据灰度策略
set-cookie并在redis存储,返回对应版本的index.html -
二次访问通过服务端的时候,如果存在
cookie并且redis已经存在对应的版本信息,则直接返回,否则重新走灰度流程
前端代码
通过代码来做灰度,其实就是根据灰度规则对应在代码层面上做判断显示哪些版本的功能,这种方案我们也使用过,灰度功能一但多了,极其难维护,不推荐,这里就不过多介绍了。
实践
在查找了一些业界资料以及结合了我们现在项目的状态后,发现上述的方案不太适合我们,或者说我们有更好的选择。
通过上面架构图,可以发现:
- 项目是由各个子应用自由组合而来的,意味着我们会做到子应用的灰度发布,这会增加
nginx的配置 - 所有的子应用都是在容器中运行的并不是在虚拟机上,那么服务流量通过
ingressroute控制 - 容器之间的静态资源是隔离的
显然,如果硬抄上述方案就不太和谐了。那么我们转化思路,整理了适合的方案:
- 需要灰度发布的应用准备两个镜像(
stable或canary) - 金丝雀平台配置灰度策略
- 通过
istio进行流量管理 - 客户端访问服务端,根据灰度策略
set-cookie,根据这个标识进行流量染色,把流量引导到对应容器中去
镜像
我们会在镜像名称上进行区分稳定版本和灰度版本,例如 portal-stable 和 portal-canary
流量染色
请求链路上各个组件如何识别出不同的灰度流量?答案就是流量染色,为请求流量添加不同灰度标识来方便区分。我们可以在请求的源头上对流量进行染色,前端在发起请求时根据用户信息或者平台信息的不同对流量进行打标。如果前端无法做到,我们也可以在微服务网关上对匹配特定路由规则的请求动态添加流量标识。此外,流量在链路中流经灰度节点时,如果请求信息中不含有灰度标识,需要自动为其染色,接下来流量就可以在后续的流转过程中优先访问服务的灰度版本。
新的项目架构
我们只需部署服务的灰度版本,流量在调用链路上流转时,由流经的网关、各个中间件以及各个微服务来识别灰度流量,并动态转发至对应服务的灰度版本。如下图:
上图可以很好展示这种方案的效果,我们用不同的颜色来表示不同版本的灰度流量,可以看出无论是微服务网关还是微服务本身都需要识别流量,根据治理规则做出动态决策。当服务版本发生变化时,这个调用链路的转发也会实时改变,帮助开发者实时快速的对线上流量进行精细化的全链路控制。
总结
如果说项目的整体框架也是从单体架构向微服务架构演进转变的话,针对业务对全链路能力的要求,前端的灰度方案全面拥抱云原生也是极好,既不需要关心 Nginx 配置,也不用指导后端如何去正确读取正确的版本,与后端的云原生共建让其在 gateway 将流量分配到对应的前端容器即可。
方案千千万,选择自己合适的就好,具体还是要结合实际的项目场景调整。