作者:潘梦源
Kruise Rollout [ 1] 是 OpenKruise 社区开源的渐进式交付框架。Kruise Rollout 支持配合流量和实例灰度的金丝雀发布、蓝绿发布、A/B Testing 发布,以及发布过程能够基于 Prometheus Metrics 指标自动化分批与暂停,并提供旁路的无感对接、兼容已有的多种工作负载(Deployment、CloneSet、DaemonSet)。
目前 Kruise Rollout 新增了流量调度支持自定义资源的能力 ,从而更好的支持渐进式发布中的流量调度。 本文将对 Kruise Rollout 所提出的方案进行介绍。
**渐进式发布(Progressive Delivery)是一种软件部署和发布策略,旨在逐步将新版本或功能引入生产环境,以降低风险并确保系统的稳定性。一些常见的渐进式发布形式如下:
金丝雀发布、A/B 测试和蓝绿发布都是逐步测试和评估新功能或变更的策略,它们可以根据具体的需求和场景选择适合的部署和测试策略,并结合流量灰度等技术实现逐步发布和测试新版本或功能。
Kruise Rollout 目前已经对 Gateway API 提供了支持,那么为什么还需要对不同供应商的网关资源提供支持呢?在解释这个问题之前,我们先来简单介绍一下 Gateway API。
当前社区中不同的供应商都有自己的网关资源,并提出了自己的标准,而 Kubernetes 为了提供一个统一的网关资源标准,构建标准化的,独立于供应商的 API,提出了 Gateway API。目前,尽管 Gateway API 还处于开发阶段,但已经有很多项目表示支持或计划支持 Gateway API。包括:
然而由于目前 Gateway API 并不能覆盖供应商所提出网关资源的所有功能,并且仍然有大量用户使用供应商提供的网关资源,虽然用户可以通过开发 Gateway API 对网关资源进行适配,但这样的工作量较大,所以仅仅为 Gateway API 提供支持是远远不够的,尽管随着 Gateway API 特性的不断丰富,在未来,使用 Gateway API 将成为一种更加推荐的方式。因此,虽然 Kruise Rollout 目前已经提供了对 Gateway API 的支持,如何对现有供应商多种多样的网关资源提供支持仍然是一个重要的问题。
当前社区中已经存在许多广泛使用的供应商提供的网关资源,比如:Istio、Kong、Apisix 等,然而正如前文所述,这些资源的配置并没有形成统一的标准,因此无法设计出一套通用的代码对资源进行处理, 这种情况给开发人员带来了一些不便和挑战。
为了能够兼容更多的社区网关资源,一些方案被提出,例如 flagger、argo-rollouts 为每一种网关资源都提供了代码实现。这些方案的实现相对简单,但也存在一些问题:
因此,需要一种支持用户定制,可以灵活插拔的实现方案,以适配社区以及用户定制的多种多样的网关资源,来满足社区不同的用户的需求,增强 Kruise Rollout 的兼容性和扩展性。
为此,我们提出了一种基于 Lua 脚本的网关资源可扩展流量调度方案。
Kruise Rollout 使用基于 Lua 脚本的网关资源定制方案,本方案通过调用 Lua 脚本根据发布策略和网关资源原始状态来获取并更新资源的期待工作状态(状态包含 spec、labels 以及 annotations),可以使用户能够轻松地适配和集成不同类型的网关资源,而无需修改现有的代码和配置。
本方案对于网关资源的处理可以表示为上图,整个过程可以描述为:
通过使用 Kruise Rollout,用户可以:
同时,Kruise Rollout 采用的方案仅需要添加 5 个新接口即可实现对多种多样网关资源的支持。相比之下,其他方案例如 argo-rollouts 则为不同供应商的网关资源提供了不同的接口,对于 Istio 和 Apisix 来说,argo-rollouts 分别提供了 14 个和 4 个新的接口, 而且,该方案随着对更多网关资源的支持,接口数量还会持续增长。相比之下,Kruise Rollout 并不需要为新的网关资源提供新的接口,这使得 Kruise Rollout 成为一种更简洁、更易于维护的选择,而不会增加过多的接口负担。同时,编写 Lua 脚本相对于开发 Gateway API 对网关资源进行适配,可以大大减小开发人员的工作量。
以下展示了一个利用 Lua 脚本对 Istio DestinationRule 进行处理的的示例。
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
...
spec:
...
trafficRoutings:
- service: mocka
createCanaryService: false # 使用原有service,不创建新的canary service
networkRefs: # 需要控制的网关资源
- apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
name: ds-demo
patchPodTemplateMetadata:
labels:
version: canary # 为新版本pod打上label
local spec = obj.data.spec -- 获取资源的spec,obj.data为资源的状态信息
local canary = {} -- 初始化一条指向新版本的canary路由规则
canary.labels = {} -- 初始化canary路由规则的labels
canary.name = "canary" -- 定义canary路由规则名称
-- 循环处理rollout配置的新版本pod label
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v -- 向canary规则中加入pod label
end
table.insert(spec.subsets, canary) -- 向资源的spec.subsets中插入canary规则
return obj.data -- 返回资源状态
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
spec:
...
subsets:
- labels: # -+
version: canary # |- Lua脚本处理后新插入的规则
name: canary # -+
- labels:
version: base
name: version-base
接下来介绍一个利用我们所提出方案对 Istio 进行支持的具体案例。
nginx 服务的 deployment 如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
version: base
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html
volumes:
- name: html-volume
configMap:
name: nginx-configmap-base # 挂载ConfigMap作为index
apiVersion: rollouts.kruise.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
annotations:
rollouts.kruise.io/rolling-style: canary
spec:
disabled: false
objectRef:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deployment
strategy:
canary:
steps:
- weight: 20 # 第一批转发20%的流量进入新版本pod
- replicas: 1 # 第二批将包含version=canary header的流量转发入新版本pod
matches:
- headers:
- type: Exact
name: version
value: canary
trafficRoutings:
- service: nginx-service # 旧版本pod使用的service
createCanaryService: false # 不创建新的canary service,新旧pod共用一个service
networkRefs: # 需要修改的网关资源
- apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
name: nginx-vs
- apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
name: nginx-dr
patchPodTemplateMetadata: # 为新版本pod打上version=canary的label
labels:
version: canary
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
...
volumes:
- name: html-volume
configMap:
name: nginx-configmap-canary # 挂载新的ConfigMap作为index
在调用 Lua 脚本获取资源状态新状态时,Kruise Rollout 支持两种 Lua 脚本调用方式,分别为:
Kruise Rollout 默认首先查找本地是否存在已发布的 Lua 脚本,这些脚本通常需要设计测试案例进行单元测试验证其可用性,具有更好的稳定性。 测试案例的格式如下所示,Kruise Rollout 利用 Lua 脚本根据 rollout 中定义的发布策略对资源原始状态进行处理,得到发布过程中每一步的资源新状态,并与测试案例中 expected 中定义的期待状态进行对比,以验证 Lua 脚本是否按照预期工作。
rollout:
# rollout配置
original:
# 资源的原始状态
expected:
# 发布过程中资源的期待状态
在资源的 Lua 脚本未发布的情况下,用户还可以快速的通过在 ConfigMap 中配置 Lua 脚本的方式由 Kruise Rollout 调用从而对资源进行处理。
apiVersion: v1
kind: ConfigMap
metadata:
name: kruise-rollout-configuration
namespace: kruise-rollout
data:
# 键以lua.traffic.routing.Kind.CRDGroup的形式命名
"lua.traffic.routing.DestinationRule.networking.istio.io": |
--- 定义Lua脚本
local spec = obj.data.spec
local canary = {}
canary.labels = {}
canary.name = "canary"
for k, v in pairs(obj.patchPodMetadata.labels) do
canary.labels[k] = v
end
table.insert(spec.subsets, canary)
return obj.data
详细的 Lua 脚本配置说明参见 Kruise Rollout 官网 [ 2] 。
非常欢迎你通过 Github/Slack/钉钉/微信 等方式加入我们来参与 OpenKruise 开源社区。
你是否已经有一些希望与我们社区交流的内容呢?可以在我们的社区双周会 [ 3] 上分享你的声音,或通过以下渠道参与讨论:
相关链接:
[1] Kruise Rollout
https://github.com/openkruise/rollouts
[2] Kruise Rollout 官网
https://openkruise.io/rollouts/introduction
[3] 社区双周会https://shimo.im/docs/gXqmeQOYBehZ4vqo
[4] Slack channel
https://kubernetes.slack.com/?redir=%2Farchives%2Fopenkruise