前言

Kubernetes本身只解决了容器调度、资源管理等底层编排问题。

而一个云原生应用从构建、交付、运维,往往需要组合使用Kruise、Prometheus、OpenTelemetry、KEDA、Istio等大量K8S生态组件。

随着能力不断叠加,系统功能并不缺失,但每个组件都从各自领域出发,关注点分散在流水线、监控、扩缩容、流量治理等局部能力上,缺乏一个以应用为中心的统一抽象和管理视角,最终导致平台集成成本高、心智负担重、整体复杂度显著上升。

KubeVela通过内置OAM规范,将碎片化的K8s原生资源统一组装,构建出更易于部署、易于观测、易于管理的标准化应用。

KubeVela = 总指挥,把计算资源、网络流量规则、存储资源统一打包成一个完整应用进行生命周期管理;

缺少这一层统一收纳,各类CRD、PVC、工作负载分散孤立,如同散落一地的零件,长期维护必然混乱,极易产生僵尸资源、配置不同步问题。

1.HelmChart和KubeVela区别?

HelmChart擅长单点部署,KubeVela擅长批量编排与全生命周期管理。

HelmChart是Kubernetes的安装工具,KubeVela采用OAM描述业务应用,是面向云原生业务应用的交付与运维引擎。

很多人认为KubeVela与HelmChart功能类似是用于生成一堆YAML文件并部署到Kubernetes。

事实上,上Kubernetes的最终目标并不是把应用跑起来,而是将一个云原生应用长期、稳定、可规模化地运行和管理。

当你已经不再满足于仅通过HelmChart将应用部署到K8s,而开始关注应用上线后,如何被持续治理和规模化运维时,KubeVela才真正进入视野。

2.ArgoCD和KubeVela中应用的区别?

GitOps决定权可以完全在开发,但它依然是以资源声明和Git 仓库为中心;

KubeVela 决定权也是开发,但它是以应用抽象和平台能力为中心,所以开发者关注的是应用做什么,而不是具体怎么写YAML写Deployment。

为什么Helm不应该直接作为业务应用的CD入口?

ArgoCD中的应用是对KubernetesYAML的同步描述,本质是配置级对象;

KubeVela中的应用是基于OAM规范的应用模型,描述的是应用的结构、能力和交付流程,两者在抽象层级上存在本质差异。

应用部署需要1个云原生应用交付和管理的平台-KubeVela,屏蔽底层K8s面临复杂操作;

KubeVela围绕着云原生应用交付和管理场景展开,背后的应用交付模型是 Open Application Model,简称OAM;

OAM是1个云原生应用定义+交付模型,允许开发者和运维人员通过标准的声明式方式定义和管理应用,而无需关心底层基础设施的复杂性。

它提供了1种面向应用的抽象,强调以应用为中心,而非底层资源(如Pod、Service 等)。

3.总结

CD系统的本质,并非YAML流水线,而是应用模型到运行态的持续转译系统。

image

参考GitOps实践在GitLab中统一管理以下3类配置来源:

  • 基础设施层(Infrastructure Layer)
    使用Terraform(基础设施资源声明规范IaC)描述云资源与基础设施能力,如 VPC、ACK / Kubernetes 集群、负载均衡、云数据库等。

  • 平台能力层(Platform Capability Layer)
    使用HelmChart(标准化Chart规范)描述平台级服务与中间件能力,例如 KEDA 、Prometheus、Harbor、Ingress Controller、Istio、Conjur 等,为上层应用提供可复用的运行环境与通用能力。

  • 业务应用层(Application Layer)
    使用KubeVela(OAM 规范) 描述和管理业务应用,如 Java、Python、Golang、Node.js 等应用实例,关注应用的运行特性、交付策略和运维能力,而非底层资源细节。

各层之间通过能力接口进行解耦:

  • 上层仅通过 Secret/ConfigMap/Service等抽象接口使用下层能力

  • 禁止跨层直接操作资源,避免平台与业务的强耦合

最终形成清晰稳定的CD演进路径:

基础设施层变更频率低、平台能力层相对稳定、业务应用层迭代频繁

CD系统的职责,是在保持下层稳定性的前提下,支撑业务应用的高速持续交付。

一、为什么要上K8s?

Kubernets封装了Iaas层,是1个标准化的能力接入层;

声明式API对象

CustomResourece:自定义底层基础设施的抽象

Node是对宿主机的抽象

Service是对负载均衡、网关、IPtables访问规则的抽象

Deployment是对Pod的抽象

Pod是对容器的抽象

控制器层

各种控制器(Controller):控制器通过控制循环驱动底层基础设施,不断接近抽象层声明API中定义终态; 

标准化能力接入层

虽然K8s已经封装了底层基础设施的复杂性,变成了一种标准化的能力接入层,但对于业务研发和业务运维来说还是比较复杂;

所以需要构建1个面向最终用户的应用管理平台,减少业务接入K8s的复杂程度;

二、CD

CI的核心是标准化自动化的工作流程,而CD远比CI复杂的多,复杂度集中在应用交付的全生命周期管控,涵盖应用描述、部署编排、版本回滚、发布策略等核心维度,二者的核心差异也正源于此。

1.需求来源

在运维与应用交付领域,表面上看变化纷繁复杂,但从本质上看,所有交付行为始终围绕着少数稳定且长期存在的需求展开。

运维世界有以下3类核心变更需求:

基础设施配置变更

关注的是应用运行所依赖的底层环境,包括计算、网络、存储以及权限与安全配置等内容。这类变更的影响面通常较广,一旦出现问题往往会对多个系统产生连锁影响。即便在基础设施逐步实现代码化管理之后,这类变更依然需要通过规范化的交付流程进行统一控制,而不能被视为独立于应用交付之外的操作。

