交付完整模块
现在你已经了解了OAM 模型和模块定义(X-Definition )的概念,本节将介绍如何使用 CUE 交付完整的模块化功能,使得你的平台可以随着用户需求变化动态扩展功能,适应各类用户和场景,满足公司业务长期发展的迭代诉求。
将 Kubernetes API 对象转化为自定义组件
KubeVela 使用 CUE 配置语言作为管理用户模块化交付的核心,同时也围绕 CUE 提供了管理工具来编辑和生成 KubeVela 的模块定义对象。
下面我们以 Kubernetes 官方的 StatefulSet 对象为例,来具体看如何使用 KubeVela 构建自定义的模块化功能并提供能力。
我们将官方文档中 StatefulSet 的 YAML 例子保存在本地,并命名为 my-stateful.yaml,
然后执行如下命令,生成一个名为 “my-stateful” 的 Component 模块定义,并输出到 “my-stateful.cue” 文件中:
 vela def init my-stateful -t component --desc "My StatefulSet component." --template-yaml ./my-stateful.yaml -o my-stateful.cue
查看生成的 “my-stateful.cue” 文件:
$ cat my-stateful.cue
"my-stateful": {
    annotations: {}
    attributes: workload: definition: {
        apiVersion: "<change me> apps/v1"
        kind:       "<change me> Deployment"
    }
    description: "My StatefulSet component."
    labels: {}
    type: "component"
}
template: {
    output: {
        apiVersion: "v1"
        kind:       "Service"
            ... // 省略一些非重要信息
    }
    outputs: web: {
        apiVersion: "apps/v1"
        kind:       "StatefulSet"
            ... // 省略一些非重要信息
    }
    parameter: {}
}
下面我们来对这个自动生成的自定义组件做一些微调:
- StatefulSet 官网的例子是由 
StatefulSet和Service两个对象构成的一个复合组件。而根据 KubeVela 自定义组件的规则,在复合组件中,比如 StatefulSet 这样的核心工作负载需要由template.output字段表示,其他辅助对象用template.outputs表示,所以我们将内容做一些调整,将自动生成的 output 和 outputs 中的全部调换。 - 然后我们将核心工作负载的 apiVersion 和 kind 数据填写到标注为 
<change me>的部分 
修改后可以用 vela def vet做一下格式检查和校验。
$ vela def vet my-stateful.cue
Validation succeed.
经过两步改动后的文件如下:
$ cat my-stateful.cue
"my-stateful": {
    annotations: {}
    attributes: workload: definition: {
        apiVersion: "apps/v1"
        kind:       "StatefulSet"
    }
    description: "My StatefulSet component."
    labels: {}
    type: "component"
}
template: {
    output: {
        apiVersion: "apps/v1"
        kind:       "StatefulSet"
        metadata: name: "web"
        spec: {
            selector: matchLabels: app: "nginx"
            replicas:    3
            serviceName: "nginx"
            template: {
                metadata: labels: app: "nginx"
                spec: {
                    containers: [{
                        name: "nginx"
                        ports: [{
                            name:          "web"
                            containerPort: 80
                        }]
                        image: "k8s.gcr.io/nginx-slim:0.8"
                        volumeMounts: [{
                            name:      "www"
                            mountPath: "/usr/share/nginx/html"
                        }]
                    }]
                    terminationGracePeriodSeconds: 10
                }
            }
            volumeClaimTemplates: [{
                metadata: name: "www"
                spec: {
                    accessModes: ["ReadWriteOnce"]
                    resources: requests: storage: "1Gi"
                    storageClassName: "my-storage-class"
                }
            }]
        }
    }
    outputs: web: {
        apiVersion: "v1"
        kind:       "Service"
        metadata: {
            name: "nginx"
            labels: app: "nginx"
        }
        spec: {
            clusterIP: "None"
            ports: [{
                name: "web"
                port: 80
            }]
            selector: app: "nginx"
        }
    }
    parameter: {}
}
将该组件定义安装到 Kubernetes 集群中:
$ vela def apply my-stateful.cue
ComponentDefinition my-stateful created in namespace vela-system.
此时平台的最终用户已经可以通过 vela components命令看到有一个 my-stateful组件可以使用了。
$ vela components
NAME        NAMESPACE   WORKLOAD                                DESCRIPTION
...
my-stateful vela-system statefulsets.apps                       My StatefulSet component.
... 
通过 KubeVela 的应用部署计划发布到集群中,就可以拉起我们刚刚定义的 StatefulSet 和 Service 对象。
cat <<EOF | vela up -f -
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: website
spec:
  components:
    - name: my-component
      type: my-stateful
