如何基于 OAM 编写一个扩展 Trait?
此文中些许部分已重构,更新版本:https://xie.infoq.cn/article/5b512225b13c7bcd525b7c35b
1. 背景
OAM 是阿里云与微软云在 2019 年末联合推出的标准化云原生应用管理模型。相比于传统 PaaS 封闭、不能同“以 Operator 为基础的云原生生态”衔接的现状,基于 OAM 和 Kubernetes 构建的现代云原生应用管理平台,本质上是一个“以应用为中心”的 Kubernetes ,保证了这个应用平台在能够无缝接入整个云原生生态。同时,OAM 可以进一步屏蔽掉容器基础设施的复杂性和差异性,为平台的使用者带来低心智负担的、标准化的、一致的应用管理与交付体验。
来源:阿里云携手微软与 Crossplane 社区发布 OAM Kubernetes 标准实现与核心依赖库
在 OAM 中,一个应用程序包含三个核心理念。
第一个核心理念是组成应用程序的组件(Component),它可能包含微服务集合、数据库和云负载均衡器;
第二个核心理念是描述应用程序运维特征(Trait)的集合,例如,弹性伸缩和 Ingress 等功能。它们对应用程序的运行至关重要,但在不同环境中其实现方式各不相同;
最后,为了将这些描述转化为具体的应用程序,运维人员使用应用配置(Application Configuration)来组合组件和相应的特征,以构建应部署的应用程序的具体】实例。
1.1 Workload
Workload 并不是一个实例,而是定义了应用程序能够使用的 Component 类型:如何运行 Component,以及它的运行内容。
Workload 既可以是根据 OAM 规范定义的类型,如 OAM core workloads:ContainerizedWorkload,参考 addon-oam-kubernetes-local;也可以复用 K8S 原生的资源,如直接使用 StatefulSet,参考 StatefulSet Workload。
1.2 Trait
Trait 所代表的是运维特征,可以将多种 Trait 自由组合并绑定在 Component 上,为应用程序扩展运维能力。
Trait 对 Workload 资源的操作方式主要分为两类,一类是直接操作 Workload 或者其生成的下层资源的字段,如修改资源的 spec.replicas
,参考 addon-oam-kubernetes-local 中的 ManualScalerTrait;另一类是创建一个独立的资源,如为资源创建一个 K8s Service,参考 ServiceTrait。
1.3 Workload 与 Trait 交互
在 OAM 中,Workload 和 Trait 都以 CR(custom resource)的方式独立存在,非常方便扩展,那么 Trait 是如何知道与之绑定的 Workload 的呢?
Application Configuration 是组合组件和相应运维特征的地方,Workload 与 Trait 的交互就在其控制器逻辑中。Application Configuration 中储存了 ComponentName、Workload、Traits 的信息,它会将 Workload 的信息依次添加到各个 Trait 中,通过在 Trait 中指定 spec.workloadRef
字段来绑定。由此 Trait 便知晓了与之绑定的 Workload 信息。
2. 使用 kubebuilder 构建 OAM 扩展 Trait
上文中简单介绍了 OAM 的两种主要资源类型:Workload 和 Trait ,并简单介绍了 Workload 与 Trait 之间的交互逻辑。
众所周知,掌握 CRD 是成为 Kubernetes 高级玩家的必备技能,而编写 OAM 扩展 Trait 的主要方式同样也是编写 CRD controller。所以接下来将介绍如何使用 CRD 编写框架 kubebuilder 来实现自定义 CRD 和 Controller,并重点讲解 Trait 的内部逻辑编写。
首先,你需要安装 kubebuilder,参照网址:https://book.kubebuilder.io/quick-start.html#installation。
2.1 构建项目
创建一个目录,并用 kubebuilder int
命令初始化一个新项目。
2.2 创建API
使用 kubebuilder create api
命令创建一个新的 API,注意指定 GVK(group/version/kind)。
由此两步便已成功构建 CRD 和 Controller 的模板:
2.3 编码
kubebuilder 已经为我们生成了较为完整的框架,我们主要编辑 cronjob_types.go
和 cronjob_controller.go
两个文件,来自定义 CRD 和 Controller 逻辑。
具体 CRD 定义和逻辑编写以及注意点参考下文3。
2.4 安装并运行
编写好逻辑后,使用以下命令安装并运行 CRD 和 Controller:
运行成功后,可编写一个 example 用于测试:
2.5 构建并部署
make run
命令来用于测试,真正将 Controller 部署到集群中需要构建镜像并部署:
3. 编写 Workload 与 Trait
由于 Workload 与 Trait 的 CRD 和 Controller 的编写有相通之处,所以此处以 ServiceTrait 为例,重点介绍如何为 OAM Trait 自定义 CRD,Controller 逻辑以及一些注意事项。
servicetrait_types.go
需在
ServiceTraitStatus
结构体中定义以下两个字段:
runtimev1alpha1.ConditionedStatus
:此字段用于反应资源在集群中的观察状态。runtimev1alpha1.TypedReference
:此字段定义资源的 APIVersion、Kind、Name、UID。
此外,还需要编写 Conditions 相关的方法,否则无法获取或设定资源在集群中的观察状态:
需在
ServiceTraitSpec
结构体中定义:
WorkloadReference
必须设置,是储存需要扩展的 Workload 信息的地方。根据自己的需求自定义字段,示例设置的 Template 为 K8S 原生的 ServiceSpec。
添加 kubebuilder 选项:
同样在 Workload 的 type.go 文件中,需要定义 WorkloadStatus 的两个字段: runtimev1alpha1.ConditionedStatus
和 runtimev1alpha1.TypedReference
;编写 GetCondition
和 SetCondition
方法;添加 kubebuilder 选项;而 WorkloadSpec 只需根据需求自定义字段即可。
servicetrait_controller.go
Trait 的控制逻辑都在 Controller 的 Reconcile
函数中实现即可。
获取 trait 对象
声明 ServiceTrait
变量,通过 req.NamespacedName
获取需要调谐的 trait 对象:
获取 workload 对象
根据获取到的 trait 对象,去获取其引用的 workload 对象:
具体 fetchworkload
函数:
声明
workload
变量。根据 trait 对象的
GetWorkloadReference
方法获取其引用的 workload 对象信息:APIVersion、Kind、Name、UID。用
client.ObjectKey
生成的NamespacedName
去获取集群中的 workload 对象并返回。
获取目标资源对象
首先需要确定 workload 对象的类型,若是自定义的 OAM workload,则其子资源才是我们需要的目标资源对象;若是 K8S CR,则 workload 所代表的资源就是我们需要的目标资源对象。
具体 DetermineWorkloadType
函数:此处示例是根据 APIVersion 来做判断,若是自定义的 OAM workload 则用 util.FetchWorkloadDefinition
去获取 workload 的子资源并返回;若是 K8S CR 则直接将 workload 作为返回值即可。
util 包地址:https://github.com/crossplane/addon-oam-kubernetes-local/tree/master/pkg/oam/util。
执行 trait 逻辑
ServiceTrait 的逻辑是为目标资源对象创建一个 K8S 原生 Service 资源。用户可根据自己的需求,自定义 trait 的逻辑。
而 Workload 的 Controller 逻辑更为简单:
第一步:定义 workload 变量,同样通过
req.NamespacedName
获取需要调谐的 workload 对象。第二步:执行 workload 逻辑。以 containerizedworkload_controller.go 为例,它为 workload 创建了一个 deployment 和一个 service 资源。
注意点
main.go 中增加映射
需在 init
函数中将 OAM core API 添加到 scheme 中,因为 trait 的 Controller 逻辑中需要获取集群中 WorkloadDefinition
对象。
servicetrait_controller.go 中增加 rbac
需添加 kubebuilder 选项,以支持 trait 控制器对资源的操作权限。ServiceTrait 添加了对 containerizedworkloads、workloaddefinitions、statefulsets、deployments、services 的操作权限。
同样在 workload_controller.go 中,也需要注意使用 kubebuilder 选项,添加对需要操作的资源的权限。
4. 部署使用 Traits
我们使用 kubebuilder 生成了框架,并自定义了 CRD 和 Controller 逻辑,由此便得到了一个能为 workload 创建一个 K8S 原生 Service 资源的运维特征:ServiceTrait;根据同样的流程逻辑,我们也能得到一个能为 workload 创建一个 K8S 原生 Ingress 资源的运维特征:IngressTrait。详细可参考oam-dev/catalog/traits。
在 IngressTrait 的例子中,编写了一个 example:Component 中的 workload 直接复用 K8S StatefulSet;ApplicationConfiguration(以下简称 appconfig) YAML 文件中指定了 componentName,并为其绑定 ServiceTrait 和 IngressTrait。
首先,appconfig 的
spec.components
字段是 ApplicationConfigurationComponent 结构体组成的数组,而 example 中定义了一个 ApplicationConfigurationComponent,包含 componentName 和 traits 的信息。由此 appconfig 控制器便可根据 componentName 去获取对应的 Component,从而由 Component 的
spec.workload
字段获取其复用的 K8S StatefulSet。接着,appconfig 会将 componentName,workload,traits 的信息储存在 appconfig 中定义的 Workload 结构体中。
最后在 Workload 结构体的
Apply
函数中,将与 traits 绑定的 workload 信息依次添加到 traits 的spec.workloadRef
字段中,实现 workload 与 traits 的交互,并同时将 workload 和 traits 部署到集群中。
如图,成功部署资源,并成功通过 ingress 访问服务。
5. 总结
本文首先介绍了 OAM Workload 与 Trait 相关知识以及它们之间的交互。而本文的重点在于如何通过 kubebuilder 为 OAM Workload 和 Trait 生成框架,以及如何编写 Workload 和 Trait 的自定义 CRD 和 Controller 逻辑。
希望通过本文能够帮助大家快速理解掌握编写 OAM Workload 和 Trait。
6. 作者简介
钱王骞,浙江大学软件学院研究生,目前在杭州谐云科技有限公司实习,同时正在参与 OAM 社区相关工作。
评论