简介: KubeVela 是一个简单易用又高度可扩展的云原生应用管理引擎,是基于 Kubernetes 及阿里云与微软云共同发布的云原生应用开发模型 OAM 构建。本文主要目的是探索 KubeVela 如何将一个 appfile 文件转换为 K8s 中特定的资源对象。

作者 | 樊大勇

KubeVela 是一个简单易用又高度可扩展的云原生应用管理引擎,是基于 Kubernetes 及阿里云与微软云共同发布的云原生应用开发模型 OAM 构建。

KubeVela 基于 OAM 模型构建了一套具体的实现,通过 Golang 编写,可以端到端地为用户构建云原生应用的平台,提供一个相对完整的解决方案。

KubeVela 项目自 2020 年 7 月份在社区里面发起,受到包括阿里、微软、Crossplane 等公司工程师在内的广大社区志愿者的欢迎,并一起投入到项目开发工作中。他们把在 OAM 实践里面的各种经验与教训,都总结沉淀到 KubeVela 项目中。

本文主要目的是探索 KubeVela 如何将一个 appfile 文件转换为 K8s 中特定的资源对象。

该过程总的来说分为两个阶段:

  1. appfile 转为 K8s 中的 application
  2. application 转换为对应的 K8s 资源对象
# vela.yaml
name: test
services:
nginx:
type: webservice
image: nginx
env:
- name: NAME
value: kubevela # svc trait
svc:
type: NodePort
ports:
- port: 80
nodePort: 32017

利用 vela up 命令可以完成部署。

vela up 命令

建议:在看 vela 命令行工具代码之前,先去简单了解一下 cobra 框架。

// references/cli/up.go
// NewUpCommand will create command for applying an AppFile
func NewUpCommand(c types.Args, ioStream cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "up",
DisableFlagsInUseLine: true,
Short: "Apply an appfile",
Long: "Apply an appfile",
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return c.SetConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
velaEnv, err := GetEnv(cmd)
if err != nil {
return err
}
kubecli, err := c.GetClient()
if err != nil {
return err
} o := &common.AppfileOptions{
Kubecli: kubecli,
IO: ioStream,
Env: velaEnv,
}
filePath, err := cmd.Flags().GetString(appFilePath)
if err != nil {
return err
}
return o.Run(filePath, velaEnv.Namespace, c)
},
}
cmd.SetOut(ioStream.Out) cmd.Flags().StringP(appFilePath, "f", "", "specify file path for appfile")
return cmd
}

上面源码展示的是 vela up 命令的入口。

在 PresistentPreRunE 函数中,通过调用 c.SetConfig() 完成 Kuberentes 配置信息 kubeconfig 的注入。

在 RunE 函数中:

  • 首先,获取 vela 的 env 变量,velaEnv.Namespace 对应 Kubernetes 的命名空间。
  • 其次,获取 Kubernetes 的客户端,kubectl。
  • 接着,利用 Kubernetes 客户端和 vleaEnv 来构建渲染 Appfile 需要的 AppfileOptions。
  • 最后,调用 o.Run(filePath, velaEnv.Namespace, c)。

    • 该函数需要三个参数,其中 filePath 用于指定 appfile 的位置,velaEnv.Namespace 和 c 用来将渲染后的 Application 创建到指定命名空间。

      • filePath: appfile 的路径
      • velaEnv.Namespace:对应 K8s 的 namespace
      • c:K8s 客户端

如何将一个 appfile 转为 Kubernetes 中的 Application

  • 起点:appfile
  • 终点:applicatioin
  • 路径:appfile -> application (services -> component)

    • comp[workload, traits]

1. 起点:AppFile

// references/appfile/api/appfile.go
// AppFile defines the spec of KubeVela Appfile
type AppFile struct {
Name string `json:"name"`
CreateTime time.Time `json:"createTime,omitempty"`
UpdateTime time.Time `json:"updateTime,omitempty"`
Services map[string]Service `json:"services"`
Secrets map[string]string `json:"secrets,omitempty"` configGetter config.Store
initialized bool
} // NewAppFile init an empty AppFile struct
func NewAppFile() *AppFile {
return &AppFile{
Services: make(map[string]Service),
Secrets: make(map[string]string),
configGetter: &config.Local{},
}
}
// references/appfile/api/service.go
// Service defines the service spec for AppFile, it will contain all related information including OAM component, traits, source to image, etc...
type Service map[string]interface{}

