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 字段中,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))验证两个 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 版本
扩展字符串库,版本 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 还包含在 Kubernetes 中使用 CEL 的任何地方都可用的 CEL 库。

Kubernetes 列表库

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

列表库还包含 minmaxsum。Sum 支持所有数字类型以及持续时间类型。Min 和 max 支持所有可比较类型。

isSorted 也作为便利函数提供,并支持所有可比较类型。

示例

使用列表库函数的 CEL 表达式示例
CEL 表达式用途
names.isSorted()验证名称列表是否按字母顺序排列
items.map(x, x.weight).sum() == 1.0验证一组对象的 "weights" 是否总计为 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 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()bool当且仅当 asInteger 可以安全调用而不会出错时返回 true。
asInteger()int如果可能,返回当前值作为 int64 的表示,如果转换会导致溢出或精度损失,则会产生错误。
asApproximateFloat()float返回数量的 float64 表示,可能会丢失精度。如果数量的值超出 float64 的范围,则将返回 +Inf/-Inf。
sign()int如果数量为正,则返回 1,如果为负,则返回 -10 如果为零。
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 表达式。部分类型检查的表达式是指一些变量是静态类型化而其他变量是动态类型化的表达式。例如,在 ValidatingAdmissionPolicies 的 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>) 评估为 selfType<uniqueNumber>.path.to.object.from.self
'object' with AdditionalPropertiesmap
'object' with x-kubernetes-embedded-typeobject / "message type",'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 隐式包含在模式中
'object' with x-kubernetes-preserve-unknown-fieldsobject / "message type",CEL 表达式中无法访问未知字段
x-kubernetes-int-or-stringint 或 string 的并集,self.intOrString < 100 || self.intOrString == '50%'50"50%" 都评估为 true
'arraylist
'array' with x-kubernetes-list-type=map具有基于 map 的相等性与唯一键保证的列表
'array' with x-kubernetes-list-type=set具有基于 set 的相等性与唯一条目保证的列表
'boolean'boolean
'number' (all formats)double
'integer' (all formats)int (64)
没有等效项uint (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-typesetmap 的数组进行的连接将使用列表类型的语义

  • setX + Y 执行并集,其中保留 X 中所有元素的数组位置,并将 Y 中的非交集元素追加,保留其部分顺序。
  • mapX + 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
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 年 8 月 7 日下午 5:37 PST:在 CEL 示例表中添加缺少的闭合大括号 (1c9bcc4bb5)