CUE入门笔记
分类: k8s 发布于: 2025-05-14

CUE 初步教程

CUE是什么?

CUE(Configure, Unify, Execute)是一种声明式配置语言,由 Google 开发,专注于数据验证、模板生成和配置管理。它的核心设计目标是:

  • 强类型约束:比 JSON/YAML 更严格的类型系统,支持复杂条件校验
  • 数据合并(Unification):类似“继承”或“混入”,可安全合并多份配置
  • 多格式输出:生成 JSON、YAML、INI 等格式,适配不同系统需求

有许多类似CUE的项目,或专注于配置生成,或专注模板构建,例如python的jinja2、Pydantic,OpenAPI Validate(基于JSON Schema),但CUE在此领域集成诸多优势脱颖而出,表现在:

  1. 强类型 + 约束组合

CUE 支持复杂类型和复合数据解构。而OpenAPI Validate基于JSON Schema,类型有限;Pydantic支持丰富类型,但其基于Python注解,无cli工具等配套生态,与其他语言(如golang)难以结合。

  1. 数据合并

CUE原生支持多&操作符来合并多源数据,类似Class的多重继承,对于复杂配置项的整合十分方便。而JSON Schema不支持合并,Pydantic可以通过update操作进行合并,但需手动声明维护,且无冲突检测机制。

  1. 配置生成

原生支持YAML/JSON,通过社区工具cue2ini等可以转换成ini,或者其他配置语言。JSON Schema仅作为校验、不生成配置。Pydantic可以方便的生成静态配置,但难以实现条件化模板(如根据环境变量动态生成字段),需要借助额外的Python逻辑来辅助实现。

对于jinja2,其仅作为一个模板语言实现字符串替换,对于校验、数据合并等,完全依赖Python实现。总的来说,CUE在YAML配置自动生成方面,具有诸多的核心优势使我们不得不考虑使用,下面是简单的使用示例:

入门

1. 安装

CUE有一个CLI命令行工具,mac执行 brew install cue-lang/tap/cue可自动安装,执行cue version校验安装是否成功:

  cue version
cue version v0.12.1

go version go1.24.2
      -buildmode exe
       -compiler gc
       -trimpath true
     CGO_ENABLED 0
          GOARCH arm64
            GOOS darwin
         GOARM64 v8.0
cue.lang.version v0.12.0

编写一个多环境配置的example.cue模板文件:

// 基础配置
Base: {
    env:     "dev" | "prod"
    apiUrl:  string
    timeout: int | *5000
}

// 环境覆盖
Prod: Base & {
    env:     "prod"
    apiUrl:  "https://api.example.com"
    timeout: 10000
}

// 输出最终配置
output: Prod

使用cue CLI进行渲染,可以看到已经输出YAML:

 cue export example.cue -e output --out yaml
env: prod
apiUrl: https://api.example.com
timeout: 10000

2. 基础语法

基本类型

// 字符串
name: "Alice"

// 数字(整数/浮点数)
age:  30
price: 9.99

// 布尔值
enabled: true

// 空值
nothing: null

复合类型

// 结构体
user: {
    name: string  // 类型约束
    age:  int
    email?: string  // 可选字段
}
// 列表
ports: [80, 443, 8080]
users: [{name: "Alice"}, {name: "Bob"}]

高级特性

// 类型约束
#Config: {
    timeout: int & >=1 & <=10  // 1~10的整数
    url:     string & =~"^https://"  // 必须https开头
}

// 默认值
settings: {
    port: int | *8080  // 默认8080
    env:  "dev" | "prod" | *"dev"
}

// 条件逻辑
config: {
    env: "dev" | "prod"
    if env == "prod" {
        replicas: 5
    } else {
        replicas: 1
    }
}

循环逻辑

parameter: {
    name:  string
    image: string
    env: [string]: string
}
output: {
    spec: {
        containers: [{
            name:  parameter.name
            image: parameter.image
            env: [
                for k, v in parameter.env {
                    name:  k
                    value: v
                },
            ]
        }]
    }
}

// 类型遍历
#a: {
    "hello": "Barcelona"
    "nihao": "Shanghai"
}

for k, v in #a {
    "\(k)": {
        nameLen: len(v)
        value:   v
    }
}

// 切片遍历
parameter: {
    env: [...{name:string,value:string}]
}
output: {
  env: [
    for _, v in parameter.env {
      name:  v.name
      value: v.value
    }
  ]
}

循环与条件判断的结合

parameter: [
{
  name: "empty"
}, {
  name: "xx1"
},
]

// 循环内使用条件判断
dataFrom: [ for _, v in parameter {
if v.name != "empty" {
  name: v.name
}
}]

/* 输出:
[{}, {
  name: "xx1"
}]
*/

// 将条件判断作为循环的条件
dataFrom: [ for _, v in parameter if v.name != "empty" {
  name: v.name
}]

