在《最佳实践在哪里-1》中,我们阐述了使用服务网格技术进行全链路灰度流量控制的各种场景以及实现思路。服务网格ASM已经在产品中提供了“流量泳道”能力,通过流量泳道,就可以通过简单的配置将调用链路的不同版本进行环境隔离,从而轻松实现自己的全链路灰度发布场景。产品功能参考:什么是流量泳道_服务网格(ASM)-阿里云帮助中心。
本文是一篇小品级示例,主要阐释全链路灰度发布是如何通过流量泳道功能来实现的,来指导用户的最佳实践。
背景、需求与前提条件
我们首先明确问题背景与用户需求,以更好地理解后面要进行的操作。
作为服务网格技术的用户,小帅开发了一套在k8s上部署的业务系统,该系统由三个云原生服务组成,分别是mocka、mockb和mockc,服务之间存在调用关系:mocka -> mockb -> mockc。现在,小帅希望对自己的这套系统构建一套完整的灰度发布流程。期望的方式是新版本的服务上线后,先将10%左右的入口流量发往新版本进行测试(灰度需要是全链路的,要保证流量不会打向老版本)。经过逐步的灰度、新版本的流量比例不断提高直到100%,最后下线老版本,期间需要保持流量无损。
让我们看看使用服务网格ASM如何实现这个需求,首先我们需要满足一些前提条件:
1、小帅已经在线上部署了自己的系统,mocka、mockb、mockc,每个服务自己的Service和Deployment都已经部署。
通过kubctl链接到Kubernetes集群,然后通过以下指令就可以部署这套示例应用:
kubectl apply -f https://alibabacloudservicemesh.oss-cn-beijing.aliyuncs.com/asm-labs/swimlane/v1/mock-v1.yaml
2、系统使用了ASM网关作为入口网关(本例中网关名字叫ingressgateway),通过网关规则(Gateway)和虚拟服务(VirtualService)、让入口网关向mocka服务转发请求。相关资源YAML如下——
网关规则:
apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: ingressgateway namespace: istio-system spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: http number: 80 protocol: HTTP
虚拟服务:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: mockvs namespace: istio-system spec: gateways: - ingressgateway hosts: - '*' http: - route: - destination: host: mocka.default.svc.cluster.local
可以通过kubectl连接到ASM实例,然后应用这些YAML资源(同时别忘了在ASM实例中创建一个默认的ASM网关)。
做完之后,应该已经可以通过网关访问我们的系统了。
curl {网关ip地址} -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157)%
本示例中使用的服务可以打印业务的调用链条,以便我们观测全链路灰度的结果,可以看到当前我们只部署了基础的mock-v1版本,调用链路是mocka-v1 -> mockb-v1 -> mockc-v1。现在可以着手准备我们的新版本上线流程了
步骤一:准备工作负载的版本标签、初始化流量泳道
要对全链路进行灰度发布,首先需要给系统重的每个服务pod进行打标,确保服务网格能够识别服务的不同版本。
我们可以看到目前部署的mock-v1版本的三个服务的pod都带有version: v1的标签,来标识它们都属于v1这个版本:
如果线上的业务系统pod没有这种可以标识版本信息的标签,则需要先添加标签。
准备好版本信息后,我们参考https://help.aliyun.com/zh/asm/user-guide/full-link-traffic-management-using-strict-mode-traffic-swimlanes官方文档,在 流量管理中心 > 流量泳道 页面中,建立一条泳道组:
泳道组建好后,点击“创建泳道”按钮,按照参考文档创建一条名字叫v1的泳道,并选择使用version: v1这个标签来区分泳道内的服务。这条泳道就代表着我们应用最基本的v1版本。
创建好后的泳道:
创建好之后,我们已经可以在虚拟服务中通过subset来指定流量应该发往的版本,我们更新之前在前提条件中创建的虚拟服务为:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: mockvs namespace: istio-system spec: gateways: - ingressgateway hosts: - '*' http: - route: - destination: host: mocka.default.svc.cluster.local subset: v1
主要的区别就是在destination中添加了subset: v1,让网关将流量转发到v1版本的流量泳道。这对当前的业务访问没有任何的影响,因为我们还没有新版本的服务,后面要通过这个虚拟服务实现流量灰度比例的逐步切换。
步骤二:部署新版本(v2)服务
接下来部署我们开发好的v2版本的服务。对于云原生应用来说,都是以k8s Service name来进行相互调用的,我们的mock应用也不例外。当我们发布应用的新版本时,服务之间肯定还是以相同的service name互相调用,因此实际部署的是新服务的deployment。
通过kubectl链接到Kubernetes集群,然后通过以下指令部署mock应用的v2版本
kubectl apply -f https://alibabacloudservicemesh.oss-cn-beijing.aliyuncs.com/asm-labs/swimlane/v2/mock-v2.yaml
部署后可以看到集群中新版本服务的pod。
可以看到这些新版本的服务pod都带有version: v2的标签,用来和最初的v1版本进行区分。
此时我们再试着调用业务:
curl {网关ip地址} -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157)%
可以发现当下无论怎么发起调用,流量还是只流经v1版本的服务。v2版本的服务并不会有流量进来,这是因为我们在步骤一中已经初始化了流量泳道,对这个流量进行了控制隔离,保证业务能够正常灰度发布。
接下来,我们需要为v2版本的服务创建一条泳道,以进行接下来的灰度流程。还是效仿步骤一中创建流量泳道的操作,不过这次指定泳道名为v2、匹配version: v2标签的pod。
现在可以在ASM控制台上看到,我们有两条泳道了(v1和v2)。
步骤三:开始灰度流程,部分流量切到v2版本
流量泳道代表着流量调用链路的隔离环境。通过上面创建的两条泳道,我们已经保证到达服务上的流量只会在服务的相同版本中传递。接下来,只需要调整网关上的虚拟服务,向v2版本进行灰度切流即可。
还是之前的那个虚拟服务,这次我们改成:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: mockvs namespace: istio-system spec: gateways: - ingressgateway hosts: - '*' http: - route: - destination: host: mocka.default.svc.cluster.local subset: v1 weight: 90 - destination: host: mocka.default.svc.cluster.local subset: v2 weight: 10
可以看到这里多了一条路由目的地,指定为mocka服务的v2版本。同时,两个版本之间的流量比例通过weight参数调整为90:10。 这样就会有10%的流量灰度到v2版本。
通过连续不断的访问我们的系统,我们可以观测到发往服务不同版本的流量比例。v1 : v2 ~= 90: 10。也就是v2版本正在进行10%流量的灰度。
for i in {1..100}; do curl {ASM网关ip地址}; echo ''; sleep 1; done; -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157) -> mocka(version: v1, ip: 10.1.0.149)-> mockb(version: v1, ip: 10.1.0.173)-> mockc(version: v1, ip: 10.1.0.157)
步骤四:完全上线v2版本,下线v1版本
通过调整步骤三中虚拟服务的weight参数,我们能够轻松控制两个版本服务的流量比例。当我们充分对v2版本进行灰度测试后,就可以考虑上线v2版本并对v1版本进行下线。
将虚拟服务改成:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: mockvs namespace: istio-system spec: gateways: - ingressgateway hosts: - '*' http: - route: - destination: host: mocka.default.svc.cluster.local subset: v2
此时流量将完全发往v2版本。我们可以再次访问系统来验证一下:
for i in {1..100}; do curl {ASM网关ip地址}; echo ''; sleep 1; done; -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215) -> mocka(version: v2, ip: 10.1.0.216)-> mockb(version: v2, ip: 10.1.0.217)-> mockc(version: v2, ip: 10.1.0.215)
可以看到已经没有请求发往v1版本了,这时就可以对v1版本进行安全的下线。
首先在ASM控制台侧删除v1版本的流量泳道:
接下来就可以删除v1版本所有服务的deployment了,可以直接在ACK控制台进行删除:
删除后,mock服务的v1版本已经完全下线,线上运行的已经100%是v2版本了~
小结
我们通过一个常见的客户需求拓展出的小品案例,给各位简要阐述了如何使用ASM的流量泳道功能、对自己的业务系统进行一个全链路的灰度发布。同时,灰度发布的整个过程中,通过流量泳道的控制,业务的流量不会受到损失~
用服务网格ASM,灰度发布不费劲!🤷🏿♀️