上面两段代码是 AppFile 在客户端的声明,vela 会将指定路径的 yaml 文件读取后,赋值给一个 AppFile。

// references/appfile/api/appfile.go
// LoadFromFile will read the file and load the AppFile struct
func LoadFromFile(filename string) (*AppFile, error) {
b, err := ioutil.ReadFile(filepath.Clean(filename))
if err != nil {
return nil, err
}
af := NewAppFile()
// Add JSON format appfile support
ext := filepath.Ext(filename)
switch ext {
case ".yaml", ".yml":
err = yaml.Unmarshal(b, af)
case ".json":
af, err = JSONToYaml(b, af)
default:
if json.Valid(b) {
af, err = JSONToYaml(b, af)
} else {
err = yaml.Unmarshal(b, af)
}
}
if err != nil {
return nil, err
}
return af, nil
}

下面为读取 vela.yaml 文件后,加载到 AppFile 中的数据:

# vela.yaml
name: test
services:
nginx:
type: webservice
image: nginx
env:
- name: NAME
value: kubevela # svc trait
svc:
type: NodePort
ports:
- port: 80
nodePort: 32017
Name: test
CreateTime: 0001-01-01 00:00:00 +0000 UTC
UpdateTime: 0001-01-01 00:00:00 +0000 UTC
Services: map[
nginx: map[
env: [map[name: NAME value: kubevela]]
image: nginx
svc: map[ports: [map[nodePort: 32017 port: 80]] type: NodePort]
type: webservice
]
]
Secrets map[]
configGetter: 0x447abd0
initialized: false

2. 终点:application

// apis/core.oam.dev/application_types.go
type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"` Spec ApplicationSpec `json:"spec,omitempty"`
Status AppStatus `json:"status,omitempty"`
} // ApplicationSpec is the spec of Application
type ApplicationSpec struct {
Components []ApplicationComponent `json:"components"` // TODO(wonderflow): we should have application level scopes supported here // RolloutPlan is the details on how to rollout the resources
// The controller simply replace the old resources with the new one if there is no rollout plan involved
// +optional
RolloutPlan *v1alpha1.RolloutPlan `json:"rolloutPlan,omitempty"`
}

上面代码,为 Application 的声明,结合 .vela/deploy.yaml(见下面代码),可以看出,要将一个 AppFile 渲染为 Application 主要就是将 AppFile 的 Services 转化为 Application 的 Components。

# .vela/deploy.yaml
apiVersion: core.oam.dev/v1alpha2
kind: Application
metadata:
creationTimestamp: null
name: test
namespace: default
spec:
components:
- name: nginx
scopes:
healthscopes.core.oam.dev: test-default-health
settings:
env:
- name: NAME
value: kubevela
image: nginx
traits:
- name: svc
properties:
ports:
- nodePort: 32017
port: 80
type: NodePort
type: webservice
status: {}

3. 路径:Services -> Components

结合以上内容可以看出,将 Appfile 转化为 Application 主要是将 Services 渲染为 Components。

// references/appfile/api/appfile.go
// BuildOAMApplication renders Appfile into Application, Scopes and other K8s Resources.
func (app *AppFile) BuildOAMApplication(env *types.EnvMeta, io cmdutil.IOStreams, tm template.Manager, silence bool) (*v1alpha2.Application, []oam.Object, error) {
...
servApp := new(v1alpha2.Application)
servApp.SetNamespace(env.Namespace)
servApp.SetName(app.Name)
servApp.Spec.Components = []v1alpha2.ApplicationComponent{}
for serviceName, svc := range app.GetServices() {
...
// 完成 Service 到 Component 的转化
comp, err := svc.RenderServiceToApplicationComponent(tm, serviceName)
if err != nil {
return nil, nil, err
}
servApp.Spec.Components = append(servApp.Spec.Components, comp)
}
servApp.SetGroupVersionKind(v1alpha2.SchemeGroupVersion.WithKind("Application"))
auxiliaryObjects = append(auxiliaryObjects, addDefaultHealthScopeToApplication(servApp))
return servApp, auxiliaryObjects, nil
}

上面的代码是 vela 将 Appfile 转化为 Application 代码实现的位置。其中 comp, err := svc.RenderServiceToApplicationComponent(tm, serviceName) 完成 Service 到 Component 的转化。