应用版本变更

来源于业务功能的持续演进,是最常见、也是最直观的一类变更需求。它不仅包含新功能上线和缺陷修复,还包括性能优化、架构调整等场景。对于这类变更而言,真正的挑战并不在于如何完成部署动作,而在于如何在交付过程中控制风险、限制影响范围,并在异常发生时具备快速恢复的能力。

配置变更

通常具有更高的频率和更低的感知度。参数调整、功能开关变化、外部依赖信息更新等操作,往往不会触发完整的发布流程,但其对系统行为的影响却可能与版本变更等量齐观。如果配置变更游离于统一交付体系之外,就会逐渐演变为不可审计、不可回滚的高风险操作,因此配置变更同样构成了 CD 必须正面承接的一类核心需求。

2.功能

三大核心变更定义了CD的需求边界,CD的所有功能,都是为了承接和控制这些变更而存在。

云原生应用描述能力

用于回答一个根本问题:我要交付的是什么?”

包括但不限于:

  • 应用组件结构

  • 服务依赖关系

  • 运行所需资源

  • 环境差异化配置

这一层解决的是交付对象的标准化描述问题。

渲染与参数化能力

将抽象的应用描述,结合环境、参数、配置,渲染为最终可执行的部署清单(Manifest)。

其核心价值在于:

  • 实现一次定义,多环境复用

  • 避免人为修改带来的不确定性

  • 为自动化交付提供稳定输入

部署与发布策略

针对应用版本变更,CD 需要支持多种发布策略,例如:

  • 滚动更新

  • 蓝绿发布

  • 灰度 / 金丝雀发布

  • 分批次发布

发布策略的本质是:控制变更的生效范围与生效节奏。

回滚与恢复能力

CD不保证发布一定成功而是默认,失败是系统设计的一部分。

因此必须具备:

  • 快速回滚能力

  • 自动或半自动触发机制

  • 与版本、状态、指标绑定的恢复策略

部署编排与依赖控制

在真实场景中,交付往往涉及多个组件、多阶段操作,CD 需要提供:

  • 发布顺序控制

  • 依赖关系编排

  • 串行与并行执行能力

以保证系统整体的一致性与稳定性。

变更治理与审计能力

作为变更系统,CD 必须具备治理属性,包括:

  • 变更审批

  • 权限控制

  • 操作审计

  • 发布记录追溯

这是CD与单纯部署工具的本质分界线。

 

三、OAM规范

K8s本身只解决了容器调度、资源管理等底层编排问题,而1个完整的云原生应用从构建、交付、发布到运行、运维,往往需要组合使用OpenKruise、Logki、Prometheus、OpenTelemetry、KEDA、Istio等大量k8s生态组件。

随着功能和能力不断叠加,系统功能并不缺失,但每个组件都从各自领域出发,关注点分散在流水线、监控、扩缩容、流量治理等局部能力上,缺乏一个以应用为中心的统一抽象和管理视角,最终导致平台集成成本高、心智负担重、CD整体复杂度显著上升。

可以按照OAM标准,以应用为中心设计应用管理平台;

1.应用

在KubeVela/OAM体系中,Application是一等资源用于描述1个完整的应用交付单元。
一个Application由以下核心部分组成:
  • 组件(Component) Application 可以包含一个或多个组件。每个组件用于描述一个可独立部署和运维的工作负载(如 Web 服务、任务、状态型服务等)。
  • 运行特征(Trait) 1个组件可以绑定一个或多个运行特征(Trait),用于声明组件在运行期的运维行为和非功能属性,例如弹性伸缩、网络暴露、资源限制、滚动升级策略等。
  • 工作流(Workflow) Application可以定义一个交付工作流(Workflow),工作流由多个工作步骤(Workflow Step)组成,用于控制 Application 中各组件及其特征的部署顺序、发布策略以及交付过程中的自动化操作(如灰度发布、通知、校验等)。
  • 策略(Policy) Application可以关联一个或多个策略(Policy),用于描述应用交付过程中的全局约束和调度规则,例如多集群部署、环境绑定、配置覆盖、资源接管和回收策略等。策略通常与工作流中的部署步骤协同生效。
 以下是详细介绍

2.Component组件

组件定义
层级 组件类型 中文名称 描述
Component cron-task 定时任务组件 Describes cron jobs that run code or a script to completion.
Component daemon 守护进程组件 Describes daemonset services in Kubernetes.
Component helm Helm 组件 helm release is a group of K8s resources from either git repository or helm repo
Component k8s-objects 原生对象组件 K8s-objects allow users to specify raw K8s objects in properties
Component kustomize Kustomize 组件 kustomize can fetching, building, updating and applying Kustomize manifests from Git repo or Bucket or OCI repo.
Component raw 原始对象组件(废弃) Raw allow users to specify raw K8s object in properties. This definition is DEPRECATED
Component ref-objects 资源引用组件 Ref-objects allow users to specify ref objects to use. Notice that this component type have special handle logic.
Component statefulset 有状态服务组件 Describes long-running, scalable, containerized services used to manage stateful application, like database.
Component task 一次性任务组件 Describes jobs that run code or a script to completion.
Component webservice Web 服务组件 Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.
Component worker 后台服务组件 Describes long-running, scalable, containerized services that running at backend. They do NOT have network endpoint to receive external network traffic.

3.Trait特征

