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 表达式示例
规则 | 用途 |
---|---|
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 版本 | |
扩展字符串库,版本 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 CrossTypeNumericComparisons | 请参阅 CEL CrossTypeNumericComparisons | 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 表达式 | 用途 |
---|---|
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
函数之外,正则表达式库还提供 find
和 findAll
,从而能够执行更广泛的正则表达式操作。
示例
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 对象将具有 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 授权库
对于 API 中的 CEL 表达式,其中类型为 Authorizer
的变量可用,授权者可用于对请求的主体(经过身份验证的用户)执行授权检查。
API 资源检查按以下方式执行
- 指定要检查的组和资源:
Authorizer.group(string).resource(string) ResourceCheck
- 可以选择调用以下构建器函数的任意组合以进一步缩小授权检查范围。请注意,这些函数将返回接收器类型,并且可以链接
ResourceCheck.subresource(string) ResourceCheck
ResourceCheck.namespace(string) ResourceCheck
ResourceCheck.name(string) ResourceCheck
- 调用
ResourceCheck.check(verb string) Decision
以执行授权检查。 - 调用
allowed() bool
或reason() string
以检查授权检查的结果。
非资源授权检查按以下方式执行
- 仅指定路径:
Authorizer.path(string) PathCheck
- 调用
PathCheck.check(httpVerb string) Decision
以执行授权检查。 - 调用
allowed() bool
或reason() string
以检查授权检查的结果。
要对服务帐户执行授权检查
Authorizer.serviceAccount(namespace string, name string) Authorizer
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 表达式 | 用途 |
---|---|
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 对象具有以下库的成员函数。
成员函数 | 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 |
示例
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 类型 |
---|---|
'object' with Properties | object / "message type" (type(<object>) 评估为 selfType<uniqueNumber>.path.to.object.from.self |
'object' with AdditionalProperties | map |
'object' with x-kubernetes-embedded-type | object / "message type",'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 隐式包含在模式中 |
'object' with x-kubernetes-preserve-unknown-fields | object / "message type",CEL 表达式中无法访问未知字段 |
x-kubernetes-int-or-string | int 或 string 的并集,self.intOrString < 100 || self.intOrString == '50%' 对 50 和 "50%" 都评估为 true |
'array | list |
'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=date | timestamp (google.protobuf.Timestamp) |
'string' with format=datetime | timestamp (google.protobuf.Timestamp) |
'string' with format=duration | duration (google.protobuf.Duration) |
另请参阅:CEL 类型、OpenAPI 类型、Kubernetes 结构模式。
对 x-kubernetes-list-type
为 set
或 map
的数组进行的相等性比较将忽略元素顺序。例如,如果数组表示 Kubernetes set
值,则 [1, 2] == [2, 1]
。
对 x-kubernetes-list-type
为 set
或 map
的数组进行的连接将使用列表类型的语义
set
:X + Y
执行并集,其中保留X
中所有元素的数组位置,并将Y
中的非交集元素追加,保留其部分顺序。map
:X + Y
执行合并,其中保留X
中所有键的数组位置,但当X
和Y
的键集相交时,值将被Y
中的值覆盖。Y
中具有非交集键的元素将被追加,保留其部分顺序。
转义
只有 [a-zA-Z_.-/][a-zA-Z0-9_.-/]*
格式的 Kubernetes 资源属性名称可从 CEL 访问。在表达式中访问时,可访问的属性名称将根据以下规则进行转义
转义序列 | 属性名称等效项 |
---|---|
__underscores__ | __ |
__dot__ | . |
__dash__ | - |
__slash__ | / |
__{keyword}__ | CEL 保留关键字 |
当您转义 CEL 的任何 保留关键字时,您需要匹配确切的属性名称并使用下划线转义(例如,sprint
这个词中的 int
将不会转义,也不需要转义)。
转义示例
属性名称 | 使用转义属性名称的规则 |
---|---|
namespace | self.__namespace__ > 0 |
x-prop | self.x__dash__prop > 0 |
redact__d | self.redact__underscores__d > 0 |
string | self.startsWith('kube') |
资源约束
CEL 不是图灵完备的,并提供各种生产安全控制以限制执行时间。CEL 的资源约束功能为开发人员提供有关表达式复杂度的反馈,并帮助保护 API 服务器在评估期间免受过度资源消耗。CEL 的资源约束功能用于防止 CEL 评估消耗过多的 API 服务器资源。
资源约束功能的关键要素是成本单位,CEL 将其定义为跟踪 CPU 利用率的一种方式。成本单位独立于系统负载和硬件。成本单位也是确定性的;对于任何给定的 CEL 表达式和输入数据,CEL 解释器对该表达式的评估将始终产生相同的成本。
CEL 的许多核心操作都具有固定成本。最简单的操作(例如 <
)的成本为 1。有些操作具有更高的固定成本,例如列表文字声明的固定基本成本为 40 个成本单位。
对原生代码中实现的函数的调用基于操作的时间复杂度来近似成本。例如:使用正则表达式的操作(例如 match
和 find
)使用 length(regexString)*length(inputString)
的近似成本进行估计。近似成本反映了 Go 的 RE2 实现的最坏情况时间复杂度。
运行时成本预算
Kubernetes 评估的所有 CEL 表达式都受到运行时成本预算的约束。运行时成本预算是在解释 CEL 表达式时通过递增成本单位计数器计算的实际 CPU 利用率的估计值。如果 CEL 解释器执行了太多指令,则运行时成本预算将被超过,表达式的执行将被中止,并产生错误。
一些 Kubernetes 资源定义了额外的运行时成本预算,用于限制多个表达式的执行。如果所有表达式的成本总和超过了预算,则表达式的执行将被中止,并产生错误。例如,自定义资源的验证对所有 验证规则 的执行都有一个每次验证的运行时成本预算,以验证自定义资源。
估计成本限制
对于一些 Kubernetes 资源,API 服务器也可能会检查 CEL 表达式的最坏情况估计运行时间是否过于昂贵。如果是,API 服务器将通过拒绝将 CEL 表达式写入 API 资源(拒绝包含 CEL 表达式的 API 资源的创建或更新操作)来防止 CEL 表达式被写入 API 资源。此功能提供了更强有力的保证,即写入 API 资源的 CEL 表达式将在运行时进行评估,而不会超过运行时成本预算。