Kubernetes 中的通用表达式语言

通用表达式语言 (CEL) 用于 Kubernetes API 中,以声明验证规则、策略规则和其他约束或条件。

CEL 表达式直接在 API 服务器 中进行评估,这使得 CEL 成为许多扩展用例中进程外机制(如 Webhook)的便捷替代方案。只要控制平面的 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 表达式示例

CEL 表达式示例及其用途
规则用途
self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas验证定义副本的三个字段是否按适当的顺序排列
'Available' in self.stateCounts验证映射中是否存在键为“Available”的条目
(self.list1.size() == 0) != (self.list2.size() == 0)验证两个列表中有一个非空,但不能同时非空
self.envars.filter(e, e.name = 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$'))验证键字段“name”为“MY_ENV”的 listMap 条目的“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)验证键为“x”的 listMap 项的“foo”属性是否小于 10
type(self) == string ? self == '99%' : self == 42针对 int 和 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”映射是否由“names”listSet 中的项作为键
self.details.all(key, key.matches('^[a-zA-Z]*$'))验证“details”映射的键
self.details.all(key, self.details[key].matches('^[a-zA-Z]*$'))验证“details”映射的值

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 列表库请参阅Kubernetes 列表库所有 Kubernetes 版本
Kubernetes 正则表达式库请参阅Kubernetes 正则表达式库所有 Kubernetes 版本
Kubernetes URL 库请参阅Kubernetes URL 库所有 Kubernetes 版本
Kubernetes 授权程序库请参阅Kubernetes 授权程序库所有 Kubernetes 版本
Kubernetes 数量库请参阅Kubernetes 数量库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 列表库

列表库包含 `indexOf` 和 `lastIndexOf`,它们的工作方式类似于同名的字符串函数。这些函数返回列表中提供的元素的第一个或最后一个位置索引。

列表库还包含 `min`、`max` 和 `sum`。`sum` 支持所有数字类型以及持续时间类型。`min` 和 `max` 支持所有可比较类型。

还提供 `isSorted` 作为便捷函数,并且支持所有可比较类型。

示例

使用列表库函数的 CEL 表达式示例
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 列表库 godoc。

Kubernetes 正则表达式库

除了 CEL 标准库提供的 `matches` 函数之外,正则表达式库还提供 `find` 和 `findAll`,从而支持更广泛的正则表达式操作。

示例

使用正则表达式库函数的 CEL 表达式示例
CEL 表达式用途
"abc 123".find('[0-9]+')查找字符串中的第一个数字
"1, 2, 3, 4".findAll('[0-9]+').map(x, int(x)).sum() < 100验证字符串中的数字之和是否小于 100

有关更多信息,请参阅 Kubernetes 正则表达式库 godoc。

Kubernetes URL 库

为了更轻松、更安全地处理 URL,添加了以下函数

  • `isURL(string)` 根据 Go 的 net/url 包检查字符串是否为有效的 URL。该字符串必须是绝对 URL。
  • `url(string) URL` 将字符串转换为 URL,如果字符串不是有效的 URL,则会导致错误。

通过 url 函数解析后,生成的 URL 对象具有 getSchemegetHostgetHostnamegetPortgetEscapedPathgetQuery 访问器。

示例

使用 URL 库函数的 CEL 表达式示例
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 授权程序库

对于 API 中存在 Authorizer 类型变量的 CEL 表达式,可以使用授权器对请求的委托人(已认证用户)执行授权检查。

API 资源检查按如下方式执行

  1. 指定要检查的组和资源:Authorizer.group(string).resource(string) ResourceCheck
  2. 可选地调用以下构建器函数的任意组合以进一步缩小授权检查范围。请注意,这些函数返回接收器类型并且可以链接
    • ResourceCheck.subresource(string) ResourceCheck
    • ResourceCheck.namespace(string) ResourceCheck
    • ResourceCheck.name(string) ResourceCheck
  3. 调用 ResourceCheck.check(verb string) Decision 执行授权检查。
  4. 调用 allowed() boolreason() string 检查授权检查的结果。

非资源授权按如下方式执行

  1. 仅指定路径:Authorizer.path(string) PathCheck
  2. 调用 PathCheck.check(httpVerb string) Decision 执行授权检查。
  3. 调用 allowed() boolreason() string 检查授权检查的结果。

要对服务帐户执行授权检查

  • Authorizer.serviceAccount(namespace string, name string) Authorizer
使用 URL 库函数的 CEL 表达式示例
CEL 表达式用途
authorizer.group('').resource('pods').namespace('default').check('create').allowed()如果允许委托人(用户或服务帐户)在“默认”命名空间中创建 Pod,则返回 true。
authorizer.path('/healthz').check('get').allowed()检查委托人(用户或服务帐户)是否有权向 /healthz API 路径发出 HTTP GET 请求。
authorizer.serviceAccount('default', 'myserviceaccount').resource('deployments').check('delete').allowed()检查服务帐户是否有权删除部署。
特性状态: Kubernetes v1.31 [alpha]

启用 alpha AuthorizeWithSelectors 特性后,可以将字段和标签选择器添加到授权检查中。

使用选择器授权函数的 CEL 表达式示例
CEL 表达式用途
authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed()如果允许委托人(用户或服务帐户)列出带有字段选择器 spec.nodeName=mynode 的 Pod,则返回 true。
authorizer.group('').resource('pods').labelSelector('example.com/mylabel=myvalue').check('list').allowed()如果允许委托人(用户或服务帐户)列出带有标签选择器 example.com/mylabel=myvalue 的 Pod,则返回 true。

有关更多信息,请参阅 Kubernetes Authz 库Kubernetes AuthzSelectors 库 godoc。

Kubernetes 数量库

Kubernetes 1.28 增加了对操作数量字符串(例如 1.5G、512k、20Mi)的支持

  • isQuantity(string) 根据 Kubernetes 的 resource.Quantity 检查字符串是否为有效的数量。
  • quantity(string) Quantity 将字符串转换为数量,如果字符串不是有效数量,则会导致错误。

通过 quantity 函数解析后,生成的 Quantity 对象具有以下成员函数库

Quantity 的可用成员函数
成员函数CEL 返回值描述
isInteger()布尔值当且仅当可以安全调用 asInteger 而不发生错误时返回 true
asInteger()整数如果可能,返回当前值作为 int64 的表示形式;如果转换会导致溢出或精度损失,则会导致错误。
asApproximateFloat()浮点数返回数量的 float64 表示形式,这可能会损失精度。如果数量的值超出 float64 的范围,则将返回 +Inf/-Inf。
sign()整数如果数量为正,则返回 1;如果数量为负,则返回 -1;如果数量为零,则返回 0
add(<Quantity>)Quantity返回两个数量的总和
add(<int>)Quantity返回数量和整数的总和
sub(<Quantity>)Quantity返回两个数量的差
sub(<int>)Quantity返回数量和整数的差
isLessThan(<Quantity>)布尔值当且仅当接收器小于操作数时返回 true
isGreaterThan(<Quantity>)布尔值当且仅当接收器大于操作数时返回 true
compareTo(<Quantity>)整数将接收器与操作数进行比较,如果它们相等,则返回 0;如果接收器更大,则返回 1;如果接收器更小,则返回 -1

示例

使用 URL 库函数的 CEL 表达式示例
CEL 表达式用途
quantity("500000G").isInteger()测试转换为整数是否会引发错误
quantity("50k").asInteger()精确转换为整数
quantity("9999999999999999999999999999999999999G").asApproximateFloat()有损转换为浮点数
quantity("50k").add(quantity("20k"))将两个数量相加
quantity("50k").sub(20000)从数量中减去一个整数
quantity("50k").add(20).sub(quantity("100k")).sub(-50000)链接加法和减法整数和数量
quantity("200M").compareTo(quantity("0.2G"))比较两个数量
quantity("150Mi").isGreaterThan(quantity("100Mi"))测试数量是否大于接收器
quantity("50M").isLessThan(quantity("100M"))测试数量是否小于接收器

类型检查

CEL 是一种渐进式类型语言

某些 Kubernetes API 字段包含完全类型检查的 CEL 表达式。例如,CustomResourceDefinitions 验证规则是完全类型检查的。

某些 Kubernetes API 字段包含部分类型检查的 CEL 表达式。部分类型检查的表达式是指某些变量是静态类型的,而其他变量是动态类型的表达式。例如,在 ValidatingAdmissionPolicies 的 CEL 表达式中,request 变量是类型的,但 object 变量是动态类型的。因此,包含 request.namex 的表达式将无法通过类型检查,因为未定义 namex 字段。但是,即使资源种类未定义 namex 字段,object.namex 也会通过类型检查,因为 object 是动态类型的。

CEL 中的 has() 宏可用于在尝试访问字段的值之前检查动态类型变量的字段是否可访问。例如

has(object.namex) ? object.namex == 'special' : request.name == 'special'

类型系统集成

显示 OpenAPIv3 类型和 CEL 类型之间关系的表格
OpenAPIv3 类型CEL 类型
带有属性的“对象”对象 / “消息类型”(type(<object>) 的计算结果为 selfType<uniqueNumber>.path.to.object.from.self
带有 AdditionalProperties 的“对象”映射
带有 x-kubernetes-embedded-type 的“对象”对象 / “消息类型”,'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 隐式包含在架构中
带有 x-kubernetes-preserve-unknown-fields 的“对象”对象 / “消息类型”,在 CEL 表达式中无法访问未知字段
x-kubernetes-int-or-stringint 或 string 的并集,self.intOrString < 100 || self.intOrString == '50%' 对于 50"50%" 都计算结果为 true
“数组”列表
带有 x-kubernetes-list-type=map 的“数组”具有基于映射的相等性和唯一键保证的列表
带有 x-kubernetes-list-type=set 的“数组”具有基于集合的相等性和唯一条目保证的列表
“布尔值”布尔值
“数字”(所有格式)双精度浮点数
“整数”(所有格式)整数 (64)
无等效项无符号整数 (64)
“空”null_type
“字符串”字符串
格式为 byte(base64 编码)的“字符串”字节
格式为 date 的“字符串”时间戳 (google.protobuf.Timestamp)
格式为 datetime 的“字符串”时间戳 (google.protobuf.Timestamp)
格式为 duration 的“字符串”持续时间 (google.protobuf.Duration)

另请参阅:CEL 类型OpenAPI 类型Kubernetes 结构化架构

x-kubernetes-list-typesetmap 的数组的相等性比较忽略元素顺序。例如,如果数组表示 Kubernetes set 值,则 [1, 2] == [2, 1]

x-kubernetes-list-type 的数组的串联使用列表类型的语义

集合
X + Y 执行并集,其中保留 X 中所有元素的数组位置,并追加 Y 中不相交的元素,保留其部分顺序。
映射
X + Y 执行合并,其中保留 X 中所有键的数组位置,但在 XY 的键集相交时,值将被 Y 中的值覆盖。追加 Y 中具有不相交键的元素,保留其部分顺序。

转义

只有格式为 [a-zA-Z_.-/][a-zA-Z0-9_.-/]* 的 Kubernetes 资源属性名称才能从 CEL 访问。在表达式中访问时,可访问的属性名称将根据以下规则进行转义

CEL 标识符转义规则表
转义序列属性名称等效项
__underscores____
__dot__.
__dash__-
__slash__/
__{keyword}__CEL **保留**关键字

当您转义 CEL 的任何**保留**关键字时,您需要匹配确切的属性名称并使用下划线转义(例如,单词 sprint 中的 int 不会被转义,也不需要转义)。

转义示例

转义的 CEL 标识符示例
属性名称带有转义属性名称的规则
namespaceself.__namespace__ > 0
x-propself.x__dash__prop > 0
redact__dself.redact__underscores__d > 0
字符串self.startsWith('kube')

资源限制

CEL 并非图灵完备的,它提供各种生产安全控制来限制执行时间。CEL 的资源限制功能向开发者提供关于表达式复杂性的反馈,并帮助保护 API 服务器在评估期间免受过度资源消耗。CEL 的资源限制功能用于防止 CEL 评估消耗过多的 API 服务器资源。

资源限制功能的一个关键要素是成本单位,CEL 将其定义为跟踪 CPU 利用率的一种方式。成本单位独立于系统负载和硬件。成本单位也是确定的;对于任何给定的 CEL 表达式和输入数据,CEL 解释器对表达式的评估将始终产生相同的成本。

许多 CEL 的核心操作都有固定成本。最简单的操作,例如比较(例如 <)的成本为 1。有些操作具有更高的固定成本,例如列表文字声明的固定基础成本为 40 个成本单位。

对以原生代码实现的函数的调用,其成本是根据操作的时间复杂度估算的。例如:使用正则表达式的操作,例如 matchfind,使用近似成本 length(regexString)*length(inputString) 进行估算。近似成本反映了 Go 的 RE2 实现的最坏情况时间复杂度。

运行时成本预算

Kubernetes 评估的所有 CEL 表达式都受到运行时成本预算的限制。运行时成本预算是实际 CPU 利用率的估计值,通过在解释 CEL 表达式时递增成本单位计数器来计算。如果 CEL 解释器执行了太多指令,则将超出运行时成本预算,表达式的执行将停止,并导致错误。

一些 Kubernetes 资源定义了额外的运行时成本预算,用于限制多个表达式的执行。如果表达式成本总和超过预算,则表达式的执行将停止,并导致错误。例如,自定义资源的验证具有每次验证运行时成本预算,用于评估所有用于验证自定义资源的验证规则

估计成本限制

对于某些 Kubernetes 资源,API 服务器还可以检查 CEL 表达式的最坏情况估计运行时间是否过高而无法执行。如果是这样,API 服务器将通过拒绝包含 CEL 表达式的创建或更新操作到 API 资源来防止将 CEL 表达式写入 API 资源。此功能提供了更强的保证,即写入 API 资源的 CEL 表达式将在运行时进行评估,而不会超出运行时成本预算。

上次修改时间:2024 年 10 月 12 日下午 11:12(太平洋标准时间):清理 using-api/cel.md (2f7dd7c114)