组件运行的特征定义
层级 特征类型 中文名称 英文描述
Trait affinity 亲和性与容忍 Affinity specifies affinity and toleration K8s pod for your workload
Trait annotations 注解 Add annotations on your workload
Trait command 启动命令 Add command on K8s pod for your workload
Trait configmap ConfigMap(废弃) Create/Attach configmaps on K8s pod (DEPRECATED)
Trait container-image 容器镜像 Set the image of the container.
Trait container-ports 容器端口 Expose on the host and bind the external port
Trait cpuscaler CPU 自动伸缩 Automatically scale the component based on CPU usage.
Trait env 环境变量 Add env on K8s pod for your workload
Trait expose 服务暴露 Expose port to enable web traffic for your component.
Trait gateway 网关 Enable public web traffic for the component
Trait helm-labels Helm 标签 Patch labels of Pods created by workloads defined in Helm Charts
Trait hostalias HostAlias Add host aliases on K8s pod
Trait hpa HPA Configure k8s HPA for Deployment or Statefulsets
Trait ingress Ingress Enable public web traffic for the component.
Trait init-container Init 容器 add an init container and use shared volume with pod
Trait json-merge-patch JSON Merge Patch Patch the output following Json Merge Patch strategy
Trait json-patch JSON Patch Patch the output following Json Patch strategy
Trait k8s-update-strategy 更新策略 Set k8s update strategy for Deployment/DaemonSet/StatefulSet
Trait kruise-rollout Kruise 灰度 Rollout workload by kruise controller.
Trait kustomize-patch Kustomize Patch A list of StrategicMerge or JSON6902 patch
Trait labels 标签 Add labels on your workload
Trait lifecycle 生命周期 Add lifecycle hooks for every container
Trait node-affinity 节点亲和 affinity specify node affinity and toleration
Trait podsecuritycontext Pod 安全上下文 Adds security context to the pod spec
Trait pvc PVC(废弃) Create a Persistent Volume Claim (DEPRECATED)
Trait resource 资源限制 Add resource requests and limits
Trait rolling-release 滚动发布 Rolling workload with specified weight via kruise rollout
Trait scaler 副本伸缩 Manually scale K8s pod
Trait securitycontext 容器安全上下文 Adds security context to the container spec
Trait service-account ServiceAccount Specify serviceAccount for your workload
Trait sidecar Sidecar Inject a sidecar container
Trait startup-probe 启动探针 Add startup probe hooks
Trait storage 存储 Add storages on K8s pod
Trait topologyspreadconstraints 拓扑分布 Add topology spread constraints hooks
Trait volumes Volume(废弃) Add volumes on K8s pod (DEPRECATED)

4.Workflow Step部署步骤

应用部署时支持的工作流步骤
层级 workflow步骤类型 中文名称 描述
Workflow apply-component 组件部署 Apply a specific component and its corresponding traits
Workflow apply-application 应用部署 Apply application for your workflow steps
Workflow apply-application-in-parallel 并行部署 Apply components of an application in parallel
Workflow canary-deploy 灰度发布 A canary rollout step for components multi-cluster delivery
Workflow notification 发布通知 Send notifications to Email, DingTalk, Slack, Lark or webhook
Workflow request 请求调用 Send request to the url
Workflow suspend 暂停 Suspend the current workflow
Workflow webhook Webhook Send a POST request to the specified Webhook URL
Workflow chat-gpt ChatGPT 调用 Send request to chat-gpt

5.Policy策略定义

层级 策略类型 中文名称 英文描述(原样)
Policy apply-once 一次性应用策略 Allow configuration drift for applied resources
Policy envbinding 环境绑定策略 Determining the destination where components should be deployed to
Policy garbage-collect 垃圾回收策略 Configure the garbage collect behaviour for the application
Policy override 配置覆盖策略 Describe the configuration to override when deploying resources
Policy read-only 只读策略 Configure the resources to be read-only
Policy replication 复制策略 Describe the configuration to replicate components
Policy resource-update 更新策略 Configure the update strategy for selected resources
Policy shared-resource 共享资源策略 Configure the resources to be sharable across applications
Policy take-over 接管策略 Configure the resources to be able to take over
Policy topology 拓扑策略 Describe the destination where components should be deployed to

 

四、KubeVela

KubeVela是1个开箱即用的现代化应用交付与管理平台,它使得应用在面向混合云环境中的交付更简单、快捷,简化Kubenetes使用细节。

使用 KubeVela的软件开发团队,可以按需使用云原生能力(Addon)构建应用,随着团队规模的发展、业务场景的变化扩展其功能,一次构建应用,随处运行。

1.KubeVela

KubeVela围绕着云原生应用交付和管理场景展开,背后的应用交付模型是 Open Application Model,简称 OAM 。

image

安装kubeVela

