背景
我们在系列文章 K8s 应用管理之道 - 升级篇(一) 中介绍了不同部署形式下应用的升级方法,同时展示了如何配置停机发布、滚动发布这两类 k8s 原生支持的部署升级策略。本文将介绍如何通过二次开发或使用一些第三方工具在 k8s 中实现应用的蓝绿发布、金丝雀发布和 A/B 测试。
蓝绿发布
如果新老版本的应用无法共存,但又希望实现零中断升级,在系统资源充足的前提下,可以选用蓝绿发布。通过 k8s 的 label-selector 机制可以轻松实现蓝绿发布。如下图所示,您需要做的仅仅是为不同版本的应用打上对应的标签,然后通过更改 service 的 selector 实现流量切换。
配置方法
这里和蓝绿发布相关的配置如下(完整配置参见 blue-green)。
[...]
kind: Service
spec:
# Service 的 selector 会选择 app 标签和 version 标签都匹配的 pod
selector:
app: spring-boot-probes
# 通过更改 version 的取值实现流量切换,例如 v2.0.0
version: v1.0.0
[...]
kind: Deployment
metadata:
name: spring-boot-probes-v1
spec:
selector:
matchLabels:
app: spring-boot-probes
version: v1.0.0
template:
metadata:
labels:
app: spring-boot-probes
# Pod 的 version 标签
version: v1.0.0
[...]
kind: Deployment
metadata:
name: spring-boot-probes-v2
spec:
selector:
matchLabels:
app: spring-boot-probes
version: v2.0.0
template:
metadata:
labels:
app: spring-boot-probes
# Pod 的 version 标签
version: v2.0.0
[...]
操作步骤
完成上述配置后,可以通过以下步骤实现蓝绿发布:
- 运行命令
kubectl apply -f ./spring-boot-probes-v2.yaml
部署 v2 版本的应用。等到 v2 版本的 pod 全部变成就绪状态后流量并不会发往它们,这是因为 service 的 selector 只与 v1 版本 pod 的标签相匹配。 - 当一切准备就绪后,可以运行命令
kubectl patch service spring-boot-probes-svc -p '{"spec":{"selector":{"version":"v2.0.0"}}}'
更改 service 的 selector,让其指向 v2 版本的 pod。这时流量会全部发往 v2。 - 如果 v2 版本的应用运行一段时间后出现了问题,可以通过命令
kubectl patch service spring-boot-probes-svc -p '{"spec":{"selector":{"version":"v1.0.0"}}}'
进行回滚,将流量再次切回到 v1 上来。 - 如果 v2 版本应用的行为符合预期,可以运行命令
kubectl delete deploy spring-boot-probes-v1
删掉 v1 版本的 deployment 以节省资源。
金丝雀发布
金丝雀发布通过逐步切换流量的方式大大降低了升级和变更可能带来的风险,因此被广泛使用。相较于其他发布方式,其最大特点在于对流量的灵活控制。回到 k8s 的场景,实现金丝雀发布最推荐的方式是使用 istio。利用 istio 强大的流量管理功能,用户可以灵活地控制发往不同版本的流量,不论该版本部署了多少个 pod 实例。而在没有 istio 的 k8s 环境中,发往新老版本的流量和它们的 pod 实例数成正比。
下图展示了基于 istio 的金丝雀发布的关键组件,用户可以通过调节 VirtualService 的 weight 字段指定将 10% 的流量发往金丝雀版本。
配置方法
以下配置指定将 90% 的流量发往服务 spring-boot-probes-svc-v1,将 10% 的流量发往服务 spring-boot-probes-svc-v2。而服务 spring-boot-probes-svc-v1 绑定了当前版本的 pod,服务 spring-boot-probes-svc-v2 绑定了金丝雀版本的 pod(完整配置参见 canary)。
[...]
kind: VirtualService
metadata:
name: spring-boot-probes
spec:
hosts:
- spring-boot-probes.local
gateways:
- spring-boot-probes
http:
- route:
- destination:
host: spring-boot-probes-svc-v1
weight: 90
- destination:
host: spring-boot-probes-svc-v2
weight: 10
[...]
kind: Service
metadata:
name: spring-boot-probes-svc-v1
spec:
selector:
app: spring-boot-probes
version: v1.0.0
[...]
kind: Service
metadata:
name: spring-boot-probes-svc-v2
spec:
selector:
app: spring-boot-probes
version: v2.0.0
[...]
操作步骤
完成上述配置后,可以通过以下步骤实现金丝雀发布:
- 运行命令
kubectl apply -f ./spring-boot-probes-v2.yaml
部署 v2 版本的 service 和少量的 pod 实例。由于此时 VirtualService 的路由目标只有 v1,流量并不会被发到 v2 上。 - 当 v2 版的 pod 就绪后,运行命令
kubectl apply -f ./virtualservice-weight.yaml
更改 VirtualService 的路由规则,让少量流量能够发往 v2。 - 如果 v2 版本应用的行为符合预期,可以通过调整权重继续扩大发往 v2 的流量比例。同时,v1 和 v2 的 pod 实例数也可以根据实际情况进一步调整,甚至可以为它们配置 Horizontal Pod Autoscaler。
- 如果 v2 版本在运行过程中出现了问题,可以更改 VirtualService 的路由规则将流量重新切回到 v1 上来。
- 待全部流量都落在 v2 上后,运行命令
kubectl delete -f ./spring-boot-probes-v1
删除 v1 版应用的资源。
A/B 测试
如果您网站的 UI 作了改版,但并不确定新版本是否会获得用户青睐,这时可以通过 A/B 测试收集用户意见,并根据实际效果确定最佳方案。A/B 测试实施的关键在于根据不同条件将访问流量分发到不同的版本。实际使用中,往往会根据具体情况选用不同的条件。常用的条件包括用户位置信息、user-agent、自定义 header、请求参数、语言等。得益于强大的流量管理功能,istio 是 k8s 场景下实现 A/B 测试的最佳方案。下图展示了如何根据 user-agent 分发访问流量。
配置方法
以下配置指定将来自 Andriod 的访问流量发往服务 spring-boot-probes-svc-v1,将来自 iPhone 的访问流量发往服务 spring-boot-probes-svc-v2。而 spring-boot-probes-svc-v1 和 spring-boot-probes-svc-v2 分别绑定了不同版本的 pod
(完整配置参见 ab-testing)。
[...]
kind: VirtualService
metadata:
name: spring-boot-probes
spec:
hosts:
- spring-boot-probes.local
gateways:
- spring-boot-probes
http:
- route:
- destination:
host: spring-boot-probes-svc-v1
match:
- headers:
user-agent:
exact: Andriod
- route:
- destination:
host: spring-boot-probes-svc-v2
match:
- headers:
user-agent:
exact: iPhone
[...]
kind: Service
metadata:
name: spring-boot-probes-svc-v1
spec:
selector:
app: spring-boot-probes
version: v1.0.0
[...]
kind: Service
metadata:
name: spring-boot-probes-svc-v2
spec:
selector:
app: spring-boot-probes
version: v2.0.0
[...]
操作步骤
完成上述配置后,可以通过以下步骤实施 A/B 测试:
- 运行命令
kubectl apply -f ./spring-boot-probes-v2.yaml
部署 v2 版本的 service 和对应的 pod 实例。 - 当 v2 版的 pod 就绪后,运行命令
kubectl apply -f ./virtualservice-match.yaml
更改 VirtualService 的路由规则,让来自 Andriod 的流量发往 v1,来自 iPhone 的流量发往 v2。 - 让两个版本的应用同时运行一段时间并不断收集用户反馈。
- 如果发现使用 user-agent 作为条件并不能达到很好的测试效果,可以更改 VirtualService 的路由规则,根据其他条件进行测试。
- 经综合分析后,如果发现 v1 更受青睐,则更改 VirtualService 的路由规则,让流量只发往 v1,然后删除 v2 版应用的资源。反之亦然。
总结
本文带您探索了在 k8s 中进行蓝绿发布、金丝雀发布和 A/B 测试的最佳实践。可以看到,运用好 istio 强大的流量管理功能可以简化复杂发布的实施流程,大大降低实现成本。
参考资料
- Fully automated canary deployments in Kubernetes
- A Detailed Guide to Canary Deployments
- Istio in Practice – Routing with VirtualService
- 《分布式系统设计模式》系列干货:管理设计篇之"部署升级策略"
扩展阅读
阿里云日志服务针对容器场景提供了一站式日志解决方案,想了解相关内容可参考下列文章: