基础入门

    KubeVela 将 CUE 作为应用交付核心依赖和扩展方式的原因如下::

    • CUE 本身就是为大规模配置而设计。 CUE 能够感知非常复杂的配置文件,并且能够安全地更改可修改配置中成千上万个对象的值。这非常符合 KubeVela 的目标,即以可编程的方式,去定义和交付生产级别的应用程序。
    • CUE 支持一流的代码生成和自动化。 CUE 原生支持与现有工具以及工作流进行集成,反观其他工具则需要自定义复杂的方案才能实现。例如,需要手动使用 Go 代码生成 OpenAPI 模式。KubeVela 也是依赖 CUE 该特性进行构建开发工具和 GUI 界面的。
    • CUE 与 Go 完美集成。 KubeVela 像 Kubernetes 系统中的大多数项目一样使用 GO 进行开发。CUE 已经在 Go 中实现并提供了丰富的 API。KubeVela 以 CUE 为核心实现 Kubernetes 控制器。借助 CUE,KubeVela 可以轻松处理数据约束问题。

    前提

    请确保你的环境中已经安装如下命令行:

    • cue v0.2.2 目前 KubeVela 暂时只支持 CUE v0.2.2 版本,将在后续迭代中升级支持新的 CUE 版本。

    CUE 是 JSON 的超集, 我们可以像使用 JSON 一样使用 CUE,并具备以下特性:

    • C 语言风格的注释
    • 字段名称可以用双引号括起来,注意字段名称中不可以带特殊字符
    • 可选字段末尾是否有逗号
    • 允许数组中最后一个元素末尾带逗号
    • 外大括号可选

    请先复制以下信息,保存为一个 文件:

    接下来,我们以上面这个文件为例子,来学习 CUE 命令行的相关指令:

    • 如何格式化 CUE 文件。(如果你使用 Goland 或者类似 JetBrains IDE, 可以参考该文章配置自动格式化插件 。)

      该命令不仅可以格式化 CUE 文件,还能提示错误的模型,相当好用的命令。

      1. cue fmt first.cue
    • 如何校验模型。除了 cue fmt,你还可以使用 cue vet 来校验模型。

      1. $ cue vet first.cue
      2. some instances are incomplete; use the -c flag to show errors or suppress this message
      3. $ cue vet first.cue -c
      4. e: incomplete value string

      提示我们:这个文件里的 e 这个变量,有数据类型 string 但并没有赋值。

    • 如何计算/渲染结果。 cue eval 可以计算 CUE 文件并且渲染出最终结果。 我们看到最终结果中并不包含 a: floatb: int,这是因为这两个变量已经被计算填充。 其中 e: string 没有被明确的赋值, 故保持不变.

      1. $ cue eval first.cue
      2. a: 1.5
      3. b: 1
      4. d: [1, 2, 3]
      5. g: {
      6. h: "abc"
      7. }
      8. e: string
    • 如何指定渲染的结果。例如,我们仅想知道文件中 b 的渲染结果,则可以使用该参数 -e

      1. $ cue eval -e b first.cue
      2. 1
    • 如何导出渲染结果。 cue export 可以导出最终渲染结果。如果一些变量没有被定义执行该命令将会报错。

      1. $ cue export first.cue
      2. e: incomplete value string

      我们更新一下 first.cue 文件,给 e 赋值:

      1. a: 1.5
      2. a: float
      3. b: 1
      4. b: int
      5. d: [1, 2, 3]
      6. g: {
      7. h: "abc"
      8. }
      9. e: string
      10. e: "abc"

      然后,该命令就可以正常工作。默认情况下, 渲染结果会被格式化为 JSON 格式。

      1. $ cue export first.cue
      2. {
      3. "a": 1.5,
      4. "b": 1,
      5. "d": [
      6. 1,
      7. 2,
      8. 3
      9. ],
      10. "g": {
      11. "h": "abc"
      12. },
      13. "e": "abc"
      14. }
    • 如何导出 YAML 格式的渲染结果。

      1. $ cue export first.cue --out yaml
      2. a: 1.5
      3. b: 1
      4. d:
      5. - 1
      6. - 2
      7. - 3
      8. g:
      9. h: abc
      10. e: abc
      1. $ cue export -e g first.cue
      2. {
      3. "h": "abc"
      4. }

    以上, 你已经学习完所有常用的 CUE 命令行指令。

    学习 CUE 语言

    在熟悉完常用 CUE 命令行指令后,我们来进一步学习 CUE 语言。

    先了解 CUE 的数据类型。以下是它的基础数据类型:

    1. // float
    2. a: 1.5
    3. // int
    4. b: 1
    5. // string
    6. c: "blahblahblah"
    7. // array
    8. d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
    9. // bool
    10. e: true
    11. // struct
    12. f: {
    13. a: 1.5
    14. b: 1
    15. d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
    16. g: {
    17. h: "abc"
    18. }
    19. }
    20. // null
    21. j: null

    如何自定义 CUE 类型?使用 # 符号来指定一些表示 CUE 类型的变量。

    1. #abc: string

    我们将上述内容保存到 second.cue 文件。 执行 cue export 不会报 #abc 是一个类型不完整的值。

    你还可以定义更复杂的自定义结构,比如:

    1. #abc: {
    2. x: int
    3. y: string
    4. z: {
    5. a: float
    6. b: bool
    7. }

    自定义结构在 KubeVela 中被广泛用于模块定义(X-Definitions)和进行验证。

    下面,我们开始尝试利用刚刚学习到的知识,来定义 CUE 模版。

    1. 定义结构体变量 parameter.
    1. parameter: {
    2. name: string
    3. image: string
    4. }

    保存上述变量到文件 deployment.cue.

    1. 定义更复杂的结构变量 template 同时引用变量 parameter.
    1. template: {
    2. apiVersion: "apps/v1"
    3. kind: "Deployment"
    4. spec: {
    5. selector: matchLabels: {
    6. "app.oam.dev/component": parameter.name
    7. }
    8. template: {
    9. metadata: labels: {
    10. "app.oam.dev/component": parameter.name
    11. }
    12. spec: {
    13. containers: [{
    14. name: parameter.name
    15. image: parameter.image
    16. }]
    17. }}}
    18. }

    熟悉 Kubernetes 的你可能已经知道,这是 Kubernetes Deployment 的模板。 parameter 为模版的参数部分。

    添加上述内容到文件 deployment.cue.

    1. 随后, 我们通过更新以下内容来完成变量赋值:
    1. parameter:{
    2. name: "mytest"
    3. image: "nginx:v1"
    4. }
    1. 最后, 导出渲染结果为 YAML 格式:
    1. $ cue export deployment.cue -e template --out yaml
    2. apiVersion: apps/v1
    3. kind: Deployment
    4. spec:
    5. selector:
    6. matchLabels:
    7. app.oam.dev/component: mytest
    8. template:
    9. metadata:
    10. labels:
    11. app.oam.dev/component: mytest
    12. spec:
    13. containers:
    14. - name: mytest
    15. image: nginx:v1

    以上,你就得到了一个 Kubernetes Deployment 类型的模板。

    CUE 的更多用法

    • 设计开放的结构体和数组。如果在数组或者结构体中使用 ...,则说明该对象为开放的。

      • 数组对象 [...string] ,说明该对象可以容纳多个字符串元素。 如果不添加 ..., 该对象 [string] 说明数组只能容纳一个类型为 string 的元素。
      • 如下所示的结构体说明可以包含未知字段。

        1. {
        2. abc: string
        3. ...
        4. }
    • 使用运算符 | 来表示两种类型的值。如下所示,变量 a 表示类型可以是字符串或者整数类型。

    1. a: string | int
    • 使用符号 * 定义变量的默认值。通常它与符号 | 配合使用, 代表某种类型的默认值。如下所示,变量 a 类型为 int,默认值为 1
    1. a: *1 | int
    • 让一些变量可被选填。 某些情况下,一些变量不一定被使用,这些变量就是可选变量,我们可以使用 ?: 定义此类变量。 如下所示, a 是可选变量, 自定义 #my 对象中 xz 为可选变量, 而 y 为必填字段。
    1. a ?: int
    2. #my: {
    3. x ?: string
    4. y : int
    5. z ?:float
    6. }

    选填变量可以被跳过,这经常和条件判断逻辑一起使用。 具体来说,如果某些字段不存在,则 CUE 语法为 if _variable_!= _ | _ ,如下所示:

    1. parameter: {
    2. name: string
    3. image: string
    4. config?: [...#Config]
    5. }
    6. output: {
    7. ...
    8. spec: {
    9. containers: [{
    10. name: parameter.name
    11. image: parameter.image
    12. if parameter.config != _|_ {
    13. config: parameter.config
    14. }
    15. }]
    16. }
    17. ...
    18. }
    • 使用运算符 & 来运算两个变量。
    1. a: *1 | int
    2. b: 3
    3. c: a & b

    你可以使用 cue eval 来验证结果:

    • 需要执行条件判断。当你执行一些级联操作时,不同的值会影响不同的结果,条件判断就非常有用。 因此,你可以在模版中执行 if..else 的逻辑。
    1. feel: *"good" | string
    2. // Feel bad if price is too high
    3. if price > 100 {
    4. feel: "bad"
    5. }

    保存上述内容到 fourth.cue 文件。

    你可以使用 cue eval 来验证结果:

    1. $ cue eval fourth.cue
    2. price: 200
    3. feel: "bad"

    另一个示例是将布尔类型作为参数。

    1. parameter: {
    2. name: string
    3. image: string
    4. useENV: bool
    5. }
    6. output: {
    7. ...
    8. spec: {
    9. containers: [{
    10. name: parameter.name
    11. image: parameter.image
    12. if parameter.useENV == true {
    13. env: [{name: "my-env", value: "my-value"}]
    14. }
    15. }]
    16. }
    17. ...
    18. }
    • 使用 For 循环。 我们为了避免减少重复代码,常常使用 For 循环。

      • 映射遍历。

        1. parameter: {
        2. name: string
        3. image: string
        4. env: [string]: string
        5. }
        6. output: {
        7. spec: {
        8. containers: [{
        9. name: parameter.name
        10. image: parameter.image
        11. env: [
        12. for k, v in parameter.env {
        13. name: k
        14. value: v
        15. },
        16. ]
        17. }]
        18. }
        19. }
      • 类型遍历。

        1. #a: {
        2. "hello": "Barcelona"
        3. "nihao": "Shanghai"
        4. }
        5. for k, v in #a {
        6. "\(k)": {
        7. nameLen: len(v)
        8. value: v
        9. }
        10. }
      • 切片遍历。

        1. parameter: {
        2. name: string
        3. image: string
        4. env: [...{name:string,value:string}]
        5. }
        6. output: {
        7. ...
        8. spec: {
        9. containers: [{
        10. name: parameter.name
        11. image: parameter.image
        12. env: [
        13. for _, v in parameter.env {
        14. name: v.name
        15. value: v.value
        16. },
        17. ]
        18. }]
        19. }
        20. }
        • 循环内使用条件判断
        1. parameter: [
        2. {
        3. name: "empty"
        4. }, {
        5. name: "xx1"
        6. },
        7. ]
        8. dataFrom: [ for _, v in parameter {
        9. if v.name != "empty" {
        10. name: v.name
        11. }
        12. }]

        结果是:

        1. cue eval ../blog/a.cue -e dataFrom
        2. [{}, {
        3. name: "xx1"
        4. }]
        • 将条件判断作为循环的条件
        1. parameter: [
        2. {
        3. name: "empty"
        4. }, {
        5. name: "xx1"
        6. },
        7. ]

      dataFrom: [ for _, v in parameter if v.name != “empty” { name: v.name }]

      1. 结果是:

      cue eval ../blog/a.cue -e dataFrom [{ name: “xx1” }]

    另外,可以使用 进行字符串内部计算,比如上面类型循环示例中,获取值的长度等等操作。

    CUE 有很多 internal packages 可以被 KubeVela 使用,这样可以满足更多的开发需求。

    比如,使用 strings.Join 方法将字符串数组拼接成字符串。

    至此,你已经学会了基础的 CUE 知识,如果你还想了解更多的 CUE 实践细节,可以参考其。

    在本部分接下来的章节里,我们会开始介绍 KubeVela 如何使用 CUE 像胶水一样衔接不同的资源,请确保你对 KubeVela 的核心概念有所了解。

    下一步