Kubernetes 中的通用表达式语言

The Common Expression Language (CEL) is used in the Kubernetes API to declare validation rules, policy rules, and other constraints or conditions.

CEL 表达式在 API 服务器 中直接执行,这使得 CEL 成为许多可扩展性用例中对进程外机制(如 Webhook)的便捷替代方案。只要控制平面的 API 服务器组件可用,您的 CEL 表达式就会继续执行。

语言概述

The CEL language has a straightforward syntax that is similar to the expressions in C, C++, Java, JavaScript and Go.

CEL 被设计为嵌入应用程序。每个 CEL "程序" 都是一个单一表达式,它计算出一个单一值。CEL 表达式通常是简短的“单行代码”,可以很好地内联到 Kubernetes API 资源的字符串字段中。

CEL 程序的输入是“变量”。每个包含 CEL 的 Kubernetes API 字段都在 API 文档中声明了哪些变量可用于该字段。例如,在 CustomResourceDefinitions 的 x-kubernetes-validations[i].rules 字段中,selfoldSelf 变量可用,并分别引用 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 或字符串字段的 int 和字符串情况
self.metadata.name == 'singleton'验证对象的名称是否与特定值匹配(使其成为单例)
self.set1.all(e, !(e in self.set2))验证两个 listSets 是否不相交
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 版本
扩展字符串库,版本 1charAt, 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 CrossTypeNumericComparisons参见 CEL CrossTypeNumericComparisonsKubernetes 版本 1.29+

CEL 函数、功能和语言设置支持 Kubernetes 控制平面回滚。例如,CEL 可选值 在 Kubernetes 1.29 中引入,因此只有该版本或更高版本的 API 服务器才会接受对使用CEL 可选值的 CEL 表达式的写入请求。但是,当集群回滚到 Kubernetes 1.28 时,已存储在 API 资源中的使用“CEL 可选值”的 CEL 表达式将继续正确计算。

Kubernetes CEL 库

除了 CEL 社区库之外,Kubernetes 还包含 CEL 库,这些库在 Kubernetes 中使用 CEL 的任何地方都可用。

Kubernetes 列表库

列表库包括 indexOflastIndexOf,它们的工作原理类似于同名字符串函数。这些函数返回列表中提供的元素的第一个或最后一个位置索引。

列表库还包括 minmaxsum。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 函数之外,正则表达式库还提供 findfindAll,从而能够进行更广泛的正则表达式操作。

示例

使用正则表达式库函数的 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 中的 CEL 表达式,如果存在类型为 Authorizer 的变量,则可以使用授权器来执行对请求的委托人(经过身份验证的用户)的授权检查。

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

  1. 指定要检查的组和资源:Authorizer.group(string).resource(string) ResourceCheck
  2. 可以选择调用以下任何组合的构建器函数以进一步缩小授权检查范围。请注意,这些函数返回接收器类型,可以进行链接
  • ResourceCheck.subresource(string) ResourceCheck
  • ResourceCheck.namespace(string) ResourceCheck
  • ResourceCheck.name(string) ResourceCheck
  1. 调用 ResourceCheck.check(verb string) Decision 执行授权检查。
  2. 调用 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()如果委托人(用户或服务帐户)被允许在“default”命名空间中创建 Pod,则返回 true。
authorizer.path('/healthz').check('get').allowed()检查委托人(用户或服务帐户)是否有权对 /healthz API 路径发出 HTTP GET 请求。
authorizer.serviceAccount('default', 'myserviceaccount').resource('deployments').check('delete').allowed()检查服务帐户是否有权删除部署。

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

Kubernetes 数量库

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

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

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

Quantity 的可用成员函数
成员函数CEL 返回值描述
isInteger()bool当且仅当 asInteger 可以安全调用而不会出错时返回 true
asInteger()int如果可能,返回当前值作为 int64 的表示,如果转换会导致溢出或精度丢失,则会导致错误。
asApproximateFloat()float返回数量的 float64 表示,这可能会导致精度丢失。如果数量的值超出 float64 的范围,则将返回 +Inf/-Inf。
sign()int如果数量为正,则返回 1,如果数量为负,则返回 -1。如果数量为零,则返回 0
add(<Quantity>)数量返回两个数量的总和
add(<int>)数量返回数量和整数的总和
sub(<Quantity>)数量返回两个数量之间的差
sub(<int>)数量返回数量和整数之间的差
isLessThan(<Quantity>)bool当且仅当接收者小于操作数时返回 true
isGreaterThan(<Quantity>)bool当且仅当接收者大于操作数时返回 true
compareTo(<Quantity>)int将接收者与操作数进行比较,如果它们相等,则返回 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 表达式。例如,自定义资源定义验证规则 是完全类型检查的。

一些 Kubernetes API 字段包含部分类型检查的 CEL 表达式。部分类型检查的表达式是指一些变量是静态类型化的,而另一些变量是动态类型化的表达式。例如,在 验证准入策略 的 CEL 表达式中,request 变量是类型化的,但 object 变量是动态类型化的。因此,包含 request.namex 的表达式将无法通过类型检查,因为 namex 字段未定义。但是,object.namex 将通过类型检查,即使 namex 字段未为 object 引用的资源类型定义,因为 object 是动态类型化的。

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

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

类型系统集成

表格显示 OpenAPIv3 类型与 CEL 类型之间的关系
OpenAPIv3 类型CEL 类型
'object' with Propertiesobject / "message type" (type(<object>) evaluates to selfType<uniqueNumber>.path.to.object.from.self
'object' with AdditionalPropertiesmap
'object' with x-kubernetes-embedded-typeobject / "message type", 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are implicitly included in schema
'object' with x-kubernetes-preserve-unknown-fieldsobject / "message type", unknown fields are NOT accessible in CEL expression
x-kubernetes-int-or-stringunion of int or string, self.intOrString < 100 || self.intOrString == '50%' evaluates to true for both 50 and "50%"
'arraylist
'array' with x-kubernetes-list-type=maplist with map based Equality & unique key guarantees
'array' with x-kubernetes-list-type=setlist with set based Equality & unique entry guarantees
'boolean'boolean
'number' (all formats)double
'integer' (all formats)int (64)
no equivalentuint (64)
'null'null_type
'string'string
'string' with format=byte (base64 encoded)bytes
'string' with format=datetimestamp (google.protobuf.Timestamp)
'string' with format=datetimetimestamp (google.protobuf.Timestamp)
'string' with format=durationduration (google.protobuf.Duration)

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

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

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

  • set: X + Y 执行一个联合,其中 X 中所有元素的数组位置都保留,Y 中的非相交元素被追加,保留其部分顺序。
  • map: X + Y 执行一个合并,其中 X 中所有键的数组位置都保留,但当 XY 的键集相交时,值会被 Y 中的值覆盖。Y 中具有非相交键的元素被追加,保留其部分顺序。

转义

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

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

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

转义示例

转义的 CEL 标识符示例
属性名称使用转义属性名称的规则
namespaceself.__namespace__ > 0
x-propself.x__dash__prop > 0
redact__dself.redact__underscores__d > 0
stringself.startsWith('kube')

资源约束

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

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

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

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

运行时成本预算

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

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

估计成本限制

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

上次修改时间:2024 年 6 月 6 日下午 4:36 PST:修复 cel.md 中不匹配的括号 (b91d36dfed)