OAM Definition Protocol
KubeVela is fully programmable via CUE, while it leverage Kubernetes as control plane and align with the API in yaml.
You can manage the definition in CUE and the vela def
command will render it into Kubernetes API with the following protocol.
Essentially, a definition object in KubeVela is a programmable building block. A definition object normally includes several information to model a certain platform capability that would used in further application deployment:
- Capability Indicator
ComponentDefinition
usesspec.workload
to indicate the workload type of this component.TraitDefinition
usesspec.definition
to indicate the provider of this trait.- These indicators can be auto detected, so they’re not necessary.
- Interoperability Fields
- they are for the platform to ensure a trait can work with given workload type. Hence only
TraitDefinition
has these fields.
- they are for the platform to ensure a trait can work with given workload type. Hence only
- Capability Encapsulation and Abstraction defined by
spec.schematic
- this defines the templating and parametering (i.e. encapsulation) of this capability.
Hence, the basic structure of definition object is as below:
apiVersion: core.oam.dev/v1beta1
kind: XxxDefinition
metadata:
name: <definition name>
annotations:
<map of annotations>
labels:
<map of labels>
spec:
...
schematic:
cue:
# cue template ...
# ... interoperability fields
Below is a framework of definition in CUE format:
<definition name>: {
annotations: {}
labels: {}
attributes: {}
description: ""
type: "<definition type>"
}
template: {
...
}
- The
"<definition name>"
aligns with the.metadata.name
in OAM API. - The
.annotations
aligns with the.metadata.annotations
in OAM API. - The
.labels
aligns with the.metadata.labels
in OAM API. - The
.attributes.
aligns with the.spec.
in OAM API except the.spec.schematic
field. - The
template
aligns with the.spec.schematic.cue
field, only CUE schematic supported in this conversion. - The
description
aligns with the.metadata.annotations["definition.oam.dev/description"]
field.
Let’s check the OAM definition API details one by one.
The design goal of ComponentDefinition is to allow platform administrators to encapsulate any type of deployable products into “components” to be delivered. Once defined, this type of component can be referenced, instantiated and delivered by users in the Application
.
Common component types include Helm Chart, Kustomize directory, a set of Kubernetes YAML files, container images, cloud resource IaC files, or CUE configuration file modules, etc. The component supplier corresponds to the real-world role, which is generally a third-party software distributor (ISV), a DevOps team engineer, or a code package and image generated by the CI system you built.
ComponentDefinition can be shared and reused. For example, a Helm chart, a CUE module, or a Terraform module. Another example is, for an Alibaba Cloud RDS
component type, end users can select the same Alibaba Cloud RDS
component type in different applications and instantiate them into cloud database instances with different specifications and different parameter configurations.
Let’s take a look at the frame format of ComponentDefinition:
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: <ComponentDefinition name>
annotations:
definition.oam.dev/description: <Function description>
spec:
workload: # Workload Capability Indicator
definition:
apiVersion: <Kubernetes Workload resource group>
kind: <Kubernetes Workload types>
schematic: # Component description
cue: # Details of components defined by CUE language
template: <CUE format template>
The indicator of workload type is declared as spec.workload
.
Below is a definition for Web Service in KubeVela:
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: webservice
namespace: default
annotations:
definition.oam.dev/description: "Describes long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers."
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
...
In above example, it claims to leverage Kubernetes Deployment (apiVersion: apps/v1
, kind: Deployment
) as the workload type for component.
The programmable template of given capability are defined in spec.schematic
field. For example, below is the full definition of Helm type in KubeVela:
Details
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: helm
namespace: vela-system
annotations:
definition.oam.dev/description: "helm release is a group of K8s resources from either git repository or helm repo"
spec:
workload:
type: autodetects.core.oam.dev
schematic:
cue:
template: |
output: {
apiVersion: "source.toolkit.fluxcd.io/v1beta1"
metadata: {
name: context.name
}
if parameter.repoType == "git" {
kind: "GitRepository"
spec: {
url: parameter.repoUrl
ref:
branch: parameter.branch
interval: parameter.pullInterval
}
}
if parameter.repoType == "helm" {
kind: "HelmRepository"
spec: {
interval: parameter.pullInterval
url: parameter.repoUrl
if parameter.secretRef != _|_ {
secretRef: {
name: parameter.secretRef
}
}
}
}
}
outputs: release: {
apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
kind: "HelmRelease"
metadata: {
name: context.name
}
spec: {
interval: parameter.pullInterval
chart: {
spec: {
chart: parameter.chart
version: parameter.version
sourceRef: {
if parameter.repoType == "git" {
kind: "GitRepository"
}
if parameter.repoType == "helm" {
kind: "HelmRepository"
}
name: context.name
namespace: context.namespace
}
interval: parameter.pullInterval
}
}
if parameter.targetNamespace != _|_ {
targetNamespace: parameter.targetNamespace
}
if parameter.values != _|_ {
values: parameter.values
}
}
}
parameter: {
repoType: "git" | "helm"
// +usage=The Git or Helm repository URL, accept HTTP/S or SSH address as git url.
repoUrl: string
// +usage=The interval at which to check for repository and relese updates.
pullInterval: *"5m" | string
// +usage=1.The relative path to helm chart for git source. 2. chart name for helm resource
chart: string
// +usage=Chart version
version?: string
// +usage=The Git reference to checkout and monitor for changes, defaults to master branch.
branch: *"master" | string
// +usage=The name of the secret containing authentication credentials for the Helm repository.
secretRef?: string
// +usage=The namespace for helm chart
targetNamespace?: string
// +usage=Chart version
value?: #nestedmap
}
#nestedmap: {
...
}
The specification of schematic
is explained in the CUE with KubeVela documentations.
TraitDefinition provides a series of DevOps actions for the component that can be bound on demand. These operation and maintenance actions are usually provided by the platform administrator, such as adding a load balancing strategy, routing strategy, or performing scaler, gray release strategy, etc.
The format and field functions of the TraitDefinition are as follows:
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
name: <TraitDefinition name>
annotations:
definition.oam.dev/description: <function description>
spec:
definition:
apiVersion: <corresponding Kubernetes resource group>
kind: <corresponding Kubernetes resource type>
workloadRefPath: <The path to the reference field of the Workload object in the Trait>
podDisruptive: <whether the parameter update of Trait cause the underlying resource (pod) to restart>
manageWorkload: <Whether the workload is managed by this Trait>
skipRevisionAffect: <Whether this Trait is not included in the calculation of version changes>
appliesToWorkloads:
- <Workload that TraitDefinition can adapt to>
conflictsWith:
- <other Traits that conflict with this><>
revisionEnabled: <whether Trait is aware of changes in component version>
controlPlaneOnly: <Whether resources generated by trait are dispatched to the hubcluster (local)>
schematic: # Abstract
cue: # There are many abstracts
template: <CUE format template>
The interoperability fields are trait only. Let’s explain them in detail.
This field defines the constraints that what kinds of workloads this trait is allowed to apply to.
- It accepts an array of string as value.
- Each item in the array refers to one or a group of workload types to which this trait is allowed to apply.
There are three approaches to denote one or a group of workload types.
ComponentDefinition
definition reference (CRD name), e.g.,deployments.apps
- Resource group of
ComponentDefinition
definition reference prefixed with*.
, e.g.,*.apps
,*.oam.dev
. This means the trait is allowed to apply to any workloads in this group. *
means this trait is allowed to apply to any workloads
If this field is omitted, it means this trait is allowed to apply to any workload types.
For now, this field is not checked when applying application. Strict checking is planed to be implemented in the KubeVela 1.6.
This field defines that constraints that what kinds of traits are conflicting with this trait, if they are applied to the same workload.
- It accepts an array of string as value.
- Each item in the array refers to one or a group of traits.
There are four approaches to denote one or a group of workload types.
TraitDefinition
name, e.g.,ingress
- Resource group of
TraitDefinition
definition reference prefixed with*.
, e.g.,*.networking.k8s.io
. This means the trait is conflicting with any traits in this group. *
means this trait is conflicting with any other trait.
If this field is omitted, it means this trait is NOT conflicting with any traits.
This field defines the field path of the trait which is used to store the reference of the workload to which the trait is applied.
- It accepts a string as value, e.g.,
spec.workloadRef
.
If this field is set, KubeVela core will automatically fill the workload reference into target field of the trait. Then the trait controller can get the workload reference from the trait latter. So this field usually accompanies with the traits whose controllers relying on the workload reference at runtime.
For example, when this field set in the following custom trait:
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
name: myscaler
spec:
workloadRefPath: spec.workloadRef
schematic:
cue:
template: |
outputs: scaler: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ManualScalerTrait"
spec: {
replicaCount: parameter.replicas
}
}
parameter: {
//+short=r
//+usage=Replicas of the workload
replicas: *1 | int
}
The controller will automatically inject the apiVersion/Kind/Name of the workload defined in the component into the field specified in workloadRefPath
, the result of the trait object in the app can be something like below, assume the workload is a Kubernetes Deployment:
apiVersion: "core.oam.dev/v1alpha2"
kind: "ManualScalerTrait"
spec:
workloadRef:
apiVersion: apps/v1
kind: Deployment
name: mywork
replicaCount: 1
This field defines that adding/updating the trait will disruptive the pod or not. In this example, the answer is not, so the field is false
, it will not affect the pod when the trait is added or updated. If the field is true
, then it will cause the pod to disruptive and restart when the trait is added or updated. By default, the value is false
which means this trait will not affect. Please take care of this field, it’s really important and useful for serious large scale production usage scenarios.
Let’s look at a practical example:
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
annotations:
definition.oam.dev/description: "configure k8s Horizontal Pod Autoscaler for Component which using Deployment as worklaod"
name: hpa
spec:
appliesToWorkloads:
- deployments.apps
workloadRefPath: spec.scaleTargetRef
schematic:
cue:
template: |
outputs: hpa: {
apiVersion: "autoscaling/v2beta2"
kind: "HorizontalPodAutoscaler"
spec: {
minReplicas: parameter.min
maxReplicas: parameter.max
metrics: [{
type: "Resource"
resource: {
name: "cpu"
target: {
type: "Utilization"
averageUtilization: parameter.cpuUtil
}
}
}]
}
}
parameter: {
min: *1 | int
max: *10 | int
cpuUtil: *50 | int
}
PolicyDefinition is simimarly to TraitDefinition, the difference is that TraitDefinition acts on a single component but PolicyDefinition is to act on the entire application as a whole (multiple components).
It can provide global policy for applications, commonly including global security policies (such as RBAC permissions, auditing, and key management), application insights (such as application SLO management, etc.).
The format is as follows:
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
name: <PolicyDefinition name>
annotations:
definition.oam.dev/description: <function description>
spec:
schematic: # strategy description
cue:
template: <CUE format template>
A specific example is shown below:
apiVersion: core.oam.dev/v1beta1
kind: PolicyDefinition
metadata:
name: env-binding
annotations:
definition.oam.dev/description: <Provide differentiated configuration and environmental scheduling strategies for applications>
spec:
schematic:
cue:
template: |
output: {
apiVersion: "core.oam.dev/v1alpha1"
kind: "EnvBinding"
spec: {
engine: parameter.engine
appTemplate: {
apiVersion: "core.oam.dev/v1beta1"
kind: "Application"
metadata: {
name: context.appName
namespace: context.namespace
}
spec: {
components: context.components
}
}
envs: parameter.envs
}
}
#Env: {
name: string
patch: components: [...{
name: string
type: string
properties: {...}
}]
placement: clusterSelector: {
labels?: [string]: string
name?: string
}
}
parameter: {
engine: *"ocm" | string
envs: [...#Env]
}
WorkflowStepDefinition is used to describe a series of steps that can be declared in the workflow, such as the deployment of execution resources, status check, data output, dependent input, external script call, etc.
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
name: <WorkflowStepDefinition name>
annotations:
definition.oam.dev/description: <function description>
spec:
schematic: # node description
cue:
template: <CUE format template>
An actual WorkflowStepDefinition is as follows:
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
name: apply-component
spec:
schematic:
cue:
template: |
import ("vela/op")
parameter: {
component: string
}
// load component from application
component: op.#Load & {
component: parameter.component
}
// apply workload to kubernetes cluster
apply: op.#ApplyComponent & {
component: parameter.name
}
// wait until workload.status equal "Running"
wait: op.#ConditionalWait & {
continue: apply.status.phase =="Running"
}
Once the application is created, KubeVela will tag the created resources with a series of tags, which include the version, name, type, etc. of the application. Through these standard protocols, application components, traits and policies can be coordinated. The specific metadata list is as follows:
Label | Description |
---|---|
workload.oam.dev/type | Corresponds to the name of ComponentDefinition |
trait.oam.dev/type | Corresponds to the name of TraitDefinition |
app.oam.dev/name | Application name |
app.oam.dev/component | Component name |
trait.oam.dev/resource | outputs.\<resource type\> in Trait |
app.oam.dev/appRevision | Application Revision Name |
In the Definitions, some runtime context information can be obtained through the context
variable, please check out:
- Context data in Component Definition
- Context data in Trait Definition
- Context data in Workflow Sep Definition
Please note that all the Definition concepts introduced in this section only need to be understood by the platform administrator when they want to expand the functions of KubeVela. The end users will learn the schema of above definitions with visualized forms (or the JSON schema of parameters if they prefer) and reference them in application deployment plan. Please check the Generate Forms from Definitions section about how this is achieved.
Last updated on Aug 4, 2023 by Daniel Higuero