root@master01:/home/zhanggen# helm repo add kubevela https://kubevela.github.io/charts
"kubevela" has been added to your repositories
root@master01:/home/zhanggen# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "kubevela" chart repository
Update Complete. ⎈Happy Helming!⎈
root@master01:/home/zhanggen# helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wait
NAME: kubevela
LAST DEPLOYED: Tue Jan 27 02:25:38 2026
NAMESPACE: vela-system
STATUS: deployed
REVISION: 1
NOTES:
Welcome to use the KubeVela! Enjoy your shipping application journey!

                                   ,
                                   //,
                                   ////
                               ./  /////*
                             ,///  ///////
                           ./////  ////////
                          ///////  /////////
                         ////////  //////////
                       ,/////////  ///////////
                      ,//////////  ///////////.
                     .///////////  ////////////
                     ////////////  ////////////.
                    *////////////  ////////////*
       #@@@@@@@@@@@*     ..,,***/  /////////////
        /@@@@@@@@@@@#
         *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&
          .@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.

              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                .&@@@*    *@@@&    ,@@@&.

       _  __       _          __     __     _
      | |/ /_   _ | |__    ___\ \   / /___ | |  __ _
      | ' /| | | || '_ \  / _ \\ \ / // _ \| | / _` |
      | . \| |_| || |_) ||  __/ \ V /|  __/| || (_| |
      |_|\_\\__,_||_.__/  \___|  \_/  \___||_| \__,_|


You can refer to https://kubevela.io for more details.

SECURITY RECOMMENDATION: Both authentication and definition validation are disabled.
   If KubeVela is running with cluster-admin or other high-level permissions,
   consider enabling one or both security features:

   1. Authentication with impersonation (recommended for multi-tenant environments):
      --set authentication.enabled=true 
      --set authentication.withUser=true
      This makes KubeVela impersonate the requesting user, applying their RBAC permissions.
      Note: Both flags must be enabled for user impersonation to work.

   2. Definition permission validation (lightweight RBAC for definitions):
      --set authorization.definitionValidationEnabled=true
      This ensures users can only reference definitions they have access to.

   Using both features together provides defense in depth.
   Without these protections, users can leverage KubeVela's permissions to deploy
   resources beyond their intended access level.
root@master01:/home/zhanggen# 
View Code

KubeVela的本质

KubeVela=用CUE描述应用模型(规则 + 模板)然后把用户的意图输入推导成一组合法的 Kubernetes资源。

工作流程如下

image

2.应用交付计划

KubeVela的核心是将应用部署所需的所有组件和各项运维动作,描述为一个统一的、与基础设施无关的“部署计划”,进而实现在混合环境中标准化和高效率的应用交付。这使得最终用户无需关注底层细节,就可以使用丰富的扩展能力,并基于统一的概念自助式操作。

每1个应用部署计划都由四个部分组成,分别是组件、运维能力、部署策略和工作流。其格式如下:

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: <name>
spec:
  components:
    - name: <component name>
      type: <component type>
      properties:
        <parameter values>
      traits:
        - type: <trait type>
          properties:
            <traits parameter values>
    - name: <component name>
      type: <component type>
      properties:
        <parameter values>
  policies:
  - name: <policy name>
    type: <policy type>
    properties:
      <policy parameter values>
  workflow:
    - name: <step name>
      type: <step type>
      properties:
        <step parameter values>   

3.Day2Operations概念

Day2Operations是IT运维领域中的1个术语,指的是系统、应用程序或服务在部署和上线后所涉及的持续运维操作。

  • Day 0(规划和设计阶段)
  • Day 1(部署和交付阶段)
  • Day 2 关注系统运行时的优化、维护和管理,以确保系统的稳定性、可靠性和高效性。

运维特征和部署策略的区别:部署策略关注是Day 1,运维特征关注的是Day 2;

1.Day 2 Operations 的核心任务

Day 2 Operations 的任务涵盖了广泛的运维工作,其核心目标是确保系统的正常运行和不断优化。以下是典型的 Day 2 工作内容:

1.1 监控和可观测性

  • 系统监控:监控 CPU、内存、磁盘、网络等资源使用情况。
  • 应用性能监控 (APM):跟踪应用的关键性能指标(KPIs),如响应时间、吞吐量和错误率。
  • 日志管理:收集和分析日志以诊断问题。
  • 分布式追踪:通过工具如 OpenTelemetry,追踪微服务调用链。

1.2 伸缩性与高可用

  • 弹性伸缩:根据负载自动扩展或缩减服务实例的数量。
  • 容灾与备份:设计和实施备份策略,确保数据和服务在灾难发生时可以快速恢复。
  • 高可用性管理:确保系统具备冗余设计,避免单点故障。

1.3 持续更新

  • 补丁和升级:对操作系统、应用程序和依赖库进行版本升级和安全补丁应用。
  • 滚动更新和蓝绿部署:通过渐进式发布减少对生产环境的影响。

1.4 安全管理

  • 身份和访问管理 (IAM):限制和管理系统用户和服务的权限。
  • 安全事件响应:检测和应对安全漏洞或攻击。
  • 合规性监控:确保系统符合行业和法规要求。

1.5 优化和成本管理

  • 资源优化:分析系统资源使用情况,优化服务性能和成本。
  • 成本控制:通过对云资源的使用监控,减少不必要的支出。

1.6 事件响应和问题管理

  • 告警处理:设置自动化告警机制,快速发现问题。
  • 根因分析 (RCA):针对问题事件进行深度分析,找到根本原因并修复。
  • 灾难恢复演练:定期测试容灾机制。

2.Day 2 Operations 的重要性

在系统上线后,Day 2 Operations 决定了服务能否持续满足用户需求、稳定运行并快速响应变化。以下是其关键价值:

  1. 提升用户体验:通过持续优化和监控,确保应用程序以最佳性能运行。
  2. 降低故障风险:通过高可用性设计和快速事件响应机制减少宕机和故障。
  3. 确保安全性:及时应用安全补丁并检测漏洞,保护数据和服务。
  4. 控制成本:优化资源利用率,避免不必要的云资源浪费。

3.工具和平台支持 Day 2 Operations

以下是一些常用工具和平台,可以帮助实施 Day 2 Operations:

3.1 监控和可观测性

  • Prometheus + Grafana:用于系统监控和可视化。
  • Elastic Stack (ELK):日志管理和分析。
  • Datadog / New Relic:全面的 APM 和基础设施监控。
  • Jaeger / OpenTelemetry:分布式追踪。

3.2 运维自动化

  • Terraform / Ansible:用于基础设施即代码(IaC)管理和自动化配置。
  • Kubernetes:通过 HPA(Horizontal Pod Autoscaler)实现应用自动伸缩。

3.3 持续交付与部署

  • ArgoCD / Flux:Kubernetes 原生的 GitOps 工具。
  • Jenkins / Tekton:持续集成与持续部署(CI/CD)工具。

3.4 容灾和备份

  • Velero:Kubernetes 的备份和恢复工具。
  • AWS Backup / Azure Backup:云平台的备份解决方案。

3.5 安全管理

  • HashiCorp Vault:用于密钥管理。
  • Falco:Kubernetes 容器安全监控。
  • Trivy:容器镜像漏洞扫描。

4.多集群应用交付

在越来越多的场景下,开发者和系统运维人员开始将应用部署在多个集群中:

  • 由于 Kubernetes 集群存在着部署规模的局限性(单一集群最多容纳 5k 节点),需要应用多集群技术来部署、管理海量的应用。
  • 考虑到稳定性及高可用性,同一个应用可以部署在多个集群中,以实现容灾、异地多活等需求。
  • 应用可能需要部署在不同的区域来满足不同政府对于数据安全性的政策需求。

4.1.架构

KubeVela的多集群依赖于内置的Cluster-Gateway组件,在KubeVela的HelmChart中自动安装。 

默认情况下,KubeVela管理多集群的方式是通过kubeconfig直连集群,你也可以使用 Open Cluster Management 组件来使用拉取(PULL)模式。

image

4.2.KubeVela多集群部署最佳实践

No Addtional Control Plane Overhead

KubeVela多集群管理的核心:管控集安装Kubevela控制器能解析OAM资源+下发Kubernetes原生yaml到被管控集群,被管控集群只懂原生Kubernetes,避免了在每1个Kubernetes集群都安装KubeVela的麻烦。

管控集群:

Management管控集群作为平台控制面(Control Plane),仅用于OAM应用编排与多集群调度,不作为业务运行环境。

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: my-app
spec:
  components:
  - name: backend
    type: webservice
    properties:
      image: nginx
  policies:
  - name: deploy-to-env
    type: topology
    properties:
      clusters:
        - dev
        - prod

 

管控集群组件

kubectl get pods -n vela-system
NAME                                        READY   STATUS    RESTARTS      AGE
kubevela-cluster-gateway-6d4dbb79f7-gqn4w   1/1     Running   2 (78m ago)   3d22h
kubevela-vela-core-5f8dd6bf96-8nd5q         1/1     Running   2 (78m ago)   3d22h
vela-workflow-84578774f4-n7mkr              1/1     Running   4 (77m ago)   3d22h
velaux-server-749dcbbfc8-lnnsr              1/1     Running   4 (78m ago)   3d22h

KubeVela通过内置的ClusterGateway组件,在管控集群实现了多集群应用交付。

image

受控集群:

受控集群不需要再次安装任何KubeVela组件,减少了额外的控制平面负载,所有工作都只在控制集群中进行。

受控集群只负责接收管控集群下发的Kubernetes原始资源(如 Deployment、Service、ConfigMap、Secret 等),并完成本地的资源落地与运行,是纯粹的执行节点。

 

五、KubeVela可视化管理平台/控制台-VelaUX

虽然KubeVela从根本上解决了以应用为中心视角发布云原生业务应用的问题,将复杂的应用、Trait、Component、Target 等概念统一管理并自动化调度到 Kubernetes 集群。

但是KubeVela本身更多面向的是具备Kubernetes基础认知的运维或平台工程师。

对于研发团队而言,直接操作KubeVela仍然需要理解 Application、Component、Trait 等底层概念,配置和命令行操作容易产生认知成本,增加上手难度,也可能导致操作不一致或错误。

VelaUX全称KubeVela User Experience平台是KubeVela提供的1个非常方便的图形化界面,它大大简化了Kubernetes应用管理和微服务部署,尤其是对于没有深厚YAML编写经验的用户来说,图形化界面可以显著降低KubeVela的使用难度。

它的核心优势就是能直观地管理应用、组件、流水线以及资源,尤其适合需要频繁更新和协调多个微服务的场景。你可以通过拖拽、点击的方式来创建应用、配置服务、定义工作流,而不需要手动编写繁琐的YAML文件,从而极大提升了平台的易用性和生产效率。

  • 多集群 & 多环境支持: 实现跨集群资源的统一管理。
  • 管道管理: 简化 CI/CD 流程,提升开发效率。
  • 可观测性增强: 直观展示关键指标,优化运维体验。

1.安装VelaUX

  kubectl create configmap workflow-addon-velaux-context -n vela-system
  kubectl create configmap workflow-addon-velaux-context -n vela-system

  vela addon disable velaux

  kubectl get event -n vela-system --sort-by=.lastTimestamp

开通访问权限

kubectl port-forward svc/velaux -n vela-system 8080:80

在浏览器中访问 http://localhost:8080,就可以看到 VelaUX 的图形化界面。

kubectl.exe get cm usr-admin -n kubevela -o yaml

六、CI系统对接KubeVela

在大型CI/CD系统架构中CI和CD通常是解耦分离的,例如:

  • CI流水线负责代码集成构建输出构建制品并上传到制品库(CI)
  • KubeVela负责应用交付和管理,KubeVela在应用配置中,直接引用构建制品,并指定环境变量、部署策略等参数,将应用多环境发布(如开发环境、测试环境、生产环境)。(CD)

CI系统(云效/ArgoWorkflows/JenKins/GitLabCI) 构建出制品上传到制品库之后, 调用kubevela的WebHook Triger,触发1个应用部署流程,实现CI/CD对接;

1.在VelaUX创建应用

配置好应用相关的运维特征,镜像地址先随便填CI之后会覆盖;

image

 

应用创建完成后查看应用的绑定的相关属性

2.获取TrigerWebhook地址

VelaUX内置了1个默认触发器,通常用来直接部署应用,而不需要额外绑定自定义工作流,这个默认触发器适用于应用场景简单、直接触发部署的需求。

如果新建触发器需要与工作流绑定,因为触发器的核心作用就是用来启动工作流或者执行某些操作。

如果没有绑定工作流或操作,触发器实际上是没有实际用途的。

从应用的Properties中获取当前应用的TrigerWebhook地址

3.CI系统调用TrigerWebhook

Workflow:CI系统(ArgoWorkflows)构建出Docker镜像制品,调用KubevelaUX的TrigerWebHook触发KubevelaUX中的Workflow执行;

app-instances:KubevelaUX中的Workflow执行完成后,在2个K8s集群中创建了2个应用实例,CD部署完成;

Service EndPoint:应用部署成功后就可以通过应用实例的Service Endpoint访问部署好的2个应用了;

 

七、client-go管理OAM应用

部署K8s资源的方式有

  • 人工通过kubectl的cli命令部署(手工部署)
  • 程序自动调用K8s的SDK/API(自动化部署)
  • 代码仓库管理K8s资源通过ArgoCD间接部署(GitOps部署)

通过client-go SDK发布OAM应用,需要K8s集群安装kubeVela控制器,并在本地配置K8s集群的kuebconfig,生产环境建议GitOps方式部署。

1.发布策略

Kruise Rollout是是面向云原生应用的发布策略工具,基于Kubernetes的一个标准扩展组件,可以配合原生工作负载(Deployment、StatefulSet)以及OpenKruise工作负载(CloneSet、Adcanced StatefulSet),实现金丝雀发布、A/B Testing发布和分批发布等功能。

apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: my-app
  namespace: oam-test
spec:
  components:
    - name: my-app
      type: webservice
      properties:
        image: nginx:1.25
        replicas: 3
      traits:
        - type: kruise-rollout
          properties:
            auto: true                # 开启自动流式发布
            releaseMode: canary       # 使用金丝雀发布
            canary:
              steps:
                - weight: 30           # 30% 流量,1 个副本
                  replicas: 1
                  duration: 60         # 等待 60 秒
                - weight: 50           # 50% 流量,2 个副本
                  replicas: 2
                  duration: 120        # 等待 120 秒
                - weight: 100          # 100% 流量,3 个副本
                  replicas: 3
                  duration: 0          # 不需要等待,立即完成

#第1步:设置了 30% 的流量,并启动 1 个副本,模拟少量用户流量的引导。
#第2步:设置了 50% 流量,并启动 2 个副本,逐步加大流量。
#第3步:100% 流量时,启动所有副本,完全切换到新版本。
View Code

2.代码

在生产部署前支持dry-run和patch更新功能。

在OAM应用创建、patch更新、删除后利用client-go的watch功能监听资源变化。

KubeVela会把最近N(默认 5)个Application版本持久化保存在etcd里,只要知道revision名称(如demo-app-v1),就可以100%精准把它完整取出来,对OAM应用资源进行全量或者增量回滚。

package service

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "testing"
    "time"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/rest"
    "k8s.io/client-go/tools/clientcmd"
    "sigs.k8s.io/yaml"
)

