基本定义
金丝雀部署是在将更改推广到整个服务集群并使其对所有人可用之前,将更改推广到一小部分用户进行测试。并在测试过程中持续观测被测试的服务各个维度的状态,验证新版本的健壮性、可用性、稳定性等。
当验证结果达到期望目标后,可以逐步将新的版本部署到更多服务器,使更多用户使用到它。
下图显示了该部署模式:旧版本显示为蓝色,新版本显示为绿色,它们部署在集群中的每一台服务器上。
优势
- 零下线时间与快速回滚:
- 在一系列的相关验证和测试之后,如果新版本的软件被认为不合适,则很容易回滚和控制影响范围。
- 真实场景下的测试:
- 由于是直接将新版本部署到生产环境进行测试,所以能够通过真实流量对新版本进行针对性的验证。当然,需要对流量和用户进行限制,来控制验证的范围和影响面。
- 较低的基础设施成本:
- 因为金丝雀部署策略是通过一定规则,按规则对的请求进行要求的分流或路由(例如:用户名、地区、年龄、随机等),所以只需要少量额外的基础设施,就可以达到验证的效果。对比蓝绿部署策略,需要准备与生产环境同样的一套基础设施,部署成本显然会高很多。
- 灵活的按需进行验证相关版本、功能的正确性。
- 可以根据不同的特征和标识,对请求的流量进行多个维度分流和路由,以达到不同粒度不同特征的灵活验证。
使用限制:
虽然金丝雀部署能够为大家的部署提供强力的支持和帮助。但是,软件工程中是没有银弹的。金丝雀部署在很多场景也需要谨慎使用:
- 严格不允许出错的系统,例如:医疗系统、消防系统等
- 需要部署的数据结构无法向下兼容已经当前版本数据结构
- 虽然 MDS 能够提供分流影子数据库的能力。但是,正式用户使用的话,依然会影响到实际的数据和行为体验。所以,仅限于测试人员或内部用户体验使用才能发挥他的能力。
- 非自动化的金丝雀部署,既耗时又容易出现错误。所以,我们应该尽可能使用自动化的方式去使用它,而不是手动去维护分流策略和逻辑。
- 以及很多其他对于生产环境要求严格的场景,均不建议使用
实现结构
金丝雀部署的关键点有如下几点:
- 网络流量分流
- 分流策略管理
- 多应用间分流传递策略
- 数据兼容性处理
流程阶段
- 正常阶段: 当前环境中只有 1-n 个版本已验证版本, 为所有的用户提供服务。
- 验证阶段:当前环境存在 2 到 n 个版本, 其中有一个已验证版本, 为大部分用户提供相对稳定服务, 剩下的 1 到 n-1 版本为需验证版本, 为特定用户(某些场景下随机挑选)提供相对不稳定服务。同时,持续观需验证版本运行情况,以判断该版本是需要进行部署,还是进行其他处理。
- 部署阶段: 某个需验证版本通过验证后,此需验证版本将被部署至计划的服务集群占比。如果,还需进一步测试,则又回到验证期进行验证.直至,需验证版本部署占比达到最终需要。往往会使用滚动部署的方式进行部署。
实现类型
- 基础设施实现(IAAS):比如通过 MDS 工具实现
- 平台实现(PAAS):通过 K8S 的 Ingress 组件或 Istio 来实现
- 通过 Nginx 等中间件实现:直接在 Nginx 中通过脚本控制流量转发规则
k8s-deployment实现金丝雀发布
1. yaml文件准备
- deployment-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v2
ports:
- name: http
containerPort: 80
- service-myapp.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: myapp
release: canary
2.发布
[root@t34 deploy]# kubectl apply -f ../deploy/
deployment.apps/myapp-deploy created
service/myapp-svc created
3. 测试
- 查看
kubectl get pod -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-deploy-675558bfc5-nwzp4 1/1 Running 0 76s
myapp-deploy-675558bfc5-z6zf7 1/1 Running 0 76s
- 升级
kubectl set image deployment myapp-deploy myapp=ikubernetes/myapp:v3 && kubectl rollout pause deployment myapp-deploy
[root@t34 deploy]# kubectl get pod -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-deploy-675558bfc5-nwzp4 1/1 Running 0 2m25s
myapp-deploy-675558bfc5-z6zf7 1/1 Running 0 2m25s
myapp-deploy-7f577979c8-jzzsj 1/1 Running 0 22s
[root@t34 deploy]# kubectl get deploy -l app=myapp
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-deploy 3/2 1 3 2m38s
[root@t34 deploy]#
此时,myapp-deploy ready的数量为3,其中两个为myapp:v2的版本,一个为myapp:v3的版本
- 流量测试
[root@t34 deploy]# kubectl get svc myapp-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-svc ClusterIP 10.43.223.204 <none> 80/TCP 7m59s
[root@t34 deploy]# for i in {1..10}; do curl 10.43.223.204:80; done
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
此时,流量会随机的流向v2和v3的服务,如果测试v3服务稳定之后,可以通过rollout resume
进行升级为v3
[root@t34 deploy]# kubectl rollout resume deploy myapp-deploy
deployment.extensions/myapp-deploy resumed
[root@t34 deploy]# kubectl get pod -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-deploy-7f577979c8-jzzsj 1/1 Running 0 15m
myapp-deploy-7f577979c8-rbwwx 1/1 Running 0 2m12s
[root@t34 deploy]# kubectl get deploy myapp-deploy
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-deploy 2/2 2 2 16m
[root@t34 deploy]# for i in {1..10}; do curl 10.43.223.204:80; done
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v3 | <a href="hostname.html">Pod Name</a>
升级服务到v3,只有一个pod是重新创建的,之前的发布的pod一直存在
- 回滚
[root@t34 deploy]# kubectl get rs -l app=myapp
NAME DESIRED CURRENT READY AGE
myapp-deploy-675558bfc5 0 0 0 26m
myapp-deploy-7f577979c8 2 2 2 24m
[root@t34 deploy]# kubectl rollout history deploy myapp-deploy
deployment.extensions/myapp-deploy
REVISION CHANGE-CAUSE
1 <none>
2 <none>
[root@t34 deploy]# kubectl rollout undo deploy myapp-deploy --to-revision=1
deployment.extensions/myapp-deploy rolled back
[root@t34 deploy]# kubectl get pod -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-deploy-675558bfc5-nzn85 1/1 Running 0 18s
myapp-deploy-675558bfc5-tnhjd 1/1 Running 0 20s
[root@t34 deploy]# kubectl get deploy myapp-deploy
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-deploy 2/2 2 2 29m
[root@t34 deploy]# kubectl rollout history deploy myapp-deploy
deployment.extensions/myapp-deploy
REVISION CHANGE-CAUSE
2 <none>
3 <none>
[root@t34 deploy]# for i in {1..10}; do curl 10.43.223.204:80; done
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>