// references/appfile/api/service.go
// RenderServiceToApplicationComponent render all capabilities of a service to CUE values to KubeVela Application.
func (s Service) RenderServiceToApplicationComponent(tm template.Manager, serviceName string) (v1alpha2.ApplicationComponent, error) { // sort out configs by workload/trait
workloadKeys := map[string]interface{}{}
var traits []v1alpha2.ApplicationTrait wtype := s.GetType()
comp := v1alpha2.ApplicationComponent{
Name: serviceName,
WorkloadType: wtype,
} for k, v := range s.GetApplicationConfig() {
// 判断是否为 trait
if tm.IsTrait(k) {
trait := v1alpha2.ApplicationTrait{
Name: k,
}
....
// 如果是 triat 加入 traits 中
traits = append(traits, trait)
continue
}
workloadKeys[k] = v
} // Handle workloadKeys to settings
settings := &runtime.RawExte nsion{}
pt, err := json.Marshal(workloadKeys)
if err != nil {
return comp, err
}
if err := settings.UnmarshalJSON(pt); err != nil {
return comp, err
}
comp.Settings = *settings if len(traits) > 0 {
comp.Traits = traits
} return comp, nil
}

4. 总结

执行 vela up 命令,渲染 appfile 为 Application,将数据写入到 .vela/deploy.yaml 中,并在 K8s 中创建。

Application 是如何转换为对应 K8s 资源对象

  • 起点:Application
  • 中点:ApplicationConfiguration, Component
  • 终点:Deployment, Service
  • 路径:

    • application_controller
    • applicationconfiguration controller

【建议】> 了解一下内容:> - client-to

  • controller-runtime
  • operator

1. Application

# 获取集群中的 Application
$ kubectl get application
NAMESPACE NAME AGE
default test 24h

2. ApplicationConfiguration 和 Component

当 application controller 获取到 Application 资源对象之后,会根据其内容创建出对应的 ApplicationConfiguration 和 Component。

# 获取 ApplicationConfiguration 和 Component
$ kubectl get ApplicationConfiguration,Component
NAME AGE
applicationconfiguration.core.oam.dev/test 24h NAME WORKLOAD-KIND AGE
component.core.oam.dev/nginx Deployment 24h

ApplicationiConfiguration 中以名字的方式引入 Component:

3. application controller

基本逻辑:
  • 获取一个 Application 资源对象。
  • 将 Application 资源对象渲染为 ApplicationConfiguration 和 Component。
  • 创建 ApplicationConfiguration 和 Component 资源对象。
代码:
// pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go

// Reconcile process app event
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
applog := r.Log.WithValues("application", req.NamespacedName) // 1. 获取 Application
app := new(v1alpha2.Application)
if err := r.Get(ctx, client.ObjectKey{
Name: req.Name,
Namespace: req.Namespace,
}, app); err != nil {
...
} ... // 2. 将 Application 转换为 ApplicationConfiguration 和 Component
handler := &appHandler{r, app, applog}
...
appParser := appfile.NewApplicationParser(r.Client, r.dm)
...
appfile, err := appParser.GenerateAppFile(ctx, app.Name, app)
...
ac, comps, err := appParser.GenerateApplicationConfiguration(appfile, app.Namespace)
... // 3. 在集群中创建 ApplicationConfiguration 和 Component
// apply appConfig & component to the cluster
if err := handler.apply(ctx, ac, comps); err != nil {
applog.Error(err, "[Handle apply]")
app.Status.SetConditions(errorCondition("Applied", err))
return handler.handleErr(err)
} ...
return ctrl.Result{}, r.UpdateStatus(ctx, app)
}

4. applicationconfiguration controller

基本逻辑:
  • 获取 ApplicationConfiguration 资源对象。
  • 循环遍历,获取每一个 Component 并将 workload 和 trait 渲染为对应的 K8s 资源对象。
  • 创建对应的 K8s 资源对象。
代码:
// pkg/controller/core.oam.dev/v1alpha2/applicationcinfiguratioin/applicationconfiguratioin.go

