导言:灰度发布是指在项目迭代的过程中用平滑过渡的方式进行发布。灰度发布可以保证整体系统的稳定性,在初始发布的时候就可以发现、调整问题,以保证其影响度。作为Istio体验系列的第一站,本文基于Istio的流量治理机制,针对最简单的几种业务场景进行了实践,为后续的探索学习提供了一个思路和实践案例。
在新版本上线时,不管是在技术上考虑产品的稳定性等因素,还是在商业上考虑新版本被用户接受的程度,直接将老版本全部升级是非常有风险的。所以一般的做法是,新老版本同时在线,新版本只切分少量流量出来,在确认新版本没有问题后,再逐步加大流量比例。这正是灰度发布要解决的问题。其核心是能配置一定的流量策略,将用户在同一个访问入口的流量导到不同的版本上。有如下几种典型场景。
蓝绿发布是指不停止老版本,部署新版本,然后进行测试,确认没有问题之后,再将流量全量切到新版本,然后老版本同时也升级到新版本。这样做的好处是无需停机,并且风险较小。
其发布的步骤大致如下:
如果版本2存在问题,需要回滚到版本1,进行流量切换回v1:v2为100:0。
在Kubernetes环境下可以基于Pod的数量比例分配流量。如下图所示,B服务的两个版本v2和v1分别有2个和3个实例,当流量被均衡地分发到每个实例上时,前者可以得到40%的流量,后者可以得到60%的流量,从而达到流量在两个版本间分配的效果。
给v1和v2版本设置对应比例的Pod数量,依靠Kube-proxy把流量均衡地分发到目标后端,可以解决一个服务的多个版本分配流量的问题,但是限制非常明显:首先,要求分配的流量比例必须和Pod数量成比例,试想,基于这种方式支持 3:97 比例的流量基本上是不可能的;另外,这种方式不支持根据请求的内容来分配流量,比如要求Chrome浏览器发来的请求和IE浏览器发来的请求分别访问不同的版本。有没有一种更细粒度的分流方式?答案当然是有,Istio就可以。Istio叠加在Kubernetes之上,从机制上可以提供比Kubernetes更细的服务控制粒度及更强的服务管理能力。
Istio本身并没有关于灰度发布的规则定义,灰度发布只是流量治理规则的一种典型应用,在进行灰度发布时,只要写个简单的流量规则配置即可。Istio在每个Pod里都注入了一个Envoy,因而只要在控制面配置分流策略,对目标服务发起访问的每个Envoy便都可以执行流量策略,完成灰度发布功能。
在使用Istio实现灰度发布的情况下,流量路由和副本部署是两个完全独立的功能。服务的pod数量可以根据流量负载灵活伸缩,与版本流量路由的控制完全无关。这在自动缩放的情况下能够更加简单地管理金丝雀版本。Istio的路由规则非常灵活,可以支持细粒度控制流量百分比(例如,路由1%的流量而不需要100个pod),也可以使用其他规则来控制流量(例如,将特定用户的流量路由到金丝雀版本)。
为了更加直观的验证和说明,接下来我们就通过搭建实验环境来模拟各种业务场景下的灰度发布。
由于个人电脑的网络和内存限制,本人是直接选择了在腾讯云服务器上安装Minikube和Kubectl,然后下载最新版本的Istio1.9,最后通过istioctl工具进行安装。安装过程不再赘述,具体可参考:
http://km.oa.com/group/34294/articles/show/410837
不过安装较新版本Istio的同学需要注意一下的是Istio 1.9 支持的kubernets版本要求不能低于v1.17,所以在用minikube启动kubernetes集群时必须指定好版本:
$ minikube start --vm-driver=none --kubernetes-version v1.18.15
具体环境和版本清单如下:
Kiali 是一个为 Istio 提供图形化界面和丰富观测功能的 Dashboard 的开源项目,其名称源于希腊语,意思是望远镜。用户利用 Kiali 可以监测网格内服务的实时工作状态,管理Istio的网络配置,快速识别网络问题。但是从Istio 1.7开始,默认不安装控制面板Kiali等组件,所以需要用户自行单独安装控制面板Kiali及相关的组件。
首先进入到Istio的安装包解压目录下,然后通过以下命令安装:
[root@chon istio-1.9.0]# kubectl apply -f samples/addons
[root@chon istio-1.9.0]# kubectl apply -f samples/addons/extras
安装时,由于网络原因,可能会报错,重试几次就好了。安装完成后,通过kubectl 命令查询相关pod的运行状态:
[root@chon istio-1.9.0]# kubectl get pod -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-94f5bf75b-fvlrt 1/1 Running 0 7h14m
istio-egressgateway-5b475b9856-lzwwm 1/1 Running 0 24h
istio-ingressgateway-648778567c-4gddl 1/1 Running 0 24h
istiod-7cccc657f6-ng9r2 1/1 Running 0 24h
jaeger-5c7675974-fmw4n 1/1 Running 0 7h14m
kiali-d4fdb9cdb-wdj2v 1/1 Running 0 7h14m
prometheus-7d76687994-p6whv 2/2 Running 0 7h14m
zipkin-679599ffd8-xxb8l 1/1 Running 0 7h1m
查看kiali服务,发现其类型为ClusterIP,没有对外暴露端口,无法从外部访问:
[root@chon istio-1.9.0]# kubectl get service kiali -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
kiali ClusterIP 10.105.136.82 20001:/TCP,9090/TCP
所以此时需要通过NodePort的方式对外暴露控制面板,我们将原来的ClusterIP类型的service导出yaml文件,通过删除注解、创建信息、状态字段及ClusterIP等信息将类型改NodePort,然后使用kubectl apply -f 创建:
[root@chon istio-1.9.0]# kubectl get svc -n istio-system kiali -o yaml > kiali-nodeport.yaml
[root@chon istio-1.9.0]# vi kiali-nodeport.yaml
#主要删除metadata下的annotation, resourceVersion,seflFlink, uid; 以及spec下的ClusterIP,修改类型为NodePort, 同时删除status状态字段即可。
[root@chon istio-1.9.0]# kubectl apply -f kiali-nodeport.yaml
此时再查看kiali的service,可以看到已经可以端口已经暴露出来:
[root@chon istio-1.9.0]# kubectl get service kiali -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kiali NodePort 10.105.136.82 20001:32662/TCP,9090:31692/TCP 7h44m
然后在浏览器中输入“http://<ip address>:32662/kiali”打开Kiali的登录页面,登录成功后,Kiali的总览视图如下所示:
下面通过经典的 Weather Forecast 进行部署实践,它是一款查询城市天气信息的应用实例,一共包含4个微服务,它们之间的调用关系如下:
Step1: 下载项目源码。由于官方代码的 Kubernetes api 版本未及时更新肯能会导致报错问题,所以这里不建议使用官,本文提供一个较新的源码:
$ git clone https://github.com/slzcc/cloud-native-istio.git
Step2: 添加 v1 版本的服务
$ kubectl create ns weather
$ kubectl label namespace weather istio-injection=enabled
$ kubectl apply -f install/weather-v1.yaml -n weather
等待服务安装成功:
Step3: 添加网关资源 Gateway。
$ kubectl apply -f install/weather-gateway.yaml
Step4: 验证访问页面。添加网关资源 Gateway 创建完成后访问 istio-ingressgateway 地址即可访问,或者访问其 NodePort 端口:
[root@chon ~]# kubectl get svc -n istio-system istio-ingressgateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.102.172.210 15021:32492/TCP,80:31844/TCP,443:32460/TCP,31400:30568/TCP,15443:31743/TCP 25h
点击查询:
至此,初始实验环境就全部搭建部署完成,接下来就正式开启Istio灰度发布功能的体验之旅。
实验中有两个核心配置文件贯穿始终,有必要先提前认识和区分一下:
- VirtualService:路由规则配置(虚拟服务),定义路由规则,可以将满足条件的流量都转发到对应的服务后端;
- DestinationRule:目标规则配置,定义发生路由后应用于服务流量的策略,描述到达目标的请求怎么处理。
目标规则是配合虚拟服务来使用的,主要用来定义子集,子集实际上就是具体的目标地址,除此以外,它主要描述的是到达目标请求后如何去处理,所谓的目标就是子集,而如何处理就是指具体的策略。
在开始实验前,首先对每个服务都创建各自的 VirtualService 和 DestinationRule 资源,将访问请求路由到所有服务的 v1 版本:
$ kubectl apply -f install/destination-rule-v1.yaml -n weather
$ kubectl apply -f install/virtual-service-v1.yaml -n weather
查看配置的路由规则,以 forecast 服务为例:
[root@chon ~]# kubectl get vs -n weather forecast-route -o yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
...
name: forecast-route
namespace: weather
...
spec:
hosts:
- forecast
http:
- route:
- destination:
host: forecast
subset: v1
在浏览器中多次加载前台页面,并查询城市的天气信息,确认显示正常。然后打开Kiali控制台,查看各个服务之间的调用关系,如下图所示:
场景一:用户需要软件能够根据不同的天气情况推荐合适的穿衣和运动信息。于是开发的同学增加了 recommendation 新服务,并升级 forecast 服务到 v2 版本来调用 recommendation 服务。在新特性上线时,运维的同学首先部署 forecast 服务的 v2 版本和 recommendation 服务,并对 forecast 服务的 v2 版本进行灰度发布。
Step1: 部署 recommendation 服务和 forecast 服务的 v2 版本。
[root@chon cloud-native-istio]# kubectl apply -f install/recommendation-service/recommendation-all.yaml -f install/forecast-service/forecast-v2-deployment.yaml -n weather
查看服务状态:
Step2: 更新 forecast 服务 v2 版本的 DestinationRule。
[root@chon cloud-native-istio]# kubectl apply -f install/forecast-service/forecast-v2-destination.yaml -n weather
查看下发成功的配置,可以看到增加了 v2 版本 subset 的定义:
[root@chon cloud-native-istio]# kubectl get dr forecast-dr -o yaml -n weather
...
host: forecast
subsets:
- labels:
version: v1
name: v1
- labels:
version: v2
name: v2
这时去浏览器中查询天气,显然还不会出现推荐信息,因为所有流量依然都被路由到 forecast 服务的 v1 版本,不会调用 recommendation 服务。
Step3: 配置 forecast 服务的 VirtualService 配置,其中的 weight 字段显示了相应服务的流量占比,可以看到此时为 v1:v2 = 1:1。
[root@chon cloud-native-istio]# kubectl apply -f chapter-files/canary-release/vs-forecast-weight-based-50.yaml -n weather
Step4: 在浏览器中查看配置后的效果。多次刷新查询天气页面,可以发现大概约 50% 的情况下不显示推荐服务,表示调用了 forecast 服务的 v1 版本;在另外 50% 的情况下表示推荐服务,调用了 forecast 服务的 v2 版本(刷新页面基本上是两个版本交替着来)。
Step5: 继续增加 forecast 服务的 v2 版本的流量比例,直到流量全部被路由到 v2 版本。
[root@chon cloud-native-istio]# kubectl apply -f chapter-files/canary-release/vs-forecast-weight-based-v2.yaml -n weather
Step6: 在浏览器中查看配置后的效果。多次刷新页面查询天气,每次都会出现推荐信息,说明访问请求都被路由到了 forecast 服务 v2 版本。
查看Kiali控制台:
Step7: 保留 forecast 服务的老版本 v1 一段时间,再确认 v2 版本的各性能指标稳定后,删除老版本 v1 的所有资源,完成灰度发布。
场景二:在生产环境中同时上线了 forecast 服务的 v1 和 v2 版本,运维同学期望让不同的终端用户访问不同的版本,例如:让使用 Chrome 浏览器的用户看到推荐信息,但让使用其他浏览器的用户看不到推荐信息。
有了上面场景一的经验,依葫芦画瓢,只需要修改 forecast 服务 v2 版本的 DestinationRule中的 match 条件,使来自Chrome浏览器的请求路由到 v2 版本,其余的不变即可:
在浏览器中查看配置后的效果:用 Chrome 浏览器多次查询天气信息,发现始终显示推荐信息,说明访问到 forecast 服务的 v2 版本;用 360 或 Firefox 浏览器多次查询天气信息,发现始终不显示推荐信息,说明访问到 forecast 服务的 v1 版本。
谷歌浏览器查询访问结果:
360浏览器查询访问结果:
现在已经掌握了两种路由规则的配置和应用之后,感兴趣的同学可以自己动手试一试,模拟将两种路由规则组合在一起的场景,比如:在生产环境中同时上线了 frontend 服务的 v1 和 v2 版本(v1 版本的按钮颜色是绿色的,v2 版本的按钮颜色是蓝色的),运维同学期望使用 Android 操作系统的一半用户看到的是 v1 版本,另一半用户看到的是 v2 版本;使用其他操作系统的用户看到的总是 v1 版本。
场景三:运维同学为 frontend 和 forecast 两个服务同时进行灰度发布,frontend 服务新增 v2 版本(界面的按钮为蓝色);forecast 服务新增 v2 版本(增加了推荐信息)。测试人员在用账户 tester 访问天气应用时会看到这两个服务的 v2 版本,其他用户只能看到两个服务的 v1 版本,要求不会出现服务版本交叉调用的情况。
在场景一中我们已经部署过了非入口服务 recommendation 和 forecast 的 v2 版本,并更新了 forecast 服务的 DestinationRule。现在我们在集群中来部署入口服务 frontend 的 v2 版本,并更新其 DestinationRule。
Step1: 部署入口服务 frontend 的 v2 版本。
[root@chon cloud-native-istio]# vi install/frontend-service/frontend-v2-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-v2
labels:
app: frontend
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
version: v2
spec:
containers:
- name: frontend
image: istioweather/frontend:v2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
[root@chon cloud-native-istio]# kubectl apply -f install/frontend-service/frontend-v2-deployment.yaml -n weather
查看部署情况:
Step2: 更新 frontend 服务的 DestinationRule,增加对 v2 版本 subset 的定义:
[root@chon cloud-native-istio]# vi frontend-service/frontend-v2-destination.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: frontend-dr
spec:
host: frontend
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
[root@chon cloud-native-istio]# kubectl apply -f install/frontend-service/frontend-v2-destination.yaml -n weather
Step3: 配置 frontend 服务的基于访问内容的路由规则,将测试账户(Cookie 带有 “user=tester”)信息的请求流量导入到 frontend 服务的 v2 版本的 Pod 实例。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: frontend-route
spec:
hosts:
- "*"
gateways:
- istio-system/weather-gateway
http:
- match:
- headers:
cookie:
regex: ^(.*?;)?(user=tester)(;.*)?$
route:
- destination:
host: frontend
subset: v2
- route:
- destination:
host: frontend
subset: v1
[root@chon cloud-native-istio]# kubectl apply -f chapter-files/canary-release/vs-frontend-multiservice-release.yaml -n weather
Step4: 配置非入口服务 forecast 的路由规则,使得只有带“version:v2”标签的 Pod 实例的流量,才能进入 forecast 服务的新版本 v2 实例:
[root@chon canary-release]# vi chapter-files/canary-release/vs-forecast-multiservice-release.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: forecast-route
spec:
hosts:
- forecast
http:
- match:
- sourceLabels:
version: v2
route:
- destination:
host: forecast
subset: v2
- route:
- destination:
host: forecast
subset: v1
[root@chon cloud-native-istio]# kubectl apply -f chapter-files/canary-release/vs-forecast-multiservice-release.yaml -n weather
Step5: 查看配置后的效果。
用 tester 账户登录并访问前台页面,界面的按钮是蓝色的,表示访问到的是 frontend 服务的 v2 版本。在查询天气时会显示推荐信息,表示可以访问到 forecast 服务的 v2 版本:
不登入或者使用其他用户则访问的是 v1 版本看不到推荐信息:
可视化视图查看服务间调用关系:
前面介绍的灰度发布的策略配置都需要人工干预。在持续交付过程中,为了解决部署和管理的复杂性,往往需要通过自动化工具实现基于权重的灰度发布。
Flagger 是一个基于 Kubernetes 和 Istio 提供灰度发布、监控和告警等功能的开源软件,通过使用 Istio 的流量路由和 Prometheus 指标来分析应用程序的行为,从而实现灰度版本的自动部署,可以使用 Webhook 扩展 Canary 分析,已运行集成测试、压力测试或其他自定义测试。
其部署流程如上图所示,由于篇幅有限,这里就不再进行赘述,有兴趣的同学可以进一步进行实践体验。
作为Istio入门体验系列的第一篇文章,关于灰度发布的实践暂时就先到这里了。对于一名刚接触Istio的小白,通过基于流量比例、基于请求内容以及多服务场景下的灰度发布的实践,Get到了它区别于Kubernetes的部署方式,也切身感受到了Istio在各种规则业务场景下的灵活性。当然,作为系列文章,接下来我也将继续学习探索,持续输出,还望各位同学多多关注,提出宝贵建议!
[