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 字段中,selfoldSelf 变量可用,它们分别指向由 CEL 表达式验证的自定义资源数据的先前状态和当前状态。其他 Kubernetes API 字段可能会声明不同的变量。请查阅 API 字段的 API 文档,了解该字段可用哪些变量。

CEL 表达式示例

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 选项、库或语言特性包含的函数/宏可用性
标准宏hasallexistsexists_onemapfilter所有 Kubernetes 版本
标准函数请参阅标准定义官方列表所有 Kubernetes 版本
同质聚合字面量所有 Kubernetes 版本
默认 UTC 时区所有 Kubernetes 版本
立即验证声明所有 Kubernetes 版本
扩展字符串库,版本 1charAtindexOflastIndexOflowerAsciiupperAsciireplacesplitjoinsubstringtrim所有 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 库包含 indexOflastIndexOf,它们的工作方式类似于同名的字符串函数。这些函数返回提供的元素在 list 中首次或最后一次出现的位置索引。

list 库还包含 minmaxsumsum 支持所有数字类型和 duration 类型。minmax 支持所有可比较类型。

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

示例

使用 list 库函数的 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 List Library godoc 以获取更多信息。

Kubernetes Regex (正则表达式) 库

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

示例

使用 regex 库函数的 CEL 表达式示例
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 对象具有 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 Authorizer (授权器) 库

对于 API 中类型为 Authorizer 的变量可用的 CEL 表达式,可以使用 authorizer 执行请求 principal(认证用户)的授权检查。

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

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

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

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

执行 ServiceAccount 的授权检查

  • Authorizer.serviceAccount(namespace string, name string) Authorizer
使用 URL 库函数的 CEL 表达式示例
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。
FEATURE STATE: Kubernetes v1.31 [alpha]

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

使用选择器授权函数的 CEL 表达式示例
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 对象具有以下成员函数库

Quantity 可用的成员函数
成员函数CEL 返回值描述
isInteger()bool当且仅当调用 asInteger 是安全的且不会出错时,返回 true。
asInteger()int如果可能,返回当前值表示为 int64,如果转换会导致溢出或精度丢失,则返回错误。
asApproximateFloat()float返回 Quantity 的 float64 表示,这可能会丢失精度。如果 Quantity 的值超出 float64 的范围,将返回 +Inf/-Inf。
sign()intint
如果 Quantity 为正,则返回 1;如果为负,则返回 -1;如果为零,则返回 0。Quantityadd(<Quantity>)
QuantityQuantity返回两个 Quantity 的总和
add(<int>)QuantityQuantity
返回 Quantity 与整数的总和Quantitysub(<Quantity>)
Quantitybool返回两个 Quantity 的差
sub(<int>)boolQuantity
返回 Quantity 与整数的差intisLessThan(<Quantity>)

示例

使用 URL 库函数的 CEL 表达式示例
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("50M").isLessThan(quantity("100M"))
测试 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' 带有 Propertiesobject / “消息类型” (type(<object>) 评估为 selfType<uniqueNumber>.path.to.object.from.self
'object' 带有 AdditionalPropertiesmap
'object' 带有 x-kubernetes-embedded-typeobject / “消息类型”,apiVersionkindmetadata.namemetadata.generateName 隐式包含在 schema 中
'object' 带有 x-kubernetes-preserve-unknown-fieldsobject / “消息类型”,未知字段在 CEL 表达式中不可访问
x-kubernetes-int-or-stringint 或 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)

另请参阅:CEL 类型OpenAPI 类型Kubernetes 结构化 Schema
对于 x-kubernetes-list-typesetmap 的 array,相等性比较会忽略元素顺序。例如,如果 array 表示 Kubernetes set 值,则 [1, 2] == [2, 1]带有 x-kubernetes-list-type 的 array 的拼接使用 list 类型的语义
set__
X + Y 执行联合操作,其中保留 X 中所有元素的 array 位置,并将 Y 中不相交的元素附加到末尾,保留其偏序。.
map-
X + Y 执行合并操作,其中保留 X 中所有键的 array 位置,但当 XY 的键集相交时,值会被 Y 中的值覆盖。Y 中键不相交的元素会被附加到末尾,保留其偏序。/
转义只有形如 [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

资源限制特性的一个关键要素是 CEL 定义的成本单位,它是一种跟踪 CPU 利用率的方式。成本单位与系统负载和硬件无关。成本单位也是确定性的;对于任何给定的 CEL 表达式和输入数据,CEL 解释器评估表达式的结果成本总是相同的。