CUE 入门
本章节将详细介绍关于如何使用 CUE 封装和抽象 Kubernetes 中已有的能力。
KubeVela 将 CUE 作为抽象最优方案的主要原因如下:
- CUE 本身就是为大规模配置而设计。 CUE 能够感知非常复杂的配置文件,并且能够安全地更改可修改配置中成千上万个对象的值。这非常符合 KubeVela 的最初目标,即以 web-scale 方式定义和交付生产级别的应用程序(web-scale,是一种软件设计方法,主要包含可扩展性、一致性、容忍度和版本控制等)。
- CUE 支持一流的代码生成和自动化。 CUE 原生支持与现有工具以及工作流进行集成,反观其他工具则需要自定义复杂的方案才能实现。例如,需要手动使用 Go 代码生成 OpenAPI 模式。KubeVela 也是依赖 CUE 该特性进行构建开发工具和GUI界面。
- CUE与Go完美集成。 KubeVela 像 Kubernetes 系统中的大多数项目一样使用 GO 进行开发。CUE 已经在 Go 中实现并提供了丰富的 API 。 KubeVela 以 CUE 为核心实现 Kubernetes 控制器。 借助 CUE KubeVela 可以轻松处理数据约束问题。
前提
请确保你的环境中已经安装如下命令行:
我们可以使用几乎相同的格式在同一个文件中定义模型和数据,以下为 CUE 基础数据类型:
CUE 是 JSON 的超集, 我们可以像使用 json 一样使用 CUE,同时具备以下便利性:
- C 样式的注释,
- 字段名称可以省略引号且不带特殊字符,
- 字段末尾逗号可选,
- 允许列表中最后一个元素末尾带逗号,
- 外花括号可选。
CUE 拥有强大的命令行。请将数据保存到 first.cue
文件并尝试使用命令行。
格式化 CUE 文件。如果你使用 Goland 或者类似 JetBrains IDE, 可以参考该文章配置自动格式化插件 。 该命令不仅可以格式化 CUE 文件,还能指出错误的模型,相当好用的命令。
cue fmt first.cue
模型校验。 除了
cue fmt
,你还可以使用vue vet
来校验模型.cue vet first.cue
计算/渲染结果。
cue eval
可以计算 CUE 文件并且渲染出最终结果。 我们看到最终结果中并不包含a: float
和b: int
,这是因为这两个变量已经被计算填充。 其中e: string
没有被明确的赋值, 故保持不变.$ cue eval first.cue
a: 1.5
b: 1
d: [1, 2, 3]
g: {
h: "abc"
}
e: string
渲染指定结果。例如,我们仅想知道文件中
b
的渲染结果,则可以使用该参数-e
。$ cue eval -e b first.cue
1
导出渲染结果。
cue export
可以导出最终渲染结果。如果一些变量没有被定义执行该命令将会报错。$ cue export first.cue
e: cannot convert incomplete value "string" to JSON:
./first.cue:9:4
我们可以通过给
e
赋值来完成赋值,例如:echo "e: \"abc\"" >> first.cue
然后,该命令就可以正常工作。默认情况下, 渲染结果会被格式化为 json 格式。
$ cue export first.cue
{
"a": 1.5,
"b": 1,
"d": [
1,
2,
3
],
"g": {
"h": "abc"
},
"e": "abc"
}
-
$ cue export first.cue --out yaml
a: 1.5
b: 1
d:
- 1
- 2
- 3
g:
h: abc
e: abc
导出指定变量的结果。
$ cue export -e g first.cue
{
"h": "abc"
}
至此, 你已经学习完所有常用 CUE 命令行参数。
CUE 语言基础
- 数据类型: 以下为 CUE 的基础数据类型。
// float
a: 1.5
// int
b: 1
// string
c: "blahblahblah"
// array
d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
// bool
e: true
// struct
f: {
a: 1.5
b: 1
d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
g: {
h: "abc"
}
}
// null
j: null
- 自定义 CUE 类型。你可以使用
#
符号来指定一些表示 CUE 类型的变量。
我们将上述内容保存到 second.cue
文件。 执行 cue export
不会报 #abc
是一个类型不完整的值。
$ cue export second.cue
你还可以定义更复杂的自定义结构,比如:
#abc: {
x: int
y: string
z: {
a: float
b: bool
}
}
自定义结构在 KubeVela 中被广泛用于定义模板和进行验证。
我们开始尝试利用刚刚学习知识来定义 CUE 模版。
- 定义结构体变量
parameter
.
parameter: {
name: string
image: string
}
保存上述变量到文件 .
- 定义更复杂的结构变量
template
同时引用变量parameter
.
template: {
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
}]
}}}
}
熟悉 Kubernetes 的人可能已经知道,这是 Kubernetes Deployment 的模板。 parameter
为模版的参数部分。
添加上述内容到文件 deployment.cue
.
- 随后, 我们通过添加以下内容来完成变量赋值:
parameter:{
name: "mytest"
image: "nginx:v1"
}
- 最后, 导出渲染结果为 yaml 格式:
$ cue export deployment.cue -e template --out yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: mytest
image: nginx:v1
metadata:
labels:
app.oam.dev/component: mytest
selector:
matchLabels:
app.oam.dev/component: mytest
高级 CUE 设计
开放的结构体和列表。在列表或者结构体中使用
...
说明该对象为开放的。- 列表对象
[...string]
,说明该对象可以容纳多个字符串元素。 如果不添加...
, 该对象[string]
说明列表只能容纳一个类型为string
的元素。 如下所示的结构体说明可以包含未知字段。
{
abc: string
...
}
- 列表对象
运算符
|
, 它可以表示两种类型的值。如下所示,变量a
表示类型可以是字符串或者整数类型。
a: string | int
- 默认值, 我们可以使用符号
*
定义变量的默认值。通常与符号|
配合使用, 代表某种类型的默认值。如下所示,变量a
类型为int
,默认值为1
。
a: *1 | int
- 选填变量。 某些情况下,一些变量不一定被使用,这些变量就是可选变量,我们可以使用
?:
定义此类变量。 如下所示,a
是可选变量, 自定义#my
对象中x
和z
为可选变量, 而y
为必填字段。
a ?: int
#my: {
x ?: string
y : int
z ?:float
}
选填变量可以被跳过,这经常和条件判断逻辑一起使用。 具体来说,如果某些字段不存在,则 CUE 语法为 if _variable_!= _ | _
,如下所示:
- 运算符
&
,该运算符用来运算两个变量。
a: *1 | int
b: 3
c: a & b
保存上述内容到 third.cue
文件。
你可以使用 cue eval
来验证结果:
$ cue eval third.cue
a: 1
b: 3
c: 3
- 条件判断。 当你执行一些级联操作时,不同的值会影响不同的结果,条件判断就非常有用。 因此,你可以在模版中执行
if..else
的逻辑。
price: number
feel: *"good" | string
// Feel bad if price is too high
if price > 100 {
feel: "bad"
}
price: 200
你可以使用 cue eval
来验证结果:
$ cue eval fourth.cue
price: 200
feel: "bad"
另一个示例是将布尔类型作为参数。
parameter: {
name: string
image: string
useENV: bool
}
output: {
...
spec: {
containers: [{
name: parameter.name
image: parameter.image
env: [{name: "my-env", value: "my-value"}]
}]
}
...
}
For循环。 我们为了避免重复可以使用 for 循环。
for k, v in #a {
"\(k)": {
nameLen: len(v)
value: v
}
}
```
- 切片循环
```
parameter: {
name: string
image: string
env: [...{name:string,value:string}]
}
output: {
...
spec: {
containers: [{
name: parameter.name
image: parameter.image
env: [
for _, v in parameter.env {
name: v.name
value: v.value
},
]
}]
}
}
```
备注, 可以使用 "\( _my-statement_ )"
进行字符串内部计算,比如上面类型循环示例中,获取值的长度等等操作。
CUE 有很多 可以被 KubeVela 使用。
如下所示,使用 strings.Join
方法将字符串列表拼接成字符串。
import ("strings")
parameter: {
outputs: [{ip: "1.1.1.1", hostname: "xxx.com"}, {ip: "2.2.2.2", hostname: "yyy.com"}]
}
output: {
spec: {
if len(parameter.outputs) > 0 {
_x: [ for _, v in parameter.outputs {
"\(v.ip) \(v.hostname)"
}]
message: "Visiting URL: " + strings.Join(_x, "")
}
}
}
导入 Kubernetes 包
KubeVela 会从 Kubernetes 集群中读取 OpenApi,并将 Kubernetes 所有资源自动构建为内部包。
你可以在 KubeVela 的 CUE 模版中通过 kube/<apiVersion>
导入这些包,就像使用 CUE 内部包一样。
比如,Deployment
可以这样使用:
import (
apps "kube/apps/v1"
)
parameter: {
name: string
}
output: apps.#Deployment
output: {
metadata: name: parameter.name
}
Service
可以这样使用(无需使用别名导入软件包):
甚至已经安装的 CRD 也可以正常使用:
import (
oam "kube/core.oam.dev/v1alpha2"
)
output: oam.#Application
output: {
metadata: {
"name": parameter.name
}
}
parameter: {