Kubernetes 中的通用表达语言
Kubernetes API 中使用 通用表达式语言 (CEL) 来声明验证规则、策略规则以及其他约束或条件。
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
字段中,self
和 oldSelf
变量可用,它们引用了要由 CEL 表达式验证的自定义资源数据的先前状态和当前状态。其他 Kubernetes API 字段可能声明不同的变量。参阅 API 字段的 API 文档,了解该字段可用的变量。
CEL 表达式示例
规则 | 用途 |
---|---|
self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas | 验证定义副本数的三个字段是否按适当顺序排列 |
'Available' in self.stateCounts | 验证 Map 中是否存在键为 '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]*$')) | 验证 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) | 验证键为 'x' 的 listMap 项的 'foo' 属性小于 10 |
type(self) == string ? self == '99%' : self == 42 | 验证 int-or-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 选项、库或语言特性 | 包含 | 可用性 |
---|---|---|
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 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 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 list 库
list 库包含 indexOf
和 lastIndexOf
,它们的功能与同名的字符串函数类似。这些函数返回给定元素在列表中的第一个或最后一个位置索引。
list 库还包括 min
、max
和 sum
。Sum 支持所有数值类型以及 duration 类型。Min 和 max 支持所有可比较类型。
isSorted
也作为便捷函数提供,并支持所有可比较类型。
示例
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 库还提供了 find
和 findAll
,支持更广泛的正则表达式操作。
示例
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 对象具有 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 authorizer 库
对于 API 中类型为 Authorizer
的变量可用的 CEL 表达式,可以使用 authorizer 对请求的主体(已认证用户)执行授权检查。
API 资源检查如下执行
- 指定要检查的 group 和 resource:
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
检查授权检查结果。
对 ServiceAccount 执行授权检查
Authorizer.serviceAccount(namespace string, name string) Authorizer
CEL 表达式 | 用途 |
---|---|
authorizer.group('').resource('pods').namespace('default').check('create').allowed() | 如果主体(用户或 ServiceAccount)被允许在 'default' 命名空间中创建 Pod,则返回 true。 |
authorizer.path('/healthz').check('get').allowed() | 检查主体(用户或 ServiceAccount)是否有权对 /healthz API 路径发出 HTTP GET 请求。 |
authorizer.serviceAccount('default', 'myserviceaccount').resource('deployments').check('delete').allowed() | 检查 ServiceAccount 是否有权删除 Deployment。 |
Kubernetes v1.31 [alpha]
启用 alpha 特性 AuthorizeWithSelectors
后,可以在授权检查中添加字段和标签选择器。
CEL 表达式 | 用途 |
---|---|
authorizer.group('').resource('pods').fieldSelector('spec.nodeName=mynode').check('list').allowed() | 如果主体(用户或 ServiceAccount)被允许使用字段选择器列出 Pod,则返回 true。spec.nodeName=mynode. |
authorizer.group('').resource('pods').labelSelector('example.com/mylabel=myvalue').check('list').allowed() | 如果主体(用户或 ServiceAccount)被允许使用标签选择器列出 Pod,则返回 true。example.com/mylabel=myvalue. |
参阅 Kubernetes Authz 库和 Kubernetes AuthzSelectors 库 godoc 了解更多信息。
Kubernetes quantity 库
Kubernetes 1.28 添加了对 Quantity 字符串(例如 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 | 返回一个float64Quantity 的表示形式,可能会损失精度。如果 Quantity 的值超出float64, +Inf/-Inf将返回。 | |
sign() | int | 返回1如果 Quantity 为正,则返回-1如果 Quantity 为负,则返回0如果 Quantity 为零,则返回 | |
add(<Quantity>) | 数量 | 返回两个 Quantity 的总和 | |
add(<int>) | 数量 | 返回 Quantity 和整数的总和 | |
sub(<Quantity>) | 数量 | 返回两个 Quantity 之间的差 | |
sub(<int>) | 数量 | 返回 Quantity 和整数之间的差 | |
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 相加 |
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")) | 测试 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 表达式中使用,用于在尝试访问动态类型变量的字段值之前检查该字段是否可访问。例如
has(object.namex) ? object.namex == 'special' : request.name == 'special'
类型系统集成
OpenAPIv3 类型 | CEL 类型 |
---|---|
具有 Properties 的 'object' | object / "消息类型" (type(<object>)评估为selfType<uniqueNumber>.path.to.object.from.self) |
具有 的 'object'additionalProperties | map |
具有 的 'object'x-kubernetes-embedded-type | object / "消息类型",'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 隐式包含在模式中 |
具有 x-kubernetes-preserve-unknown-fields 的 'object' | object / "消息类型",未知字段在 CEL 表达式中不可访问 |
x-kubernetes-int-or-string | 的联合int或string, self.intOrString < 100 | self.intOrString == '50%'对两者均评估为 true50和"50%" |
'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' | null_type |
'string' | string |
格式为 byte ('string') 的 'string' (base64 编码) | bytes |
格式为 date 的 'string' | timestamp (google.protobuf.Timestamp) |
格式为 datetime 的 'string' | timestamp (google.protobuf.Timestamp) |
格式为 duration 的 'string' | duration (google.protobuf.Duration) |
另请参阅:CEL 类型、OpenAPI 类型、Kubernetes 结构化模式。
对于 x-kubernetes-list-type
为 set
或 map
的数组,相等性比较会忽略元素顺序。例如,如果数组代表 Kubernetes set
值,则 [1, 2] == [2, 1]
。
具有 x-kubernetes-list-type
的数组上的连接使用列表类型的语义
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 资源。此特性提供了更强的保证,确保写入 API 资源的 CEL 表达式将在运行时进行评估,而不会超出运行时成本预算。