/* 输出:
[{
  name: "xx1"
}]
*/

数据合并与冲突

// merge
base: { timeout: 30, log: "info" }
prod: { log: "error" }

result: base & prod  // {timeout:30, log:"error"}

// 冲突
a: { port: 8080 }
b: { port: 80 }

combined: a & b  // 错误:port冲突

模板化与复用

// 模板定义
#Deployment: {
    apiVersion: "apps/v1"
    kind:       "Deployment"
    spec: {
        replicas: int & >=1
        template: {
            spec: containers: [{
                name:  string
                image: string
            }]
        }
    }
}

// 实例化
myApp: #Deployment & {
    metadata: name: "web"
    spec: {
        replicas: 3
        template: {
            spec: containers: [{
                name:  "nginx"
                image: "nginx:latest"
            }]
        }
    }
}

校验与输出:

// 校验
cue vet config.cue  # 检查配置合法性

// 渲染输出
cue export config.cue -e myApp --out yaml > deploy.yaml

3. 关键概念

: 值为具体的数据实例,如:

name: "Alice"     // 字符串值
port: 8080        // 整数值
enabled: true     // 布尔值

// 默认值:用 | * 指定
port: int | *8080  // 未指定时默认为8080

// 可选字段使用?
user: {
    name: string
    email?: string  // 可省略
}

约束: 对值的限定规则,如:

// 类型约束
age: int          // 必须为整数
// 范围约束
timeout: >0 & <10 // 必须大于0且小于10
// 正则约束
email: =~"^\\w+@\\w+\\.\\w+$"

合并:合并策略中,包含 &(Unification)、 | (Disjunction)等.

使用"&"会要求数据同时满足两侧的所有约束,两侧字段必须兼容,即类型相同或可统一。以下是"&"的行为:

  • 具体值 vs. 默认值:具体值优先(表现像覆盖)
  • 具体值 vs. 具体值:必须相同,否则报错
  • 约束 vs. 约束:叠加约束(如 >0 & <10,叠加后必须都满足,否则报错)
  • 约束 vs. 具体值:具体值必须满足约束,此时将会以此值进行实例化,否则报错

默认值被覆盖的一个典型情况:

base: {
    port: int | *8080  // 默认值 8080
    env:  string | *"dev"
}

prod: {
    port: 80          // 具体值(非默认值)
    env:  "prod"      // 具体值
}

config: base & prod   // 结果: {port: 80, env: "prod"}

"|"则表示“或”关系:值必须满足其中任意一个分支的约束,但CUE是逻辑自洽的,当发生多个值冲突时,也会报错,如:

port: 8080 | 80  // 错误:两个具体值冲突,无法自动选择

// 上述语句单独写,CUE无法确认到底应该选择哪个值。
// 若将8080与80看做合法的枚举值,则可以将上述语句嵌入结构体或模板中,并在上下文中提供实例化的数值,如:

#Server: {
    // 约束端口为 80 或 8080,默认8080
    port: 80 | 8080 | *8080
    // 根据端口自动生成协议
    protocol: if port == 80 { "http" } else { "http-alt" }
}

// 实例化
web: #Server & { port: 80 }     // 结果: {port:80, protocol:"http"}
api: #Server & { port: 8080 }   // 结果: {port:8080, protocol:"http-alt"}

它适用于下述情况:

// 具体值 vs 默认值
port: 8080 | *80  // 若用户未指定,port=80;若用户指定8080,则port=8080

// 约束 vs 默认值
timeout: int & >0 | *30  // 用户未指定时=30;若指定则必须为正整数

// 纯约束的组合
env: "dev" | "prod"  // 只能是 "dev" 或 "prod",这里的"dev"、"prod"同样为可选择的枚举值
age: int | float     // 可以是任意整数或浮点数

模式匹配 ...(Ellipsis): 这个操作符适用于两种情况:

  • 开放结构体:允许未显式定义的额外字段
  • 开放列表:允许列表扩展
// 允许任意额外字段(需符合类型约束)
#OpenSchema: {
    name: string
    ...  // 允许其他字段
    [_:string]: int | string  // 额外字段只能是int或string
}

// 允许列表扩展
numbers: [1, 2, ...]  // 表示以1,2开头的任意长度列表

4. 注入实践

针对一个动态配置系统,模拟前端用户提交表单,后端生成yaml:

// template.cue
FlatInput: {
    user_name:  string | *"Any"
    item_count: int | *0
    priority:   string | *"normal"
}

output: {
    person: {
        name:    FlatInput.user_name
        details: {
            count: FlatInput.item_count
            config: {
                priority: FlatInput.priority
            }
        }
    }
}

用户输入:

// data.json
{"FlatInput": {
    "user_name": "Alice",
    "item_count": 3,
    "priority": "high"
}}

执行命令cue export data.json -e output template.cue --out yaml 输出渲染结果:

person:
  name: Alice
  details:
    count: 3
    config:
      priority: high