应用纳管和灰度发布:谐云基于 KubeVela 的企业级云原生实践

本文涉及的产品
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
应用实时监控服务-用户体验监控,每月100OCU免费额度
云原生网关 MSE Higress,422元/月
简介: 谐云通过类比事务的方式,将渲染过程分为正向和逆向,同时将首次纳管和真正的纳管动作进行了分离,完成了平台升级的同时,给应用的纳管行为留下了一定的可操作空间。

以下文章来源于谐云科技 ,作者陈炜舜


在 OAM 最早推出时,谐云就参与其中,并基于社区中 oam-kubernetes-runtime 项目二次开发,以满足容器云产品中 OAM 应用模型的功能需求。该功能是将应用划分为多个 Kubernetes 资源 —— 组件(Component)、配置清单(ApplicationConfiguration = Component + Trait),其目的是希望将用户侧的开发、运维视角进行分离,并能够借助社区的资源快速上线一些开源组件和运维特征。


后续,KubeVela 项目在组件和配置清单两个资源上抽象出应用资源(Application),并借助 cuelang 实现 KubeVela 的渲染引擎。谐云快速集成了 KubeVela,并将原有的多种应用模型(基于 Helm、基于原生 Workload、基于 OAM 的应用模型)统一成基于 KubeVela 的应用模型。这样做既增强了谐云 Kubernetes 底座的扩展性和兼容性,同时又基于 Application 这一抽象资源分离的基础架构和平台研发,将许多业务功能下沉到底座基础设施,以便适应社区不断发展的节奏,快速接入多种解决方案。


除此之外,确定的、统一的应用模型能够帮助谐云多产品间的融合,尤其是容器云产品和中间件产品的融合,将中间件产品中提供的多款中间件作为不同的组件类型快速接入到容器云平台,用户在处理中间件特性时使用中间件平台的能力,在处理底层资源运维时,使用容器云平台的能力。


基于以上背景,谐云对社区版本管理、纳管功能进行了增强,并希望能够分享至社区进行讨论,引发更多的思考后,对社区功能做出贡献。


应用版本管理


版本控制与回滚


在应用运维时,应用的版本控制是用户非常关心的问题,KubeVela 社区中提供了 ApplicationRevision 资源进行版本管理,该资源在用户每次修改 Application 时将产生新的版本,记录用户的修改,实现用户对每次修改的审计和回滚。


而谐云的应用模型当中,由于组件可能会包含一些“不需要计入版本”的纯运维 Trait,例如版本升级的 Trait,手动指定实例数的 Trait 等,我们在升级、回滚时,需要将这些 Trait 忽略。


在社区早期版本中,TraitDefinition 含有 skipRevisionAffect 字段,该字段在早期社区版本中实现如下:


  • ApplicationRevision 中仍会记录 skipRevisionAffect 的 Trait
  • 若用户触发的修改范围仅包含 skipRevisionAffect 的 Trait,将此次更新直接修改至当前记录的最新版本中
  • 若用户触发的修改范围不仅包含 skipRevisionAffect 的 Trait,将此次更新作为新版本记录


这样实现的 skipRevisionAffect 无法真正使 Trait 不计入版本,例如,我们将 manualscaler 作为不计入版本运维特征,与 Deployment 的伸缩类似,当我们仅修改 manualscaler ,新的实例数量会被计入到最新版本,但当我们的版本真正发生改变产生新的版本后,再次手动修改了实例数量,最后因为某些原因回滚到上一个版本时,此时实例数量将发生回滚(如下图)。而通常情况下,决定应用实例数量的原因不在其处于什么版本,而在当前的资源使用率、流量等环境因素。且在 Deployment 的使用中,实例数量也不受版本的影响。


1.png


基于以上需求,谐云提出了一套另一种思路的版本管理设计[1]在记录版本时,将彻底忽略 skipRevisionAffect 的 Trait,在进行版本回滚时,将当前 Application 中包含的 skipRevisionAffect 的 Trait 合并到目标版本中,这样便是的这些纯运维的 Trait 不会随着应用版本的改变而改变。