var (
    nameSpace = "oam-test"
    appName   = "demo-app"
    appGVR    = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "applications",
    }
    appRevGVR = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "applicationrevisions",
    }
    componentGVR = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "componentdefinitions",
    }
    traitGVR = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "traitdefinitions",
    }
    workflowGVR = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "workflowstepdefinitions",
    }
    policyGVR = schema.GroupVersionResource{
        Group:    "core.oam.dev",
        Version:  "v1beta1",
        Resource: "policydefinitions",
    }
)

// 创建client-go客户端:自动适配 InCluster / 本地 kubeconfig
func NewDynamicClient() (dynamic.Interface, error) {
    var (
        cfg *rest.Config
        err error
    )

    // 1.优先 InCluster
    cfg, err = rest.InClusterConfig()
    if err != nil {
        // 2.fallback 到 ~/.kube/config
        cfg, err = clientcmd.BuildConfigFromFlags(
            "",
            clientcmd.RecommendedHomeFile,
        )
        if err != nil {
            return nil, fmt.Errorf("load kubeconfig failed: %w", err)
        }
    }

    return dynamic.NewForConfig(cfg)
}

// 列出所有 namespace下的Application
func Test_ListOAMApplications(t *testing.T) {
    dynClient, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }
    // 列出所有namespace
    list, err := dynClient.
        Resource(appGVR).
        Namespace("").
        List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        t.Fatalf("list applications failed: %v", err)
    }
    // 打印结果
    for _, item := range list.Items {
        fmt.Printf(
            "namespace=%s name=%s\n",
            item.GetNamespace(),
            item.GetName(),
        )
    }

}

