做者 | 孙健波(天元)阿里云技术专家linux
导读:OAM Spec 经历了近 3 个月的迭代,v1alpha2 版本终于发布啦!新版本在坚持 OAM Spec 平台无关的基础上,总体变得更 Kubernetes 友好化,很大程度上平衡了标准与可扩展性,更好的支持 CRD。若是你已经编写了现成的 CRD Operator,能够平滑的接入到 OAM 体系中,而且享受到 OAM 模型的红利。git
目前 OAM 已经成为了包括阿里、微软、Upbond、谐云等多家公司构建云产品的核心架构。他们经过 OAM 构建了“以应用为中心”、用户友好化的 Kubernetes PaaS;充分发挥 OAM 的标准化与可扩展性,实现 OAM 核心 Controller 的同时,快速接入了已有的 Operator 能力;经过 OAM 横向打通多个模块,破除了原有 Operator 彼此孤立、没法复用的窘境。
github
下面言归正传,让咱们来看一下 v1alpha2 到底作了哪些改动?web
为了方便你们阅读,这里只罗列了最主要的改动点,一些细节仍是以上游 OAM Spec Github 仓库为准。redis
v1alpha1 原先的方式是这样的:docker
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: WorkloadType metadata: name: OpenFaaS annotations: version: v1.0.0 description: "OpenFaaS a Workload which can serve workload running as functions" spec: group: openfaas.com version: v1alpha2 names: kind: Function singular: function plural: functions workloadSettings: | { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": [ "name", "image" ], "properties": { "name": { "type": "string", "description": "the name to the function" }, "image": { "type": "string", "description": "the docker image of the function" } } }
在原先的模式中,group/version/kind 分别是字段,spec 的校验经过 jsonschema 表示,总体的格式实际上相似 CRD,但不彻底一致。json
新版 v1alpha2 中完全改成了引用模型,经过 WorkloadDefinition
TraitDefinition
ScopeDefinition
的形式,描述了一个引用关系。能够直接引用一个 CRD,name 就是 CRD 的名称。对于非 K8s 的 OAM 实现来讲,这里的名字则是一个索引,能够找到相似 CRD 的校验文件,校验文件中包含 apiVersion 和 kind,以及相应的 schema 校验。api
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: containerisedworkload.core.oam.dev spec: definitionRef: # Name of CRD. name: containerisedworkload.core.oam.dev
apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata: name: manualscalertrait.core.oam.dev spec: appliesToWorkloads: - containerizedworkload.core.oam.dev definitionRef: name: manualscalertrait.core.oam.dev
apiVersion: core.oam.dev/v1alpha2 kind: ScopeDefinition metadata: name: networkscope.core.oam.dev spec: allowComponentOverlap: true definitionRef: name: networkscope.core.oam.dev
注意:数组
<plural-kind>.<group>
组成。社区的最佳实践是一个 CRD 只有一个 version 在集群中运行,通常新 version 都会向前兼容,升级时都一次性替换到最新 version。若是确实有 2 个 version 同时存在的场景,用户也能够经过 kubectl get crd <name>
的方式进一步选择;原先的方式在 Workload 和 Trait 层面咱们都只把 CR 的 spec 部分拿出来,分别放在 workloadSettings
和 properties
字段里。架构
这样的方式虽然已经能够“推导”出 K8s CR,可是不利于 K8s 生态内的 CRD 接入,须要换种格式从新定义一遍 spec。
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster workloadSettings: engineVersion: 1.0 region: cn
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: custom-single-app annotations: version: v1.0.0 description: "Customized version of single-app" spec: variables: components: - componentName: frontend instanceName: web-front-end parameterValues: traits: - name: manual-scaler properties: replicaCount: 5
如今的方式则直接嵌入 CR,能够看到,在 workload
和 trait
字段下面是完整的 CR 描述。
apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: prameters: - name: xxx fieldPaths: - "spec.osType" workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: osType: linux containers: - name: my-cool-server image: name: example/very-cool-server:1.0.0 ports: - name: http value: 8080 env: - name: CACHE_SECRET
apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: cool-example spec: components: - componentName: example-server traits: - trait: apiVersion: core.oam.dev/v1alpha2 kind: ManualScalerTrait spec: replicaCount: 3
这样的好处很明显:
Deployment
(做为自定义 workload 接入)等资源;这里你们注意到 traits 下面是 []trait{CR}
而不是 []CR
的结构,多了一层看似无用的 trait
字段,主要由以下 2 个缘由:
ordering
) 等。研发可以留出字段给运维覆盖,一直是 OAM 很重要的功能。
体如今 OAM Spec 的流程上就是:研发在 Component 里面定义 parameter,运维在 AppConfig 里面经过 parameterValue 去覆盖对应的参数。
最初的参数传递,是在每一个字段后面有个 fromParam
字段,对于支持了自定义 schema 后,这样的方式显然是没法覆盖全部场景的:
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster parameters: - name: engineVersion type: string workloadSettings: - name: engineVersion type: string fromParam: engineVersion
后来咱们曾经提议过这样的方案:
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster parameters: - name: engineVersion type: string workloadSettings: engineVersion: "[fromParam(engineVersion)]"
这个方案最大的问题是 静态的 IaD (Infrastructure as Data) 里面加入了动态的函数,给理解和使用带来了复杂性。
通过多方面的讨论,在新方案里咱们经过 JsonPath 的形式描述要注入的参数位置,在用户理解上保证了 AppConfig 是静态的。
apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: containers: - name: my-cool-server image: name: example/very-cool-server:1.0.0 ports: - name: http value: 8080 env: - name: CACHE_SECRET value: cache parameters: - name: instanceName required: true fieldPaths: - ".metadata.name" - name: cacheSecret required: true fieldPaths: - ".workload.spec.containers[0].env[0].value"
fieldPaths 是个数组,每一个元素定义了参数和对应 Workload 里的字段。
apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: my-app-deployment spec: components: - componentName: example-server parameterValues: - name: cacheSecret value: new-cache
在 AppConfig 中仍是走 parameterValues 去覆盖 Component 中的 parameter。
原先组件这个概念叫 ComponentSchematic,这样命名的主要缘由是里面混了一些语法描述和选择,好比针对 Core Workload( container
)和针对扩展 Workload ( workloadSettings
),写法上不同的,container
里是定义具体的参数,workloadSettings
更像是 schema(参数怎么填的说明)。v1alpha1 版本的 WorkloadSetting 还融入了 type/description之类的,更显得模棱两可。
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: containers: ... workloadSettings: - name: engineVersion type: string description: engine version fromParam: engineVersion ...
在 v1alpha2 版本中,组件这个概念改成 Component,明确为 Workload 的实例,全部语法定义都是在WorkloadDefinition 中引用的实际 CRD 定义的。
在 K8s 实现中,WorkloadDefinition 就是引用 CRD ,Component.spec.workload 里就是写 CRD 对应的实例 CR。
apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: ...
v1alpha1 中的 Scope 是由 AppConfig 建立的,从例子中能够看出,本质上它也是个 CR,能够“推导”建立出 CR来。可是因为 Scope 的定位是能够容纳不一样 AppConfig 中的 Component,且 Scope 自己并不是一个 App,因此使用 AppConfig 建立 Scope 一直是不太合适的。
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: my-vpc-network spec: variables: - name: networkName value: "my-vpc" scopes: - name: network type: core.oam.dev/v1alpha1.Network properties: network-id: "[fromVariable(networkName)]" subnet-ids: "my-subnet1, my-subnet2"
v1alpha2 新版本全面使用 CR 来对应实例,为了让 Scope 的概念更清晰,更方便对应不一样类型的 Scope,将 Scope 拿出来直接由 ScopeDefinition 定义的 CRD 对应的 CR 建立。例子以下所示:
apiVersion: core.oam.dev/v1alpha2 kind: ScopeDefinition metadata: name: networkscope.core.oam.dev spec: allowComponentOverlap: true definitionRef: name: networkscope.core.oam.dev
apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope metadata: name: example-vpc-network labels: region: us-west environment: production spec: networkId: cool-vpc-network subnetIds: - cool-subnetwork - cooler-subnetwork - coolest-subnetwork internetGatewayType: nat
在 AppConfig 中使用 scope 引用以下所示:
apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: custom-single-app annotations: version: v1.0.0 description: "Customized version of single-app" spec: components: - componentName: frontend scopes: - scopeRef: apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope name: my-vpc-network - componentName: backend scopes: - scopeRef: apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope name: my-vpc-network
v1alpha1 版本中有 Variable 是为了 AppConfig 里开源引用一些公共变量减小冗余,因此加入了 Variable 列表。但实践来看,减小的冗余并无明显减小 OAM spec 的复杂性,相反,增长动态的函数显著增长了复杂性。
另外一方面,fromVariable 这样的能力彻底能够经过 helm template/ kustomiz 等工具来作,由这些工具渲染出完整的 OAM spec,再进行使用。
因此这里 variables 列表和相关的 fromVariable 均去掉,并不影响任何功能。
// 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: my-app-deployment spec: variables: - name: VAR_NAME value: SUPPLIED_VALUE components: - componentName: my-web-app-component instanceName: my-app-frontent parameterValues: - name: ANOTHER_PARAMETER value: "[fromVariable(VAR_NAME)]" traits: - name: ingress properties: DATA: "[fromVariable(VAR_NAME)]"
由于如今已经统一用 WorkloadDefinition 定义 Workload,Component 变成了实例,因此原先的六种核心Workload 实际上都变成了同一个 WorkloadDefinition,字段描述彻底同样,惟一的不一样是对 trait 约束和诉求不同。所以原先的六种核心 Workload 的 spec,统一修改成一种名为 ContainerizedWorkload 的 Workload 类型。
同时,这里计划要经过增长 policy 这样的概念,来让研发表达对运维策略的诉求,即 Component 中能够表达但愿增长哪些 trait。
apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: containerizedworkloads.core.oam.dev spec: definitionRef: name: containerizedworkloads.core.oam.dev
一个使用 ContainerizedWorkload 示例:
apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: frontend annotations: version: v1.0.0 description: "A simple webserver" spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: ContainerizedWorkload metadata: name: sample-workload spec: osType: linux containers: - name: web image: example/charybdis-single:latest@@sha256:verytrustworthyhash resources: cpu: required: 1.0 memory: required: 100MB env: - name: MESSAGE value: default parameters: - name: message description: The message to display in the web app. required: true type: string fieldPaths: - ".spec.containers[0].env[0].value"
对于原先是 K8s 上的应用管理平台,接入改造为 OAM 实现能够分为两个阶段:
现有的 CRD Operator** 功能上能够平滑接入 OAM 体系,好比做为一个独立扩展 Workload 接入。可是为了更好的让终端用户体会到 OAM 关注点分离的好处,咱们强烈建议 CRD Operator 根据研发和运维不一样的关注点分离为不一样的 CRD,研发关注的 CRD 做为 Workload 接入 OAM,运维关注的 CRD 做为 Trait 接入 OAM。
目前,OAM 规范和模型实际已解决许多现有问题,但它的路程才刚刚开始。OAM 是一个中立的开源项目,咱们欢迎更多的人参与其中,共同定义云原生应用交付的将来。
参与方式:
孙健波(花名:天元)阿里云技术专家,是 OAM 规范的主要制定者之一,致力于推进云原生应用标准化。同时也在阿里巴巴参与大规模云原生应用交付与应用管理相关工做。团队诚邀应用交付、Serverless、PaaS 领域的专家加入,欢迎联系 jianbo.sjb AT alibaba-inc.com
云原生应用平台诚邀 Kubernetes / Serverless / PaaS / 应用交付领域专家( P6-P8 )加盟:
简历马上回复,2~3 周出结果,简历投递:jianbo.sjb AT alibaba-inc.com。
“ 阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术圈。”