下图是该设计的版本管理过程


  • testapp 应用中包含 nginx 组件,且镜像版本为 1.16.0,其中包含三个运维特征,manualreplicas 控制其实例数量,是 skipRevisionAffect 的 Trait,该应用发布后,版本管理控制器将记录该版本至自定义的 Revision 中,且将 manualreplicas 从组件的运维特征中删除;
  • 当修改 testapp 的镜像版本、实例数量及其他运维特征,发生升级时,将生成新版本的 Revision,且 manualreplicas 仍被删除;
  • 此时若发生回滚,新的 Application 将使用 v1 版本 Revision 记录的信息与回滚前版本(v2)进行合并,得到实例数量为 2 的 1.6.0 的 nginx 组件。

2.png


版本升级


版本管理除了版本控制和回滚之外,还需要关注应用的升级过程,社区目前较为流行的方式是使用 kruise-rollout 的 Rollout 资源对工作负载进行金丝雀升级。我们在使用 kruise-rollout 时发现,在金丝雀升级过程中,应用新旧版本最多可能同时存在两倍实例数量的实例,在某些资源不足的环境中,可能出现由于资源不足导致实例无法启动,从而阻塞升级过程。


基于以上场景,我们在 kruise-rollout 上进行了二次开发,添加了滚动升级的金丝雀策略,能够使得应用在升级过程中通过新版本滚动替换旧版本实例,控制实例数量总数最大不超过实例数量+滚动步长。


但这么做仍然存在一些问题,例如 kruise-rollout 能够完全兼容升级过程中的实例扩缩场景(无论是 hpa 触发还是手动扩缩),但带有滚动策略的升级过程一开始就需要确定总的升级实例数量,且在升级过程中,hpa 和手动扩缩容都将失效。


我们认为带有滚动策略的金丝雀发布仍在某些资源不足的场景下是有用的,所有并没有更改社区 kruise-rollout 的策略,仅是在社区的版本上做了一些补充。


以下是社区版本的金丝雀升级过程与带有滚动策略的金丝雀升级的过程


  • 社区金丝雀升级过程:


3.png


  • 带有滚动策略的金丝雀升级过程:

4.png


小结

我们在基于 KubeVela 的应用模型上对应用版本管理采用了另外一种思路,主要为了满足上文中描述的场景,应用版本管理的整体架构如下:


5.png


  • 通过 vela-core 管理应用模型
  • 通过自研的 rollback-controller 进行应用版本控制和应用回滚
  • 通过二次开发的 kruise-rollout 进行应用升级


应用纳管


在接入 KubeVela 的同时,面对存量集群的应用模型纳管也是一个值得思考的话题。对于谐云而言,将 KubeVela 定义为新版本容器云的唯一应用模型,在平台升级过程中,纳管问题也是无法避免的。


由于我们定义的 ComponentDefinition 和存量集群中的工作负载在大部分情况下都存在差异,直接将原有的工作负载转换为 Component 将导致存量业务的重启。而平台升级后,KubeVela 作为我们的唯一模型,我们需要在业务上能够看到原有的应用,但不希望它直接重启,而是在期望的时间窗口有计划地按需重启。


为了解决上述矛盾,我们提出了以下纳管思路


首先要做的是在平台升级过程中,尽可能地不去影响原有的应用,即在首次纳管时我们通过社区中提供的 ref-objects 组件对现有的工作负载进行纳管。由于容器云产品中面向的是 Application 资源,此时被纳管的组件在应用模型中无法进行日常的运维操作(没有可用的运维特征),但仍可以通过工作负载资源直接运维(如直接操作 Deployment)。


6.png


我们将工作负载及其关联资源转换为 Component 的关键在于理解 Definition。在 KubeVela 中,工作负载及其关联资源是通过 cuelang 进行渲染生成的,这是一个开放的模型,我们无法假定 Definition 的内容,但我们期望编写 Definition 的人员可以同时编写 Decompile 资源指导程序如何将工作负载及其关联资源转换为 Component 或 Trait。


这就类似于我们将 Definition 作为一次事务,而回滚时要执行的动作由 Decompile 决定,两者都是开放的,具体行为取决于开发者。