// 列出OAM应用的版本信息
func Test_Apprevisions(t *testing.T) {
    dynClient, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }
    revList, err := dynClient.Resource(appRevGVR).Namespace(nameSpace).List(context.TODO(), metav1.ListOptions{
        LabelSelector: "app.oam.dev/name=demo-app",
    })
    if err != nil {
        log.Fatalf("failed to list revisions: %v", err)
    }
    for _, rev := range revList.Items {
        name := rev.GetName()
        hash := rev.GetLabels()["app.oam.dev/app-revision-hash"]
        beginTime := rev.GetCreationTimestamp().Time.Format("2006-01-02 15:04:05")
        status, _, _ := unstructured.NestedMap(rev.Object, "status")
        revision, _, _ := unstructured.NestedInt64(status, "revision")
        succeeded, _, _ := unstructured.NestedBool(status, "succeeded")
        yamlBytes, err := yaml.Marshal(rev.Object)
        if err != nil {
            fmt.Println(err)
        }
        sizeBytes := len(yamlBytes)

        fmt.Printf("NAME=%s REVISION=%d HASH=%s SUCCEEDED=%v BEGIN_TIME=%s SIZE=%d bytes\n", name, revision, hash, succeeded, beginTime, sizeBytes)
    }
}

// 回滚OAM应用到指定版本
func TestRollbackOAMApplication(t *testing.T) {
    ctx := context.TODO()
    // 创建动态客户端
    dynClient, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }
    historicalRev := "demo-app-v1"
    // 1.KubeVela会把最近N(默认 5)个Application版本持久化保存在etcd里
    // 2.你只要知道revision名称(如demo-app-v1),就可以100%精准把它完整取出来
    revObj, err := dynClient.Resource(appRevGVR).Namespace(nameSpace).Get(ctx, historicalRev, metav1.GetOptions{})
    if err != nil {
        t.Fatalf("get ApplicationRevision failed: %v", err)
    }

    // 提取历史spec
    appSpec, found, err := unstructured.NestedMap(revObj.Object, "spec", "application", "spec")
    if err != nil || !found {
        t.Fatalf("application spec not found in revision")
    }

    // Patch Application.spec
    patch := map[string]interface{}{
        "spec": appSpec,
    }

    patchBytes, err := json.Marshal(patch)
    if err != nil {
        t.Fatalf("marshal patch failed: %v", err)
    }

    updatedApp, err := dynClient.Resource(appGVR).Namespace(nameSpace).Patch(ctx, appName, types.MergePatchType, patchBytes, metav1.PatchOptions{})
    if err != nil {
        t.Fatalf("rollback patch failed: %v", err)
    }
    t.Logf(
        "Rollback done, generation=%d, resourceVersion=%s",
        updatedApp.GetGeneration(),
        updatedApp.GetResourceVersion(),
    )
}

