灰度更新的设计
在聊了上面的这些知识后,来看一下灰度更新。上图是灰度更新的简易示例图,流程是从左开始到右边结束。
第一步是创建灰度服务,创建后可以更新灰度。比如刚才的 Nginx 的例子,我们创建的版本号是 1.19。但是在灰度过程中发现当前版本有 bug,而在对这个 bug 进行修复后,确认无误就可以将原服务更新到版本号 1.20,然后删除灰度服务。如果发现 1.20 版本依然有 bug,也可以选择删除灰度服务,让你原服务接管所有流量。这就是 CRD 对开发步骤的简化。
灰度更新一共有以下 4 个阶段:
创建
更新
替换
删除
创建
因为 Kubernetes 是水平触发的,所有它创建和更新的处理逻辑是相同的,只看最终状态即可。
这张图比较重要,大家可以仔细看一下。图中右上部分是原服务,原服务包含 Kubernetes 无状态服务、Service 内部域名、ApisixRoute、Apisix 路由规则、ApisixUpstrean,以及 Apisix 上游的一些配置。原服务下方是灰度服务,左边的 controller 是之前提到的 CRD 控制器。
原服务创建好后,创建无状态服务,配置对应的 http 转发规则后转到 ApisixRoute 服务站中进行对应路由的配置,之后只有转到容器网关就会自动定位到指定服务。然后大家可以看到,我们自定义的 CRD 类型名是 CanaryDeployment,是灰度的无状态服务。创建这个无状态服务的流程和原服务是相同的。
CRD 的定义是如何设计的?下图是一个简单示例:
apiVersion 我们先不讲,具体看一下下面的部分:
kind:类型,上图类型为 CanaryDeployment(无状态服务)
name:名称
namespace:位置,在 mohb-test 这个测试空间下
version:版本
replicas:灰度实例个数,这个个数是可配的
weight:权重,影响了灰度服务接管多少流量
apisix:服务对应的 hb 转化规则
apisixRouteMatches:相关功能
parentDeployment:原无状态服务名称
template:这里定义了刚刚讲的镜像、其他命令、开放端口等配置
在定义 CRD 的时候可能会遇到几个问题。第一个问题是如果删除了原服务,那灰度服务不会自动删除,会被遗留。出现这个问题是因为没有做 Kubernetes 的回收技术,而解决这个问题需要 Kubernetes 的 ownerReferences。它可以帮助你把灰度服务的 CRD 指到原服务的无状态服务中,也就是灰度服务的 owner 由原服务负责。
这样当删除原服务的时候,owner 会负责删除灰度服务。而删除 CanaryDeployment的时候,只会删除它右边的 Deployment。
ownerReferences 的具体设置如下图:
我们在定义 CRD 时加入红框部分的字段,这个字段会指定它是谁的 owner,以及它的指向。到这里创建阶段基本就完成了。
替换
接下来看第二阶段——替换。
我通过加入字段 replace 进行控制,默认情况下它是 false,如果值是 true 那控制器就会知道要用 deployment 的进行替换。这里有个问题是什么时候进行替换?也就是什么时候把流量切过去。虽然直接切也可以,但是等原服务完全运行起来后再切无疑是更好的。
那具体要怎么做呢?
这就涉及到 informer 的部分逻辑了。这需要控制器能够感知到灰度服务的 parentDeployment 是否发生变更。这部分 operator-sdk 和 Kubebuilder 就很好,它可以把不是 CRD 事件的变动也导入到调和函数内,让控制器可以监听无状态服务。
具体可以看一下代码。首先注册一些 watch 来监听无状态服务,然后写一个函数让无状态服务对应到 CanaryDeployment,比如在 text back 内对无状态服务进行了标记,这样当感知到事件后可以看一下是哪个无状态服务进行了替换,并推算出对应的 CanaryDeployment,然后通过调用调和函数对比和预期是否有差距。
取消
接下来看最后一个阶段——取消阶段。郑州哪家精神病医院好http://www.juenpt.com/
如果直接把 CanaryDeployment 对应的对象删掉,就会发现它的右边多了一个 deletionTimestamp 的字段,这是 Kubernetes 打的删除时间标记。而对于控制器来讲,就是知道这个已经是删除状态了,需要调整对应内容。
这有个问题,删除是瞬间的操作,可能等不到控制器运行起来,删除就已经完成了。因此 Kubernetes 提供了 Finalizer,Finalizer 决定了最终由谁来做释放。