Author | Sun Jianbo (Tian Yuan)
Source | Alibaba Cloud Native Official Account
Last month, KubeVela was officially released as a simple, easy-to-use and highly scalable application management platform and core engine. It can be said to be a magic weapon for platform engineers to build their own cloud-native PaaS. Then this article uses a practical example to explain how to "launch" a new capability for you based on KubeVela's PaaS within 20 minutes.
Before starting the tutorial of this document, please make sure that you have installed KubeVela and its dependent K8s environment correctly .
The basic structure of KubeVela extension
The basic structure of KubeVela is shown in the figure:
To put it simply, KubeVela expands capabilities for users by adding Workload Type and Trait . The service provider of the platform registers and expands through the Definition file, and reveals the expanded functionality through Appfile upwards. The basic writing process is also given in the official documents, two of which are extension examples of Workload, and one is an extension example of Trait:
- Workload Type extension using OpenFaaS as an example
- Workload Type extension using cloud resource RDS as an example
- Trait extension using KubeWatch as an example
We take a built-in WorkloadDefinition as an example to introduce the basic structure of the Definition file:
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: webservice
annotations:
definition.oam.dev/description: "`Webservice` is a workload type to describe long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.
If workload type is skipped for any service defined in Appfile, it will be defaulted to `Web Service` type."
spec:
definitionRef:
name: deployments.apps
extension:
template: |
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": context.name
}
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
if parameter["env"] != _|_ {
env: parameter.env
}
if context["config"] != _|_ {
env: context.config
}
ports: [{
containerPort: parameter.port
}]
if parameter["cpu"] != _|_ {
resources: {
limits:
cpu: parameter.cpu
requests:
cpu: parameter.cpu
}}
}]
}}}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
// +usage=Commands to run in the container
cmd?: [...string]
// +usage=Which port do you want customer traffic sent to
// +short=p
port: *80 | int
// +usage=Define arguments by using environment variables
env?: [...{
// +usage=Environment variable name
name: string
// +usage=The value of the environment variable
value?: string
// +usage=Specifies a source the value of this var should come from
valueFrom?: {
// +usage=Selects a key of a secret in the pod's namespace
secretKeyRef: {
// +usage=The name of the secret in the pod's namespace to select from
name: string
// +usage=The key of the secret to select from. Must be a valid secret key
key: string
}
}
}]
// +usage=Number of CPU units for the service, like `0.5` (0.5 CPU core), `1` (1 CPU core)
cpu?: string
}
At first glance it looks quite long and seems very complicated, but don't worry, it is actually divided into two parts:
- Definition registration part without extension fields
- CUE Template section for Appfile
Let's take it apart and introduce it slowly, but it's actually very easy to learn.
Definition registration part without extension fields
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: webservice
annotations:
definition.oam.dev/description: "`Webservice` is a workload type to describe long-running, scalable, containerized services that have a stable network endpoint to receive external network traffic from customers.
If workload type is skipped for any service defined in Appfile, it will be defaulted to `Web Service` type."
spec:
definitionRef:
name: deployments.apps
This part ManDaManSuan line 11, which line 3 is introduced in the webservice
function, lines 5 are fixed format. Only 2 lines have specific information:
definitionRef:
name: deployments.apps
The meaning of these two lines represents what the CRD name is used behind this Definition, and its format is <resources>.<api-group>
. Learn K8s students should know K8s more commonly used by api-group
, version
and kind
locate resources, and kind
corresponding in K8s restful API is resources
. With the familiar Deployment
and ingress
, for example, its correspondence is as follows:
Here is a little knowledge. Why do we need to add the concept of resources when we have a kind? Because a CRD has some fields like status and replica in addition to the kind itself and hopes to be decoupled from the spec itself to be updated separately in the restful API, so in addition to the kind corresponding to the resources, there will be some additional resources, such as Deployment The status is expressed as
deployments/status
.
So I believe that you are smart enough to understand how to write Definition without extension. The easiest way is to splice it according to the K8s resource combination method. Just fill in the spaces in the three angle brackets below.
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: <这里写名称>
spec:
definitionRef:
name: <这里写resources>.<这里写api-group>
This is also true for TraitDefinition registration.
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: <这里写名称>
spec:
definitionRef:
name: <这里写resources>.<这里写api-group>
So put Ingress
as KubeVela is written into the extension:
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: ingress
spec:
definitionRef:
name: ingresses.networking.k8s.io
In addition, some other functional model layer functions have been added to TraitDefinition, such as:
appliesToWorkloads
: Indicates which workload types this trait can act on.conflictWith
: Indicates that this trait conflicts with other types of traits.workloadRefPath
: Indicates the workload field contained in this trait. KubeVela will automatically fill it when generating the trait object. ...
These functions are optional, and are not involved in this article. We will introduce them in detail in other subsequent articles.
So here, I believe you have mastered a basic extension mode without extensions, and the rest is the abstract template around CUE .
CUE Template section for Appfile
Students who are interested in CUE itself can refer to this introduction to CUE basics to do more understanding. Due to space limitations, this article will not elaborate on CUE itself.
Everyone knows that KubeVela's Appfile is very concise to write, but the object of K8s is a relatively complex YAML. In order to keep it concise without losing scalability, KubeVela provides a bridge from complexity to simplicity. This is the role of CUE Template in Definition.
CUE format template
Let us first look at a deployment YAML file, as shown below, many of the content is a fixed frame (template part), the content that really needs the user to fill is actually a few fields (parameter part).
apiVersion: apps/v1
kind: Deployment
meadata:
name: mytest
spec:
template:
spec:
containers:
- name: mytest
env:
- name: a
value: b
image: nginx:v1
metadata:
labels:
app.oam.dev/component: mytest
selector:
matchLabels:
app.oam.dev/component: mytest
In KubeVela, the fixed format Definition file is divided output
and the parameter
two parts. Where the output
content is the "Templates section", which parameter
is part of the argument.
Then let's rewrite the above Deployment YAML into the format of the template in the Definition.
output: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: name: "mytest"
spec: {
selector: matchLabels: {
"app.oam.dev/component": "mytest"
}
template: {
metadata: labels: {
"app.oam.dev/component": "mytest"
}
spec: {
containers: [{
name: "mytest"
image: "nginx:v1"
env: [{name:"a",value:"b"}]
}]
}}}
}
This format is very similar to json, in fact this is the format of CUE, and CUE itself is a superset of json. In other words, the CUE format meets the JSON rules and adds some simple rules to make it easier to read and use:
- The comment style of the C language.
- The double quotation marks that indicate the field name can be defaulted without special symbols.
- The comma at the end of the field value can be defaulted, and there is no error if the comma at the end of the field is written.
- The outer brace can be omitted.
Template parameter in CUE format--variable reference
The template part is written, let us build the parameter part, and this parameter is actually a variable reference.
parameter: {
name: string
image: string
}
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": parameter.name
}
template: {
metadata: labels: {
"app.oam.dev/component": parameter.name
}
spec: {
containers: [{
name: parameter.name
image: parameter.image
}]
}}}
}
As shown in the above example, the template parameters KubeVela is through parameter
this section accomplished, but parameter
essentially as a reference, replacing the output
certain field.
Complete Definition and use in Appfile
In fact, after the combination of the above two parts, we can already write a complete Definition file:
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: mydeploy
spec:
definitionRef:
name: deployments.apps
extension:
template: |
parameter: {
name: string
image: string
}
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": parameter.name
}
template: {
metadata: labels: {
"app.oam.dev/component": parameter.name
}
spec: {
containers: [{
name: parameter.name
image: parameter.image
}]
}}}
}
In order to facilitate debugging, in general, it may be previously divided into two files, the front part of the discharge portion yaml assumed named def.yaml
as:
apiVersion: core.oam.dev/v1alpha2
kind: WorkloadDefinition
metadata:
name: mydeploy
spec:
definitionRef:
name: deployments.apps
extension:
template: |
The other puts the cue file, assuming the name is def.cue
:
parameter: {
name: string
image: string
}
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": parameter.name
}
template: {
metadata: labels: {
"app.oam.dev/component": parameter.name
}
spec: {
containers: [{
name: parameter.name
image: parameter.image
}]
}}}
}
First to def.cue
make a formatted, format, while cue tool itself will do some checking, can also be more in-depth to make debugging cue command :
cue fmt def.cue
After debugging, you can assemble this yaml through a script:
./hack/vela-templates/mergedef.sh def.yaml def.cue > mydeploy.yaml
Then apply this yaml file to the K8s cluster.
$ kubectl apply -f mydeploy.yaml
workloaddefinition.core.oam.dev/mydeploy created
Once the new ability kubectl apply
to Kubernetes in without restarting, do not update, KubeVela users can immediately see a new capability appears and you can use:
$ vela worklaods
Automatically discover capabilities successfully ✅ Add(1) Update(0) Delete(0)
TYPE CATEGORY DESCRIPTION
+mydeploy workload description not defined
NAME DESCRIPTION
mydeploy description not defined
It is used in Appfile as follows:
name: my-extend-app
services:
mysvc:
type: mydeploy
image: crccheck/hello-world
name: mysvc
The implementation vela up
will be able to run this up:
$ vela up -f docs/examples/blog-extension/my-extend-app.yaml
Parsing vela appfile ...
Loading templates ...
Rendering configs for service (mysvc)...
Writing deploy config to (.vela/deploy.yaml)
Applying deploy configs ...
Checking if app has been deployed...
App has not been deployed, creating a new deployment...
✅ App has been deployed