// 列出所有组件类型
func Test_ListComponentDefinitions(t *testing.T) {
    dynClient, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }

    list, err := dynClient.Resource(componentGVR).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        t.Fatalf("list componentdefinitions failed: %v", err)
    }

    for _, item := range list.Items {
        fmt.Printf(
            "type=%s description=%s\n",
            item.GetName(),
            getNestedString(item.Object, "spec", "description"),
        )
    }
}

// 列出所有运维特征
func Test_ListAllTaritDefinitions(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }

    // TaritDefinition 是 cluster-scoped,无 namespace
    list, err := client.Resource(traitGVR).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        t.Fatalf("list traitDefinitions failed: %v", err)
    }

    t.Logf("Found %d traitDefinitions", len(list.Items))
    for _, item := range list.Items {
        t.Logf("Name=%s Description=%s", item.GetName(), getNestedString(item.Object, "spec", "description"))
    }
}

// 列出所有工作流
func Test_ListAllWorfkflowsDefinitions(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }

    // WorfkflowDefinition 是 cluster-scoped,无 namespace
    list, err := client.Resource(workflowGVR).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        t.Fatalf("list workflowdefinitions failed: %v", err)
    }

    t.Logf("Found %d WorkflowDefinitions", len(list.Items))
    for _, item := range list.Items {
        t.Logf("Name=%s Description=%s", item.GetName(), getNestedString(item.Object, "spec", "description"))
    }
}

// 列出所有策略
func Test_ListAllPolicyDefinitions(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatal(err)
    }

    list, err := client.Resource(policyGVR).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        t.Fatalf("list policydefinitions failed: %v", err)
    }

    t.Logf("Found %d PolicyDefinitions", len(list.Items))
    for _, item := range list.Items {
        t.Logf("Name=%s Description=%s", item.GetName(), getNestedString(item.Object, "spec", "description"))
    }
}

// 创建OAM应用
func TestCreateOAMApplication(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatalf("failed to create dynamic client: %v", err)
    }
    //构建app
    app := &unstructured.Unstructured{
        Object: map[string]interface{}{
            "apiVersion": "core.oam.dev/v1beta1",
            "kind":       "Application",
            "metadata": map[string]interface{}{
                "name":      appName,
                "namespace": nameSpace,
            },
            "spec": map[string]interface{}{
                "components": []interface{}{
                    map[string]interface{}{
                        "name": "my-web",
                        "type": "webservice", // 必须是你集群里已有的 ComponentDefinition
                        "properties": map[string]interface{}{
                            "image": "nginx:latest",
                            "port":  80,
                        },
                    },
                },
            },
        },
    }

    // 执行创建
    created, err := client.Resource(appGVR).Namespace(nameSpace).Create(context.TODO(), app, metav1.CreateOptions{})
    if err != nil {
        t.Fatalf("failed to create OAM application: %v", err)
    }

    // 打印结果
    t.Logf("Application created: namespace=%s name=%s", created.GetNamespace(), created.GetName())

    // 1.List一次,检查是否已经Ready
    appObj, err := client.Resource(appGVR).Namespace(nameSpace).Get(context.TODO(), appName, metav1.GetOptions{})
    fmt.Printf("appObj=%#v\n", appObj)
    if err == nil {
        status, _, _ := unstructured.NestedMap(appObj.Object, "status")
        phase, _, _ := unstructured.NestedString(status, "phase")
        health, _, _ := unstructured.NestedString(status, "health", "status")
        if phase == "running" && health == "healthy" {
            t.Log("Application already ready")
            return
        }
    }

    // 2.Watch直到OAM应用Ready
    watcher, err := client.Resource(appGVR).Namespace(nameSpace).Watch(context.TODO(), metav1.ListOptions{
        FieldSelector: fmt.Sprintf("metadata.name=%s", appName),
    })
    if err != nil {
        t.Fatalf("failed to start watch: %v", err)
    }
    defer watcher.Stop()
    timeout := time.After(5 * time.Minute)
    for {
        select {
        case event := <-watcher.ResultChan():
            obj := event.Object.(*unstructured.Unstructured)
            statusStr, _, _ := unstructured.NestedString(obj.Object, "status", "status")
            services, _, _ := unstructured.NestedSlice(obj.Object, "status", "services")
            healthy := true
            for _, s := range services {
                svc := s.(map[string]interface{})
                if h, ok := svc["healthy"].(bool); ok && !h {
                    healthy = false
                }
            }
            t.Logf("Application status=%s, healthy=%v", statusStr, healthy)
            // Ready 条件
            if (statusStr == "running" || statusStr == "succeeded") && healthy {
                t.Log("Application is ready")
                return
            }

        case <-timeout:
            t.Fatalf("timeout waiting for Application to be ready")
        }
    }
}