// Reconcile an OAM ApplicationConfigurations by rendering and instantiating its
// Components and Traits.
func (r *OAMApplicationReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
...
ac := &v1alpha2.ApplicationConfiguration{}
// 1. 获取 ApplicationConfiguration
if err := r.client.Get(ctx, req.NamespacedName, ac); err != nil {
...
}
return r.ACReconcile(ctx, ac, log)
} // ACReconcile contains all the reconcile logic of an AC, it can be used by other controller
func (r *OAMApplicationReconciler) ACReconcile(ctx context.Context, ac *v1alpha2.ApplicationConfiguration,
log logging.Logger) (result reconcile.Result, returnErr error) { ...
// 2. 渲染
// 此处 workloads 包含所有Component对应的的 workload 和 tratis 的 k8s 资源对象
workloads, depStatus, err := r.components.Render(ctx, ac)
... applyOpts := []apply.ApplyOption{apply.MustBeControllableBy(ac.GetUID()), applyOnceOnly(ac, r.applyOnceOnlyMode, log)} // 3. 创建 workload 和 traits 对应的 k8s 资源对象
if err := r.workloads.Apply(ctx, ac.Status.Workloads, workloads, applyOpts...); err != nil {
...
} ... // the defer function will do the final status update
return reconcile.Result{RequeueAfter: waitTime}, nil
}

5. 总结

当 vela up 将一个 AppFile 渲染为一个 Application 后,后续的流程由 application controller 和 applicationconfiguration controller 完成。

作者简介

樊大勇,华胜天成研发工程师,GitHub ID:@just-do1。

本文为阿里云原创内容,未经允许不得转载。

源码解读:KubeVela 是如何将 appfile 转换为 K8s 特定资源对象的的更多相关文章

  1. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  2. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  3. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  4. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  5. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  6. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  7. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  8. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  9. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

  10. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

随机推荐

  1. Inno setup 脚本判断 Microsoft Visual C++ Redistributable 不同版本区别

    有个需要是需要在安装包安装初始化时安装 Microsoft Visual c++ 2013 Redistributable 也就是判断软件安装前需不需要运行 vcredist_x64.exe 和 VC ...

  2. 32_音视频播放器_SDL播放

    目录 一.简介 二.音频重采样 2.1 引入头文件 2.2 定义重采样相关属性 2.3初始化重采样 2.4 重采样 三.SDL播放 四.停止功能 五.处理读完音频包的情况 六.实现调节音量 七.实现静 ...

  3. 云端虚拟展示有多酷炫?3DCAT邀您来视博会现场体验!

    3DCAT实时渲染云将于2021年05月10日-12日,参展亚洲视觉智能与沉浸式产业博览会.在广交会4.2号展馆H31d展位,3DCAT将与大家分享云端虚拟展示领域最新的研究成果与创新应用案例,与全球 ...

  4. Java 22正式发布,一文了解全部新特性

    就在昨晚,Java 22正式发布!该版本提供了 12 项功能增强,其中包括 7 项预览功能和 1 项孵化器功能.它们涵盖了对 Java 语言.API.性能以及 JDK 中包含的工具的改进. 下面就来一 ...

  5. 是时候来唠一唠synchronized关键字了,Java多线程的必问考点!

    写在开头 在之前的博文中,我们介绍了volatile关键字,Java中的锁以及锁的分类,今天我们花5分钟时间,一起学习一下另一个关键字:synchronized. synchronized是什么? 首 ...

  6. Informix日志报错:Could not do a physical-order read to fetch netxt row

    jmeter请求接口,1线程不报错,2线程及以上报错"无法执行查询",看后台日志,报错Could not do a physical-order read to fetch net ...

  7. CDC实战:MySQL实时同步数据到Elasticsearch之数组集合(array)如何处理【CDC实战系列十二】

    需求背景: mysql存储的一个字段,需要同步到elasticsearch,并存储为数组,以便于查询. 如下例子,就是查询预期. PUT /t_test_1/_doc/1 { "name&q ...

  8. 表的唯一约束的作用 KingbaseES VS Oracle

    背景 演示唯一约束怎样创建.删除.禁用和使用唯一性约束,已经多种数据库的差异. 什么是唯一约束 唯一性约束指表中一个字段或者多个字段联合起来可以唯一标识一条记录的约束, 字段中,可以包括空值. 唯一性 ...

  9. #线段树,树状数组#CodeChef Merciless Chef

    MLCHEF 分析 首先按照dfs序将子树转换为区间,其实就是区间减和区间维护最小值判断是否大于0 因为大于0一定最多只有 \(n\) 个,所以直接将一个数记录被删除并设为正无穷. 代码 #inclu ...

  10. 【直播回顾】OpenHarmony知识赋能五期第五课——多媒体子系统之视频解读

    5月19日晚上19点,知识赋能第五期第五节课<OpenHarmony标准系统多媒体子系统之视频解读>,在OpenHarmony开发者成长计划社群内成功举行. 本期课程,由深开鸿资深技术专家 ...