在首次纳管之后到下一次纳管目标组件进行版本升级之前,我们将持续保持上述状态,等到该组件进行升级时,我们将通过“反编译”将纳管目标工作负载及其关联资源转换为 Component + Trait,并将需要升级的部分合并到反编译的结果中,通过容器云平台更新到 Application 中,彻底完成应用模型的转换


该过程如下图所示:


7.png


示例


例如,我们包含一个节点亲和类型的运维特征:


# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
  annotations:
    definition.oam.dev/description: Add nodeAffinity for your Workload
  name: hc.node-affinity
  namespace: vela-system
spec:
  appliesToWorkloads:
  - hc.deployment
  podDisruptive: true
  schematic:
    cue:
      template: |
        parameter: {
          isRequired: bool
          labels: [string]: string
        }
        patch: spec: template: spec: affinity: nodeAffinity: {
          if parameter.isRequired == true {
            // +patchKey=matchExpressions
            requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [
              for k, v in parameter.labels {
                {
                  matchExpressions: [
                    {
                      key:      k
                      operator: "In"
                      values: [v]
                    },
                  ]
                }
              },
            ]
          }
          if parameter.isRequired == false {
            // +patchKey=preference
            preferredDuringSchedulingIgnoredDuringExecution: [
              for k, v in parameter.labels {
                {
                  weight: 50
                  preference: matchExpressions: [{
                    key:      k
                    operator: "In"
                    values: [v]
                  }]
                }
              },
            ]
          }
        }


同时我们还包含一个从 Deployment 节点亲和到 Trait 转换的 Decompile 资源(它的 cuelang 模型与 Trait 类似,都是通过参数和输出部分组成,只是在正向过程中,output 输出的是 CR,而在本过程中,output 输出的是 component 或是 trait):


apiVersion: application.decompile.harmonycloud.cn/v1alpha1
kind: DecompileConfig
metadata:
  annotations:
    "application.decompile.harmonycloud.cn/description": "decompiling deployment node affinity to application"
  labels:
    "decompiling/apply": "true"
    "decompiling/type": "node-affinity"
  name: node-affinity-decompile
  namespace: vela-system
spec:
  targetResource:
    - deployment
  schematic:
    cue:
      template: |
        package decompile
        import (
          "k8s.io/api/apps/v1"
          core "k8s.io/api/core/v1"
        )
        parameter: {
          deployment: v1.#Deployment
        }
        #getLabels: {
          x="in": core.#NodeSelectorTerm
          out: [string]: string
          if x.matchExpressions != _|_ {
            for requirement in x.matchExpressions {
              key: requirement.key
              if requirement.operator == "In" {
                if requirement.values == _|_ {
                  out: "\(key)": ""
                }
                if requirement.values != _|_ {
                  for i, v in requirement.values {
                    if i == 0 {
                      out: "\(key)": v
                    }
                  }
                }
              }
            }
          }
          if x.matchFields != _|_ {
            for requirement in x.matchFields {
              if requirement.operator == "In" {
                if requirement.values == _|_ {
                  out: "\(requirement.key)": ""
                }
                if requirement.values != _|_ {
                  for i, v in requirement.values {
                    if i == 0 {
                      out: "\(requirement.key)": v
                    }
                  }
                }
              }
            }
          }
        }
        #outputParameter: {
          isRequired: bool
            labels: [string]: string
        }
        #decompiling: {
          x="in": v1.#Deployment
          out?: #outputParameter
          if x.spec != _|_ && x.spec.template.spec != _|_ && x.spec.template.spec.affinity != _|_ && x.spec.template.spec.affinity.nodeAffinity != _|_  {
            nodeAffinity: x.spec.template.spec.affinity.nodeAffinity
            if nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution != _|_ {
              out: isRequired: true
              for term in nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms {
                result: #getLabels & {in: term}
                if result.out != _|_ {
                  for k, v in result.out {
                    out: labels: {
                      "\(k)": "\(v)"
                    }
                  }
                }
              }
            }
            if nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution != _|_ {
              out: isRequired: false
              for schedulingTerm in nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution {
                if schedulingTerm.preference != _|_ {
                  result: #getLabels & {in: schedulingTerm.preference}
                  if result.out != _|_ {
                    for k, v in result.out {
                      out: labels: {
                        "\(k)": "\(v)"
                      }
                    }
                  }
                }
              }
            }
          }
        }
        result: #decompiling & {in: parameter.deployment}
        output: traits: [
          if result.out != _|_ {
            {
              type: "hc.node-affinity"
              properties: #outputParameter & result.out
            }
          }
        ]