// 创建OAM应用Dry-Run
func TestCreateOAMApplicationDryRun(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatalf("failed to create dynamic client: %v", err)
    }

    app := &unstructured.Unstructured{
        Object: map[string]interface{}{
            "apiVersion": "core.oam.dev/v1beta1",
            "kind":       "Application",
            "metadata": map[string]interface{}{
                "name":      appName,
                "namespace": nameSpace,
            },
            "spec": map[string]interface{}{
                "components": []interface{}{
                    map[string]interface{}{
                        "name": "my-web",
                        "type": "webservice", // 必须是你集群里已有的 ComponentDefinition
                        "properties": map[string]interface{}{
                            "image": "nginx:latest",
                            "port":  80,
                        },
                    },
                },
            },
        },
    }

    // 执行Dry-Run创建
    created, err := client.Resource(appGVR).Namespace(nameSpace).Create(context.TODO(), app, metav1.CreateOptions{DryRun: []string{"All"}})
    if err != nil {
        t.Fatalf("failed to dry-run create OAM application: %v", err)
    }
    // 打印 Dry-Run 结果
    t.Logf("Dry-Run Application created (not persisted): namespace=%s name=%s", created.GetNamespace(), created.GetName())
}

// patch更新OAM应用部分字段
func TestUpdateOAMApplication(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatalf("failed to create dynamic client: %v", err)
    }

    // 获取现有的Application
    app, err := client.Resource(appGVR).Namespace(nameSpace).Get(context.TODO(), appName, metav1.GetOptions{})
    if err != nil {
        t.Fatalf("failed to get OAM application: %v", err)
    }

    // 执行更新metadata.annotations存描述
    annotations := app.GetAnnotations()
    if annotations == nil {
        annotations = map[string]string{}
    }
    annotations["app.oam.dev/description"] = "====================Updated by GoClient============"
    patchData := map[string]interface{}{
        "metadata": map[string]interface{}{
            "annotations": annotations,
        },
    }
    patchBytes, err := json.Marshal(patchData)
    if err != nil {
        t.Fatalf("failed to marshal patch data: %v", err)
    }

    // 执行patch
    _, err = client.Resource(appGVR).Namespace(nameSpace).Patch(
        context.TODO(),
        appName,
        types.MergePatchType,
        patchBytes,
        metav1.PatchOptions{},
    )
    if err != nil {
        t.Fatalf("failed to update OAM application: %v", err)
    }

    t.Logf("Application updated: namespace=%s name=%s", nameSpace, appName)
}

// 删除OAM应用
func TestDeleteOAMApplication(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatalf("failed to create dynamic client: %v", err)
    }
    // 执行删除
    err = client.Resource(appGVR).Namespace(nameSpace).Delete(context.TODO(), appName, metav1.DeleteOptions{})
    if err != nil {
        t.Fatalf("failed to delete OAM application: %v", err)
    }

    t.Logf("Application deleted: namespace=%s name=%s", nameSpace, appName)
}

// Dry-Run删除OAM应用
func TestDeleteOAMApplicationDryRun(t *testing.T) {
    client, err := NewDynamicClient()
    if err != nil {
        t.Fatalf("failed to create dynamic client: %v", err)
    }
    // 执行Dry-Run删除
    err = client.Resource(appGVR).Namespace(nameSpace).Delete(context.TODO(), appName, metav1.DeleteOptions{DryRun: []string{"All"}})
    if err != nil {
        t.Fatalf("failed to dry-run delete OAM application: %v", err)
    }
    t.Logf("Dry-Run delete succeeded: namespace=%s name=%s (not actually deleted)", nameSpace, appName)
}

// 安全读取嵌套字段(避免 panic)
func getNestedString(obj map[string]interface{}, fields ...string) string {
    var cur interface{} = obj
    for _, f := range fields {
        m, ok := cur.(map[string]interface{})
        if !ok {
            return ""
        }
        cur, ok = m[f]
        if !ok {
            return ""
        }
    }

    if s, ok := cur.(string); ok {
        return s
    }
    return ""
}
View Code

 

 

 

 

 

 

 

参考

posted on 2024-09-23 21:18  运维体系建设之路  阅读(349)  评论(0)    收藏  举报