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

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测监控 Prometheus 版,每月50GB免费额度
简介: 谐云通过类比事务的方式,将渲染过程分为正向和逆向,同时将首次纳管和真正的纳管动作进行了分离,完成了平台升级的同时,给应用的纳管行为留下了一定的可操作空间。

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


在 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架构转型中的关键作用,通过具体案例分析展示了云原生如何促进企业的敏捷开发、高效运维及成本优化。不同于传统摘要仅概述内容,本部分旨在激发读者对云原生领域的兴趣,强调其在加速数字化转型过程中的不可或缺性,为后续详细论述奠定基础。 ####
|
4天前
|
运维 Cloud Native 开发工具
智能运维:云原生大规模集群GitOps实践
智能运维:云原生大规模集群GitOps实践,由阿里云运维专家钟炯恩分享。内容涵盖云原生运维挑战、管理实践、GitOps实践及智能运维体系。通过OAM模型和GitOps优化方案,解决大规模集群的发布效率与稳定性问题,推动智能运维工程演进。适用于云原生环境下的高效运维管理。
|
1月前
|
Cloud Native 安全 Java
铭师堂的云原生升级实践
铭师堂完整经历了云计算应用的四个关键阶段:从”启动上云”到”全量上云”,再到”全栈用云”,最终达到”精益用云”。通过 MSE 云原生网关的落地,为我们的组织带来了诸多收益,SLA 提升至100%,财务成本降低67%,算力成本降低75%,每次请求 RT 减少5ms。
铭师堂的云原生升级实践
|
20天前
|
存储 人工智能 调度
容器服务:智算时代云原生操作系统及月之暗面Kimi、深势科技实践分享
容器技术已经发展成为云计算操作系统的关键组成部分,向下高效调度多样化异构算力,向上提供统一编程接口,支持多样化工作负载。阿里云容器服务在2024年巴黎奥运会中提供了稳定高效的云上支持,实现了子弹时间特效等创新应用。此外,容器技术还带来了弹性、普惠的计算能力升级,如每分钟创建1万Pod和秒级CPU资源热变配,以及针对大数据与AI应用的弹性临时盘和跨可用区云盘等高性能存储解决方案。智能运维方面,推出了即时弹性节点池、智能应用弹性策略和可信赖集群托管运维等功能,进一步简化了集群管理和优化了资源利用率。
|
1月前
|
人工智能 缓存 异构计算
云原生AI加速生成式人工智能应用的部署构建
本文探讨了云原生技术背景下,尤其是Kubernetes和容器技术的发展,对模型推理服务带来的挑战与优化策略。文中详细介绍了Knative的弹性扩展机制,包括HPA和CronHPA,以及针对传统弹性扩展“滞后”问题提出的AHPA(高级弹性预测)。此外,文章重点介绍了Fluid项目,它通过分布式缓存优化了模型加载的I/O操作,显著缩短了推理服务的冷启动时间,特别是在处理大规模并发请求时表现出色。通过实际案例,展示了Fluid在vLLM和Qwen模型推理中的应用效果,证明了其在提高模型推理效率和响应速度方面的优势。
云原生AI加速生成式人工智能应用的部署构建
|
1月前
|
Cloud Native
邀您参加云原生高可用技术沙龙丨云上高可用体系构建:从理论到实践
云原生高可用技术专场,邀您从理论到实践一起交流,探索云上高可用体系构建!
|
1月前
|
Cloud Native 安全 Java
杭州铭师堂的云原生升级实践
在短短 2-3 年间,杭州铭师堂完整经历了云计算应用的四个关键阶段:从“启动上云”到“全量上云”,再到“全栈用云”,最终达到“精益用云”。也从云计算的第一次浪潮,迈过了第二次浪潮,顺利的进入到了 第三次浪潮 AI + 云。
118 11
|
20天前
|
运维 监控 Cloud Native
云原生之运维监控实践:使用 taosKeeper 与 TDinsight 实现对 时序数据库TDengine 服务的监测告警
在数字化转型的过程中,监控与告警功能的优化对保障系统的稳定运行至关重要。本篇文章是“2024,我想和 TDengine 谈谈”征文活动的三等奖作品之一,详细介绍了如何利用 TDengine、taosKeeper 和 TDinsight 实现对 TDengine 服务的状态监控与告警功能。作者通过容器化安装 TDengine 和 Grafana,演示了如何配置 Grafana 数据源、导入 TDinsight 仪表板、以及如何设置告警规则和通知策略。欢迎大家阅读。
47 0
|
1月前
|
Cloud Native API 持续交付
云原生架构下的微服务治理策略与实践####
本文旨在探讨云原生环境下微服务架构的治理策略,通过分析当前面临的挑战,提出一系列实用的解决方案。我们将深入讨论如何利用容器化、服务网格(Service Mesh)等先进技术手段,提升微服务系统的可管理性、可扩展性和容错能力。此外,还将分享一些来自一线项目的经验教训,帮助读者更好地理解和应用这些理论到实际工作中去。 ####
52 0
|
1月前
|
运维 Cloud Native 持续交付
深入理解云原生架构及其在现代企业中的应用
随着数字化转型的浪潮席卷全球,企业正面临着前所未有的挑战与机遇。云计算技术的迅猛发展,特别是云原生架构的兴起,正在重塑企业的IT基础设施和软件开发模式。本文将深入探讨云原生的核心概念、关键技术以及如何在企业中实施云原生策略,以实现更高效的资源利用和更快的市场响应速度。通过分析云原生架构的优势和面临的挑战,我们将揭示它如何助力企业在激烈的市场竞争中保持领先地位。