(由于长度原因,省略了 hc.deployment 的正反渲染)


我们在集群中创建这样一个 Deployment:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: area
                operator: In
                values:
                - east
      containers:
      - image: 10.120.1.233:8443/library/nginx:1.21
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP


通过调用 kubevela-decompile-controller 提供的 API,将 demo-app 进行转换,将得到如下结果,平台可以将 data 中的 component 替换掉 Application 中的 ref-objects 组件。


8.png


{
  "code": 0,
  "message": "success",
  "data": {
    "components": [
      {
        "name": "demo-app",
        "type": "hc.deployment",
        "properties": {
          "initContainers": [],
          "containers": [
            {
              "name": "nginx",
              "image": "10.120.1.233:8443/library/nginx:1.21",
              "imagePullPolicy": "IfNotPresent",
              "ports": [
                {
                  "port": 80,
                  "protocol": "TCP"
                }
              ]
            }
          ]
        },
        "traits": [
          {
            "type": "hc.dns",
            "properties": {
              "dnsPolicy": "ClusterFirst"
            }
          },
          {
            "type": "hc.node-affinity",
            "properties": {
              "isRequired": true,
              "labels": {
                "area": "east"
              }
            }
          },
          {
            "type": "hc.manualscaler",
            "properties": {
              "replicas": 1
            }
          }
        ]
      }
    ]
  }
}


小结


谐云通过类比事务的方式,将渲染过程分为正向和逆向,同时将首次纳管和真正的纳管动作进行了分离,完成了平台升级的同时,给应用的纳管行为留下了一定的可操作空间。这是一种应用纳管的思路,近期社区当中对于应用纳管的讨论也十分火热,并且在 1.7 的版本更新中也推出了应用纳管的能力[2],同时同样支持“反向渲染”的功能,能够支持我们将现有的纳管模式迁移到社区的功能中。


结语


到此,内容分享结束,希望能够引发更多思考,对社区功能做出贡献


您可以通过如下材料了解更多关于 KubeVela 以及 OAM 项目的细节:


  • 项目代码库:
    github.com/oam-dev/kubevela
    欢迎 Star/Watch/Fork!
  • 项目官方主页与文档:kubevela.io
    从 1.1 版本开始,已提供中文、英文文档,更多语言文档欢迎开发者进行翻译。
  • 项目钉钉群:23310022;Slack:CNCF #kubevela Channel
  • 加入微信群:请先添加以下 maintainer 微信号,表明进入KubeVela用户群:


9.png


相关链接


[1] 版本管理能力

https://github.com/kubevela/kubevela/issues/2168


[2] 应用纳管的能力

https://kubevela.net/docs/next/end-user/policies/resource-adoption#use-in-application