EOF
为组件定义定制化参数
为了满足用户变化的需求,我们需要在最后的 parameter 里暴露一些参数,在 CUE 基础入门文档中你可以了解到参数相关的语法。在本例中,我们为用户暴露一下参数:
- 镜像名称,允许用户自定义镜像
 - 实例名,允许用户自定义生成的 StatefulSet 对象和 Service 对象的实例名称
 - 副本数,生成对象的副本数
 
    ... # 省略其他没有修改的字段 
    template: {
        output: {
            apiVersion: "apps/v1"
            kind:       "StatefulSet"
            metadata: name: parameter.name
            spec: {
                selector: matchLabels: app: "nginx"
                replicas:    parameter.replicas
                serviceName: "nginx"
                template: {
                    metadata: labels: app: "nginx"
                    spec: {
                        containers: [{
                            image: parameter.image
        
                            ... // 省略其他没有修改的字段  
                        }]
                    }
                }
                    ... // 省略其他没有修改的字段
            }
        }
        outputs: web: {
            apiVersion: "v1"
            kind:       "Service"
            metadata: {
                name: "nginx"
                labels: app: "nginx"
            }
            spec: {
                ... // 省略其他没有修改的字段      
            }
        }
        parameter: {
            image: string
            name: string
            replicas: int
        }
    }
修改后同样使用 vela def apply安装到集群中:
$ vela def apply my-stateful.cue
ComponentDefinition my-stateful in namespace vela-system updated.
这个修改过程是实时生效的,用户立即可以看到系统中的 my-stateful 组件增加了新的参数。
$ vela show my-stateful
# Properties
+----------+-------------+--------+----------+---------+
|   NAME   | DESCRIPTION |  TYPE  | REQUIRED | DEFAULT |
+----------+-------------+--------+----------+---------+
| name     |             | string | true     |         |
| replicas |             | int    | true     |         |
| image    |             | string | true     |         |
+----------+-------------+--------+----------+---------+
组件定义的修改并不会影响已经在运行的应用,当下次应用修改并重新部署时,新的组件定义就会生效。
最终用户就可以在应用中指定新增的这三个参数:
    apiVersion: core.oam.dev/v1beta1
    kind: Application
    metadata:
      name: website
    spec:
      components:
        - name: my-component
          type: my-stateful
          properties:
            image: nginx:latest
            replicas: 1
            name: my-component
将文件保存在本地并命名为 app-stateful.yaml,执行 vela up -f app-stateful.yaml更新应用,你可以看到 StatefulSet 对象的名称、镜像和实例数均已更新。
调试模块化功能的正确性
为了保证用户的应用使用参数能够正确运行,你也可以用 vela dry-run 命令对你的模板进行试运行验证。
vela dry-run -f app-stateful.yaml
查看输出,你就可以对比生成的对象和你实际期望的对象是否一致。甚至可以直接把这个 YAML 执行到 Kubernetes 集群中使用看运行的结果做验证。
Details
# Application(website) -- Component(my-component)
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
    app.oam.dev/appRevision: ""
    app.oam.dev/component: my-component
    app.oam.dev/name: website
    workload.oam.dev/type: my-stateful
  name: nginx
  namespace: default
spec:
  clusterIP: None
  ports:
  - name: web
    port: 80
  selector:
    app: nginx
  template:
    spec:
      containers:
      - image: saravak/fluentd:elastic
        name: my-sidecar
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app.oam.dev/appRevision: ""
    app.oam.dev/component: my-component
    app.oam.dev/name: website
    trait.oam.dev/resource: web
    trait.oam.dev/type: AuxiliaryWorkload
  name: web
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  serviceName: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: k8s.gcr.io/nginx-slim:0.8
        name: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: www
      terminationGracePeriodSeconds: 10
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
      storageClassName: my-storage-class
