5. 基于ingress和service实现灰度发布
关于灰度发布有好几种方式,比如蓝绿发布,滚动发布以及金丝雀发布。基于它们的表现形式不同,可以在不同场景下做到灵活应用。
细分的话基于Request Header的流量切分,基于Cookie的流量切分以及基于服务权重的流量切分都是灰度发布的具体表现,那我们这篇文章重点来聊聊蓝绿发布和金丝雀发布。
先大概介绍下这三种发布:
蓝绿发布:蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。
也就是说老版本(绿)在提供服务的时候,新版本上线(蓝),把流量全部切到新版本之后,如果验证通过就直接将老版本资源释放掉(下线机器等资源).
这种方式风险太大,一旦新版本有问题,回退之后切到原来老版本上,那么数据库里面的数据有可能因为这次灰度变脏了,怎么处理这些数据需要较强的运维能力和解决问题能力。
所以蓝绿发布不太适合高并发的场景中,比较适合后台这样的系统发布。
滚动发布:摘除一个发布一个,当然前提是你有多个这样的服务。那如果是一个呢?那我们为了不停机提供服务,首先得先起一个服务,然后把新进来的流量都打到刚才起的服务上,然后等摘除的服务把它原本处理的流量处理完成之后就可以选择下线了。
这种方式是比较建议的一种部署方式,也是k8s比较推荐的。这个上篇文章已经讲过实操,这里就不再演示了。
金丝雀发布:不用解释太多,看这个故事就知道了。
矿井中的金丝雀 17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。
故事结束之后我们总结下经验:新老服务都在线,只不过新上的服务可能是一个,等新的服务没有问题之后,把剩余的服务全部升级到新的服务。
这种发布也是灰度的一种,也是比较推荐的。
接下来看下蓝绿发布和金丝雀发布实操。
1. 蓝绿发布
- 准备好两个版本的Deployment
deploy.yaml(v1.0.0)
apiVersion: apps/v1 kind: Deployment metadata: name: k8sdemo-deploy labels: app: k8sdemo version: 1.0.0 # pod版本 spec: replicas: 1 selector: matchLabels: app: k8sdemo version: 1.0.0 template: metadata: labels: app: k8sdemo version: 1.0.0 spec: containers: - name: k8sdemo image: k8s-grpc-demo:v2 imagePullPolicy: Never
deploy1.yaml(v1.0.1)
apiVersion: apps/v1 kind: Deployment metadata: name: k8sdemo-deploy-old labels: app: k8sdemo version: 1.0.1 # pod版本 spec: replicas: 1 selector: matchLabels: app: k8sdemo version: 1.0.1 template: metadata: labels: app: k8sdemo version: 1.0.1 spec: containers: - name: k8sdemo image: k8s-grpc-demo:v6 imagePullPolicy: Never
service.yaml
apiVersion: v1 kind: Service metadata: name: k8sdemo-svc spec: ports: - name: k8sdemo-svc port: 8000 targetPort: 60001 selector: app: k8sdemo version: 1.0.0 # 关联pod的版本 type: NodePort
ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: k8sdemo-ingress-prefix annotations: kubernetes.io/ingress.class: "nginx" spec: rules: - host: hi.k8sdemo.com http: paths: - path: "/hello" pathType: ImplementationSpecific backend: service: name: k8sdemo-svc # 关联service port: number: 8000
直接kubectl apply -f .
就都创建好了。
我们首先访问下老的服务:
docker@minikube:~$ curl -X POST http://hi.k8sdemo.com:31721/hello -d '{"name": "fromGW"}' {"message":"Hello fromGW from 172.17.0.3:50009"}
没问题之后我们最后升级发布新的版本:在service.yaml中只需修改version=1.0.1就可以了。
apiVersion: v1 kind: Service metadata: name: k8sdemo-svc spec: ports: - name: k8sdemo-svc port: 8000 targetPort: 60001 selector: app: k8sdemo version: 1.0.1 # 这里从1.0.0 到 1.0.1 type: NodePort
再次kubectl apply -f service.yaml
。
我们访问看下:
docker@minikube:~$ curl -X POST http://hi.k8sdemo.com:31721/hello -d '{"name": "fromGW"}' {"message":"[gray] Hello fromGW from 172.17.0.7:50009"}
可以看到这次返回的内容就是我们新增加或者修改的内容,因为它带有[gray]标识‼️
最后我们就可以把老的服务下线:
kubectl delete deploy k8sdemo-deploy
这样就完成了服务的蓝绿部署。
2. 金丝雀发布
还是上面几个yaml文件,只不过策略上有点区别,就是老的v1.0.0多起几个pod,比如这里我们扩容到3个pod;我们新的服务就一个v1.0.1的pod。
大家可以看下:
接下来为了测试,service.yaml需要把version标签去掉,因为这次我们允许新的服务也提供线上服务(新老共存)。service.yaml:
apiVersion: v1 kind: Service metadata: name: k8sdemo-svc spec: ports: - name: k8sdemo-svc port: 8000 targetPort: 60001 selector: app: k8sdemo type: NodePort
我们访问看下:
docker@minikube:~$ while true; do curl -X POST http://hi.k8sdemo.com:31721/hello -d '{"name": "fromGW"}'; done { "message": "Hello fromGW from 172.17.0.3:50009" } { "message": "Hello fromGW from 172.17.0.8:50009" } { "message": "Hello fromGW from 172.17.0.8:50009" } { "message": "[gray] Hello fromGW from 172.17.0.7:50009" }
我们看到是RR轮循的方式把流量负载均衡到各个pod中。最后我们也看到[gray]出现在我们的打印中。
当我们验证没有问题之后我们就可以把流量全部切换到新的版本中,只需要修改service.yaml加上新的版本号即可,然后通过kubectl scale
扩容到和老的服务一样的数量。
这种就是按照1:3的流量做灰度的。这在规模比较小的时候可以使用,当规模达到成千上万的时候,不建议这么做了。
写到这里我不禁感叹:技术的发展就是把人变得懒惰一点,原本部署很复杂的事情,但是你两行命令就搞定了,剩下的时间干什么呢?喝杯咖啡打发掉还能怎样?
3. 小结
基于k8s的ingress和service实现的灰度发布方案到这里就结束了,因为k8s对细粒度的灰度支持不太友好,所以我们需要借助第三方组件帮助我们完成,比如云厂商提供的ingress以及istio
提供的ingress,像这样的ingress就可以做到真正的全方位的灰度发布。