此处查看 KubeVela 项目官网

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
运维 Cloud Native 持续交付
深入理解云原生架构及其在现代企业中的应用
随着数字化转型的浪潮席卷全球,企业正面临着前所未有的挑战与机遇。云计算技术的迅猛发展,特别是云原生架构的兴起,正在重塑企业的IT基础设施和软件开发模式。本文将深入探讨云原生的核心概念、关键技术以及如何在企业中实施云原生策略,以实现更高效的资源利用和更快的市场响应速度。通过分析云原生架构的优势和面临的挑战,我们将揭示它如何助力企业在激烈的市场竞争中保持领先地位。
|
1月前
|
运维 Cloud Native 安全
云原生技术在现代企业中的应用与挑战####
本文探讨了云原生技术在现代企业IT架构中的关键作用,分析了其带来的优势和面临的主要挑战。通过实际案例分析,揭示了如何有效应对这些挑战,以实现业务敏捷性和技术创新的平衡。 ####
|
1月前
|
Cloud Native 持续交付 开发者
云原生技术在现代企业中的应用与实践####
本文深入探讨了云原生技术的核心概念及其在现代企业IT架构转型中的关键作用,通过具体案例分析展示了云原生如何促进企业的敏捷开发、高效运维及成本优化。不同于传统摘要仅概述内容,本部分旨在激发读者对云原生领域的兴趣,强调其在加速数字化转型过程中的不可或缺性,为后续详细论述奠定基础。 ####
|
1月前
|
人工智能 缓存 异构计算
云原生AI加速生成式人工智能应用的部署构建
本文探讨了云原生技术背景下,尤其是Kubernetes和容器技术的发展,对模型推理服务带来的挑战与优化策略。文中详细介绍了Knative的弹性扩展机制,包括HPA和CronHPA,以及针对传统弹性扩展“滞后”问题提出的AHPA(高级弹性预测)。此外,文章重点介绍了Fluid项目,它通过分布式缓存优化了模型加载的I/O操作,显著缩短了推理服务的冷启动时间,特别是在处理大规模并发请求时表现出色。通过实际案例,展示了Fluid在vLLM和Qwen模型推理中的应用效果,证明了其在提高模型推理效率和响应速度方面的优势。
云原生AI加速生成式人工智能应用的部署构建
|
1月前
|
Cloud Native JavaScript Docker
云原生技术:构建现代应用的基石
在数字化转型的浪潮中,云原生技术如同一艘承载梦想的航船,引领企业驶向创新与效率的新海域。本文将深入探索云原生技术的核心价值,揭示其如何重塑软件开发、部署和运维模式,同时通过一个简易代码示例,展现云原生应用的构建过程,让读者领略到云原生技术的魅力所在。
|
1月前
|
Kubernetes Cloud Native 微服务
探索云原生技术:容器化与微服务架构的融合之旅
本文将带领读者深入了解云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同构建现代软件系统。我们将通过实际代码示例,探讨如何在云平台上部署和管理微服务,以及如何使用容器编排工具来自动化这一过程。文章旨在为开发者和技术决策者提供实用的指导,帮助他们在云原生时代中更好地设计、部署和维护应用。
|
2月前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
64 3
|
2月前
|
Cloud Native 持续交付 云计算
云原生架构的演进与挑战
随着云计算技术的不断发展,云原生架构已成为企业数字化转型的重要支撑。本文深入探讨了云原生架构的概念、发展历程、核心技术以及面临的挑战,旨在为读者提供一个全面了解云原生架构的视角。通过分析Kubernetes、Docker等关键技术的应用,以及微服务、持续集成/持续部署(CI/CD)等实践案例,本文揭示了云原生架构在提高应用开发效率、降低运维成本、增强系统可扩展性等方面的显著优势。同时,也指出了云原生架构在安全性、复杂性管理等方面所面临的挑战,并提出了相应的解决策略。
|
2天前
|
人工智能 编解码 自然语言处理
AI运用爆发时代, 视频服务云原生底座“视频云”架构的全智能再进化
本文介绍了AI运用爆发时代下,视频服务云原生底座“视频云”架构的全智能再进化。随着AI技术的发展,视频内容和交互方式正经历深刻变革。文章从背景、视频AI应用挑战、视频云网端底座、AIGC时代的全智能化及未来展望五个方面展开讨论。重点阐述了云、网、端三者如何深度融合,通过AI赋能视频采集、生产、分发和消费全流程,实现视频处理的智能化和高效化。同时,展望了未来AI在视频领域的创新应用和潜在的杀手级应用。
|
1月前
|
运维 Cloud Native 持续交付
云原生技术深度探索:重塑现代IT架构的无形之力####
本文深入剖析了云原生技术的核心概念、关键技术组件及其对现代IT架构变革的深远影响。通过实例解析,揭示云原生如何促进企业实现敏捷开发、弹性伸缩与成本优化,为数字化转型提供强有力的技术支撑。不同于传统综述,本摘要直接聚焦于云原生技术的价值本质,旨在为读者构建一个宏观且具体的技术蓝图。 ####