你还可以通过 vela dry-run -h 来查看更多可用的功能参数。
使用上下文信息减少参数
在我们上面的 Application 例子中,properties 中的 name 和 Component 的 name 字段是相同的,此时我们可以在模板中使用携带了上下文信息的 context关键字,其中 context.name 就是运行时组件名称,此时 parameter 中的 name 参数就不再需要的。
... # 省略其他没有修改的字段 
template: {
    output: {
        apiVersion: "apps/v1"
        kind:       "StatefulSet"
        metadata: name: context.name
            ... // 省略其他没有修改的字段
    }
    parameter: {
        image: string
        replicas: int
    }
}
KubeVela 内置了应用所需的上下文信息,你可以根据需要配置.
使用运维能力按需添加配置
对于用户的新需求,除了修改组件定义增加参数以外,你还可以使用运维能力,按需添加配置。一方面,KubeVela 已经内置了大量的通用运维能力,可以满足诸如:添加 label、annotation,注入环境变量、sidecar,添加 volume 等等的需求。另一方面,你可以像自定义组件一样,自定义补丁型运维特征,来满足更多的配置灵活组装的需求。
你可以使用 vela traits 查看,带 * 标记的 trait 均为通用 trait,能够对常见的 Kubernetes 资源对象做操作。
$ vela traits
NAME                        NAMESPACE   APPLIES-TO          CONFLICTS-WITH  POD-DISRUPTIVE  DESCRIPTION
annotations                 vela-system *                                   true            Add annotations on K8s pod for your workload which follows
                                                                                            the pod spec in path 'spec.template'.
configmap                   vela-system *                                   true            Create/Attach configmaps on K8s pod for your workload which
                                                                                            follows the pod spec in path 'spec.template'.
env                         vela-system *                                   false           add env on K8s pod for your workload which follows the pod
                                                                                            spec in path 'spec.template.'
hostalias                   vela-system *                                   false           Add host aliases on K8s pod for your workload which follows
                                                                                            the pod spec in path 'spec.template'.
labels                      vela-system *                                   true            Add labels on K8s pod for your workload which follows the
                                                                                            pod spec in path 'spec.template'.
lifecycle                   vela-system *                                   true            Add lifecycle hooks for the first container of K8s pod for
                                                                                            your workload which follows the pod spec in path
                                                                                            'spec.template'.
node-affinity               vela-system *                                   true            affinity specify node affinity and toleration on K8s pod for
                                                                                            your workload which follows the pod spec in path
                                                                                            'spec.template'.
scaler                      vela-system *                                   false           Manually scale K8s pod for your workload which follows the
                                                                                            pod spec in path 'spec.template'.
sidecar                     vela-system *                                   true            Inject a sidecar container to K8s pod for your workload
                                                                                            which follows the pod spec in path 'spec.template'.
以 sidecar 为例,你可以查看 sidecar 的用法:
$ vela show sidecar
# Properties
+---------+-----------------------------------------+-----------------------+----------+---------+
|  NAME   |               DESCRIPTION               |         TYPE          | REQUIRED | DEFAULT |
+---------+-----------------------------------------+-----------------------+----------+---------+
| name    | Specify the name of sidecar container   | string                | true     |         |
| cmd     | Specify the commands run in the sidecar | []string              | false    |         |
| image   | Specify the image of sidecar container  | string                | true     |         |
| volumes | Specify the shared volume path          | [[]volumes](#volumes) | false    |         |
+---------+-----------------------------------------+-----------------------+----------+---------+
## volumes
+------+-------------+--------+----------+---------+
| NAME | DESCRIPTION |  TYPE  | REQUIRED | DEFAULT |
+------+-------------+--------+----------+---------+
| path |             | string | true     |         |
| name |             | string | true     |         |
+------+-------------+--------+----------+---------+
直接使用 sidecar 注入一个容器,应用的描述如下:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: website
spec:
  components:
    - name: my-component
      type: my-stateful
      properties:
        image: nginx:latest
        replicas: 1
        name: my-component
      traits:
      - type: sidecar
        properties:
          name: my-sidecar
          image: saravak/fluentd:elastic
部署运行该应用,就可以看到 StatefulSet 中已经部署运行了一个 fluentd 的 sidecar。
你也可以使用 vela def 获取 sidecar 的 CUE 源文件进行修改,增加参数等。
vela def get sidecar
运维能力的自定义与组件自定义类似,不再赘述,你可以阅读运维能力自定义文档了解更详细的功能。
总结
本节介绍了如何通过 CUE 交付完整的模块化能力,其核心是可以随着用户的需求,不断动态增加配置能力,逐步暴露更多的功能和用法,以便降低用户整体的学习门槛,最终提升研发效率。 KubeVela 背后提供的开箱即用的能力,包括组件、运维功能、策略以及工作流,均是通过同样的方式提供了可插拔、可修改的能力。