Kubernetes 中的通用表达式语言
Common Expression Language (CEL) 用于 Kubernetes API 中,用于声明验证规则、策略规则以及其他约束或条件。
CEL 表达式直接在 API 服务器中评估,使得 CEL 成为许多可扩展性用例中进程外机制(例如 webhooks)的便捷替代方案。只要控制平面的 API 服务器组件可用,您的 CEL 表达式就会继续执行。
语言概览
CEL 语言语法直观,类似于 C、C++、Java、JavaScript 和 Go 中的表达式。
CEL 设计用于嵌入到应用中。每个 CEL“程序”都是一个单一表达式,其评估结果为一个单一值。CEL 表达式通常是短小的“一行代码”,可以很好地内联到 Kubernetes API 资源的字符串字段中。
CEL 程序的输入是“变量”。包含 CEL 的每个 Kubernetes API 字段在其 API 文档中声明了该字段可用哪些变量。例如,在 CustomResourceDefinitions 的 x-kubernetes-validations[i].rules
字段中,self
和 oldSelf
变量可用,它们分别指向由 CEL 表达式验证的自定义资源数据的先前状态和当前状态。其他 Kubernetes API 字段可能会声明不同的变量。请查阅 API 字段的 API 文档,了解该字段可用哪些变量。
CEL 表达式示例
规则 | 用途 |
---|---|
self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas | 验证定义副本数的三个字段顺序正确 |
'Available' in self.stateCounts | 验证 map 中是否存在键为 'Available' 的条目 |
(self.list1.size() == 0) != (self.list2.size() == 0) | 验证两个 list 中恰好有一个非空 |
self.envars.filter(e, e.name = 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$')) | 验证 listMap 中键字段为 'name' 且值为 'MY_ENV' 的条目的 'value' 字段 |
has(self.expired) && self.created + self.ttl < self.expired | 验证 'expired' 日期在 'create' 日期加上 'ttl' 时长之后 |
self.health.startsWith('ok') | 验证 'health' 字符串字段具有前缀 'ok' |
self.widgets.exists(w, w.key == 'x' && w.foo < 10) | 验证 listMap 中键为 'x' 的条目的 'foo' 属性小于 10 |
type(self) == string ? self == '99%' : self == 42 | 验证 int-or-string 字段的 int 和 string 两种情况 |
self.metadata.name == 'singleton' | 验证对象的名称是否匹配特定值(使其成为单例) |
self.set1.all(e, !(e in self.set2)) | 验证两个 listSet 是不相交的 |
self.names.size() == self.details.size() && self.names.all(n, n in self.details) | 验证 'details' map 的键是 'names' listSet 中的条目 |
self.details.all(key, key.matches('^[a-zA-Z]*$')) | 验证 'details' map 的键 |
self.details.all(key, self.details[key].matches('^[a-zA-Z]*$')) | 验证 'details' map 的值 |
CEL 选项、语言特性和库
CEL 使用以下选项、库和语言特性进行配置,它们在指定的 Kubernetes 版本中引入
CEL 选项、库或语言特性 | 包含的函数/宏 | 可用性 |
---|---|---|
标准宏 | has 、all 、exists 、exists_one 、map 、filter | 所有 Kubernetes 版本 |
标准函数 | 请参阅标准定义官方列表 | 所有 Kubernetes 版本 |
同质聚合字面量 | 所有 Kubernetes 版本 | |
默认 UTC 时区 | 所有 Kubernetes 版本 | |
立即验证声明 | 所有 Kubernetes 版本 | |
扩展字符串库,版本 1 | charAt 、indexOf 、lastIndexOf 、lowerAscii 、upperAscii 、replace 、split 、join 、substring 、trim | 所有 Kubernetes 版本 |
Kubernetes List 库 | 请参阅Kubernetes List 库 | 所有 Kubernetes 版本 |
Kubernetes Regex (正则表达式) 库 | 请参阅Kubernetes Regex (正则表达式) 库 | 所有 Kubernetes 版本 |
Kubernetes URL 库 | 请参阅Kubernetes URL 库 | 所有 Kubernetes 版本 |
Kubernetes Authorizer (授权器) 库 | 请参阅Kubernetes Authorizer (授权器) 库 | 所有 Kubernetes 版本 |
Kubernetes Quantity (数量) 库 | 请参阅Kubernetes Quantity (数量) 库 | Kubernetes 1.29 及以上版本 |
CEL 可选类型 | 请参阅CEL 可选类型 | Kubernetes 1.29 及以上版本 |
CEL 跨类型数字比较 | 请参阅CEL 跨类型数字比较 | Kubernetes 1.29 及以上版本 |
CEL 函数、特性和语言设置支持 Kubernetes 控制平面回滚。例如,CEL 可选值在 Kubernetes 1.29 中引入,因此只有该版本或更新版本的 API 服务器才会接受使用 CEL 可选值的 CEL 表达式的写入请求。然而,当集群回滚到 Kubernetes 1.28 时,已存储在 API 资源中的、使用“CEL 可选值”的 CEL 表达式将继续正确评估。
Kubernetes CEL 库
除了 CEL 社区库之外,Kubernetes 还包含在 Kubernetes 中任何使用 CEL 的地方都可用的 CEL 库。
Kubernetes List 库
list 库包含 indexOf
和 lastIndexOf
,它们的工作方式类似于同名的字符串函数。这些函数返回提供的元素在 list 中首次或最后一次出现的位置索引。
list 库还包含 min
、max
和 sum
。sum
支持所有数字类型和 duration 类型。min
和 max
支持所有可比较类型。
isSorted
也作为便利函数提供,并支持所有可比较类型。
示例
CEL 表达式 | 用途 |
---|---|
names.isSorted() | 验证姓名列表按字母顺序排列 |
items.map(x, x.weight).sum() == 1.0 | 验证对象列表的“权重”总和为 1.0 |
lowPriorities.map(x, x.priority).max() < highPriorities.map(x, x.priority).min() | 验证两组优先级不重叠 |
names.indexOf('should-be-first') == 1 | 要求列表中的第一个姓名是特定值 |
请参阅Kubernetes List Library godoc 以获取更多信息。
Kubernetes Regex (正则表达式) 库
除了 CEL 标准库提供的 matches
函数外,regex 库还提供了 find
和 findAll
,从而支持更广泛的正则表达式操作。
示例
CEL 表达式 | 用途 |
---|---|
"abc 123".find('[0-9]+') | 查找字符串中的第一个数字 |
"1, 2, 3, 4".findAll('[0-9]+').map(x, int(x)).sum() < 100 | 验证字符串中的数字总和小于 100 |
请参阅Kubernetes regex 库 godoc 以获取更多信息。
Kubernetes URL 库
为了更容易、更安全地处理 URL,添加了以下函数
isURL(string)
根据 Go 的 net/url 包检查字符串是否是有效的 URL。字符串必须是绝对 URL。url(string) URL
将字符串转换为 URL,如果字符串不是有效的 URL,则返回错误。
一旦通过 url
函数解析,生成的 URL 对象具有 getScheme
、getHost
、getHostname
、getPort
、getEscapedPath
和 getQuery
访问器。
示例
CEL 表达式 | 用途 |
---|---|
url('https://example.com:80/').getHost() | 获取 URL 的主机部分 'example.com:80' |
url('https://example.com/path with spaces/').getEscapedPath() | 返回 '/path%20with%20spaces/' |
请参阅Kubernetes URL 库 godoc 以获取更多信息。
Kubernetes Authorizer (授权器) 库
对于 API 中类型为 Authorizer
的变量可用的 CEL 表达式,可以使用 authorizer 执行请求 principal(认证用户)的授权检查。
API 资源检查按如下方式执行
- 指定要检查的 group 和 resource:
Authorizer.group(string).resource(string) ResourceCheck
- 可选地调用以下任何组合的 builder 函数以进一步缩小授权检查范围。请注意,这些函数返回接收者类型,并且可以链式调用。
ResourceCheck.subresource(string) ResourceCheck
ResourceCheck.namespace(string) ResourceCheck
ResourceCheck.name(string) ResourceCheck
- 调用
ResourceCheck.check(verb string) Decision
执行授权检查。 - 调用
allowed() bool
或reason() string
来检查授权检查的结果。
执行非资源授权按如下方式使用
- 仅指定 path:
Authorizer.path(string) PathCheck
- 调用
PathCheck.check(httpVerb string) Decision
执行授权检查。 - 调用
allowed() bool
或reason() string
来检查授权检查的结果。
执行 ServiceAccount 的授权检查
Authorizer.serviceAccount(namespace string, name string) Authorizer
CEL 表达式 | 用途 |
---|---|
authorizer.group('').resource('pods').namespace('default').check('create').allowed() | 如果 principal(用户或 ServiceAccount)被允许在 'default' 命名空间创建 pod,则返回 true。 |
authorizer.path('/healthz').check('get').allowed() | 检查 principal(用户或 ServiceAccount)是否被授权对 /healthz API path 执行 HTTP GET 请求。 |
authorizer.serviceAccount('default', 'myserviceaccount').resource('deployments').check('delete').allowed() | 检查 ServiceAccount 是否被授权删除 deployment。 |
Kubernetes v1.31 [alpha]
启用 Alpha AuthorizeWithSelectors
特性门控后,可以将字段和标签选择器添加到授权检查中。
CEL 表达式 | 用途 |
---|---|
authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed() | 如果 principal(用户或 ServiceAccount)被允许列出带有字段选择器 spec.nodeName=mynode 的 pod,则返回 true。 |
authorizer.group('').resource('pods').labelSelector('example.com/mylabel=myvalue').check('list').allowed() | 如果 principal(用户或 ServiceAccount)被允许列出带有标签选择器 example.com/mylabel=myvalue 的 pod,则返回 true。 |
请参阅 Kubernetes Authz 库和 Kubernetes AuthzSelectors 库 godoc 以获取更多信息。
Kubernetes Quantity (数量) 库
Kubernetes 1.28 添加了对操作数量字符串(例如 1.5G、512k、20Mi)的支持。
isQuantity(string)
根据 Kubernetes 的 resource.Quantity 检查字符串是否是有效的 Quantity。quantity(string) Quantity
将字符串转换为 Quantity,如果字符串不是有效的数量,则返回错误。
一旦通过 quantity
函数解析,生成的 Quantity 对象具有以下成员函数库
成员函数 | CEL 返回值 | 描述 |
---|---|---|
isInteger() | bool | 当且仅当调用 asInteger 是安全的且不会出错时,返回 true。 |
asInteger() | int | 如果可能,返回当前值表示为 int64,如果转换会导致溢出或精度丢失,则返回错误。 |
asApproximateFloat() | float | 返回 Quantity 的 float64 表示,这可能会丢失精度。如果 Quantity 的值超出 float64 的范围,将返回 +Inf/-Inf。 |
sign() | int | int |
如果 Quantity 为正,则返回 1;如果为负,则返回 -1;如果为零,则返回 0。 | Quantity | add(<Quantity>) |
Quantity | Quantity | 返回两个 Quantity 的总和 |
add(<int>) | Quantity | Quantity |
返回 Quantity 与整数的总和 | Quantity | sub(<Quantity>) |
Quantity | bool | 返回两个 Quantity 的差 |
sub(<int>) | bool | Quantity |
返回 Quantity 与整数的差 | int | isLessThan(<Quantity>) |
示例
CEL 表达式 | 用途 |
---|---|
bool | 当且仅当接收者小于操作数时,返回 true。 |
isGreaterThan(<Quantity>) | bool |
当且仅当接收者大于操作数时,返回 true。 | compareTo(<Quantity>) |
int | 将接收者与操作数比较,如果相等则返回 0,如果接收者更大则返回 1,如果接收者更小则返回 -1。 |
quantity("500000G").isInteger() | 测试转换为整数是否会抛出错误 |
quantity("50k").asInteger() | 精确转换为整数 |
quantity("9999999999999999999999999999999999999G").asApproximateFloat() | 有损转换为 float |
quantity("50k").add(quantity("20k")) | 添加两个 Quantity |
quantity("50k").sub(20000) | 从 Quantity 中减去一个整数 |
quantity("50k").add(20).sub(quantity("100k")).sub(-50000)
链式添加和减去整数和 Quantity
quantity("200M").compareTo(quantity("0.2G"))
比较两个 Quantity
quantity("150Mi").isGreaterThan(quantity("100Mi"))
has(object.namex) ? object.namex == 'special' : request.name == 'special'
测试 Quantity 是否大于接收者
测试 Quantity 是否小于接收者 | 类型检查 |
---|---|
CEL 是一种渐进式类型语言。 | 某些 Kubernetes API 字段包含完全类型检查的 CEL 表达式。例如,CustomResourceDefinitions 验证规则是完全类型检查的。 |
某些 Kubernetes API 字段包含部分类型检查的 CEL 表达式。部分类型检查的表达式是指某些变量是静态类型,而其他变量是动态类型的表达式。例如,在 ValidatingAdmissionPolicies 的 CEL 表达式中,request 变量是类型化的,而 object 变量是动态类型化的。因此,包含 request.namex 的表达式将无法通过类型检查,因为未定义 namex 字段。然而,即使未定义 object 所引用的资源种类的 namex 字段,object.namex 也会通过类型检查,因为 object 是动态类型化的。 | CEL 中的 has() 宏可用于 CEL 表达式中,以在尝试访问字段值之前检查动态类型变量的字段是否可访问。例如: |
类型系统集成 | 显示 OpenAPIv3 类型与 CEL 类型之间关系的表格 |
OpenAPIv3 类型 | CEL 类型 |
'object' 带有 Properties | object / “消息类型” (type(<object>) 评估为 selfType<uniqueNumber>.path.to.object.from.self ) |
'object' 带有 AdditionalProperties | map |
'object' 带有 x-kubernetes-embedded-type | object / “消息类型”,apiVersion 、kind 、metadata.name 和 metadata.generateName 隐式包含在 schema 中 |
'object' 带有 x-kubernetes-preserve-unknown-fields | object / “消息类型”,未知字段在 CEL 表达式中不可访问 |
x-kubernetes-int-or-string | int 或 string 的联合,self.intOrString < 100 || self.intOrString == '50%' 对于 50 和 "50%" 都评估为 true |
'array' | list |
'array' 带有 x-kubernetes-list-type=map | 具有基于 map 的相等性 和唯一键保证的 list |
'array' 带有 x-kubernetes-list-type=set | 具有基于 set 的相等性 和唯一条目保证的 list |
'boolean' | boolean |
'number' (所有格式) | double |
'integer' (所有格式) | int (64) |
无对应类型 | uint (64) |
'null' | uint (64) |
null_type | 'string' |
string
'string' 带有 format=byte (base64 编码)
bytes
'string' 带有 format=date
- timestamp (google.protobuf.Timestamp)
CEL 中的
has()
宏可用于 CEL 表达式中,以在尝试访问字段值之前检查动态类型变量的字段是否可访问。例如:- 'string' 带有 format=datetime
'string' 带有 format=duration
duration (google.protobuf.Duration)
对于 x-kubernetes-list-type 为 set 或 map 的 array,相等性比较会忽略元素顺序。例如,如果 array 表示 Kubernetes set 值,则 [1, 2] == [2, 1] 。 | 带有 x-kubernetes-list-type 的 array 的拼接使用 list 类型的语义 |
---|---|
set | __ |
| . |
map | - |
| / |
转义 | 只有形如 [a-zA-Z_.-/][a-zA-Z0-9_.-/]* 的 Kubernetes 资源属性名称可以从 CEL 访问。可访问的属性名称在表达式中访问时按照以下规则进行转义。 |
CEL 标识符转义规则表
转义序列
__underscores__ | _ 下划线 |
---|---|
__dot__ |
|
__dash__ |
|
__slash__ |
|
double | __{keyword}__ |
CEL 保留关键字
当你转义 CEL 的任何保留关键字时,需要匹配精确的属性名称并使用下划线转义(例如,单词 sprint 中的 int
不会被转义,也不需要转义)。
转义示例
转义后的 CEL 标识符示例
属性名称
带有转义属性名称的规则
namespace
self.__namespace__ > 0
x-prop
self.x__dash__prop > 0