本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 1.25:CustomResourceDefinition 验证规则进阶至 Beta

在 Kubernetes 1.25 中,CustomResourceDefinitions (CRD) 的验证规则已晋升为 Beta!

验证规则使得使用通用表达式语言 (CEL) 声明如何验证自定义资源成为可能。例如

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
    ...
    openAPIV3Schema:
      type: object
      properties:
        spec:
          type: object
          x-kubernetes-validations:
            - rule: "self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas"
              message: "replicas should be in the range minReplicas..maxReplicas."
          properties:
            replicas:
              type: integer
            ...

验证规则支持广泛的用例。为了解其部分功能,我们来看几个例子:

验证规则目的
self.minReplicas <= self.replicas验证一个整数字段小于或等于另一个整数字段
'Available' in self.stateCounts验证映射中存在键为 'Available' 的条目
self.set1.all(e, !(e in self.set2))验证两个集合的元素互不相交
self == oldSelf验证一个必填字段一旦设置后就不可变
self.created + self.ttl < self.expired验证 'expired' 日期在 'create' 日期加上一个 'ttl' 持续时间之后

验证规则富有表现力且灵活。请参阅验证规则文档以了解更多关于验证规则功能的信息。

为什么选择 CEL?

选择 CEL 作为验证规则的语言有几个原因:

  • CEL 表达式可以轻松地内联到 CRD 模式中。它们的表现力足以取代当前在准入 Webhook 中实现的绝大多数 CRD 验证检查。这使得 CRD 更加独立自足,也更易于理解。
  • CEL 表达式会根据 CRD 的模式进行“提前”(在 CRD 创建和更新时)编译和类型检查,这使得它们在“运行时”(在验证自定义资源时)能够高效、安全地进行评估。即使是 CEL 中的正则表达式字符串字面量,在创建或更新 CRD 时也会被验证和预编译。

为什么不使用验证 Webhook?

与验证 Webhook 相比,使用验证规则有以下好处:

  • CRD 作者可以受益于更简单的工作流程,因为验证规则免去了开发和维护 Webhook 的需要。
  • 集群管理员受益于不再需要为 CRD 验证而安装、升级和操作 Webhook。
  • 集群的可操作性得到改善,因为 CRD 验证不再需要远程调用 Webhook 端点,消除了 Kubernetes API 服务器请求服务路径中的一个潜在故障点。这使得集群能够在扩展到更大数量的已安装 CRD 扩展时保持高可用性,因为否则每增加一个已安装的 Webhook,预期的控制平面可用性都会降低。

开始使用验证规则

在 OpenAPIv3 模式中编写验证规则

你可以在 CRD 的 OpenAPIv3 模式的任何层级定义验证规则。验证规则会自动作用于它们在模式中声明的位置。

CRD 验证规则的良好实践

  • 将验证规则的作用域尽可能地限定在它们所验证的字段附近。
  • 在验证独立约束时使用多条规则。
  • 不要将验证规则用于已有的验证
  • 在可用时使用 OpenAPIv3 的值验证maxLengthmaxItemsmaxPropertiesrequiredenumminimummaximum 等)和字符串格式
  • 在适当的地方使用 x-kubernetes-int-or-stringx-kubernetes-embedded-typex-kubernetes-list-type=(set|map)

良好实践的示例

验证最佳实践示例
验证一个整数在 0 到 100 之间。使用 OpenAPIv3 的值验证。
type: integer
minimum: 0
maximum: 100
限制映射(带有 additionalProperties 的对象)、数组和字符串的最大大小限制。使用 OpenAPIv3 值验证。推荐用于所有映射、数组和字符串。这一最佳实践对于规则成本估算(下文解释)至关重要。
type:
maxItems: 100
要求一个日期时间比某个特定时间戳更晚。使用 OpenAPIv3 字符串格式来声明该字段为日期时间。使用验证规则将其与特定时间戳进行比较。
type: string
format: date-time
x-kubernetes-validations:
- rule: "self >= timestamp('2000-01-01T00:00:00.000Z')"
要求两个集合不相交。使用 x-kubernetes-list-type 来验证数组是集合。
使用验证规则来验证集合不相交。
type: object
properties:
set1:
type: array
x-kubernetes-list-type: set
set2: ...
x-kubernetes-validations:
- rule: "!self.set1.all(e, !(e in self.set2))"

CRD 转换规则

转换规则使得在验证规则中比较资源的新旧状态成为可能。你可以使用转换规则来确保集群的 API 服务器不接受无效的状态转换。转换规则是引用 'oldSelf' 的验证规则。API 服务器仅在旧值和新值都存在时才评估转换规则。

转换规则示例

转换规则目的
self == oldSelf对于必填字段,使其一旦设置后不可变。对于可选字段,只允许从未设置转换为已设置,或从已设置转换为未设置。
(在字段的父级上) has(self.field) == has(oldSelf.field)
在字段上: self == oldSelf
使一个字段不可变:验证一个字段,即使是可选的,在资源创建后也永不改变(对于必填字段,前面的规则更简单)。
self.all(x, x in oldSelf)只允许向代表集合的字段添加项(防止删除)。
self >= oldSelf验证一个数字是单调递增的。

使用函数库

验证规则可以访问几个不同的函数库:

函数库使用示例

验证规则目的
!(self.getDayOfWeek() in [0, 6])验证一个日期不是周日或周六。
isUrl(self) && url(self).getHostname() in ['a.example.com', 'b.example.com']验证一个 URL 的主机名在允许的范围内。
self.map(x, x.weight).sum() == 1验证对象列表中各对象的权重总和为 1。
int(self.find('^[0-9]*')) < 100验证字符串以小于 100 的数字开头。
self.isSorted()验证一个列表是已排序的。

资源使用和限制

为了防止 CEL 评估消耗过多的计算资源,验证规则施加了一些限制。这些限制基于 CEL *成本单位*,这是一种与平台和机器无关的执行成本度量。因此,无论在何处强制执行,限制都是相同的。

估算成本限制

CEL 在设计上是非图灵完备的,因此停机问题不是一个问题。CEL 利用这一设计选择,包含了一个“估算成本”子系统,可以静态计算任何 CEL 表达式的最坏情况运行时成本。验证规则与估算成本系统集成,并禁止将估算成本过高(高)的 CEL 表达式包含在 CRD 中。估算成本限制设置得相当高,通常需要对无界大小的对象执行 O(n^2) 或更差的操作才会超出。幸运的是,修复通常非常简单:因为成本系统知道 CRD 模式中声明的大小限制,CRD 作者可以在 CRD 的模式中添加大小限制(数组的 `maxItems`、映射的 `maxProperties`、字符串的 `maxLength`)来降低估算成本。

良好实践

在 CRD 模式中为所有数组、映射(带有 `additionalProperties` 的 `object`)和字符串类型设置 `maxItems`、`maxProperties` 和 `maxLength`!这会带来更低、更准确的估算成本,并通常使 CRD 更安全。

CRD 验证规则的运行时成本限制

除了估算成本限制外,CEL 在评估 CEL 表达式时还会跟踪实际成本,如果超过限制,将停止表达式的执行。

由于已经有了估算成本限制,运行时成本限制很少会遇到。但它是有可能发生的。例如,对于一个完全由单个大列表组成的大型资源,以及一个在列表的每个元素上评估或遍历整个列表的验证规则,可能会遇到这种情况。

CRD 作者可以确保不会超过运行时成本限制,方法与避免估算成本限制的方式大致相同:通过在数组、映射和字符串类型上设置 `maxItems`、`maxProperties` 和 `maxLength`。

未来的工作

我们期待与社区合作,共同推动 CRD 验证规则的采用,并希望在即将到来的 Kubernetes 版本中看到该功能晋升为正式发布!

一个不断壮大的 Kubernetes 贡献者社区正在思考如何使用 CEL 作为准入 Webhook 的替代品,为策略执行用例编写可扩展的准入控制器。任何感兴趣的人都应该通过常规的 SIG API Machinery 渠道或 Slack 上的 #sig-api-machinery-cel-dev 与我们联系。

致谢

特别感谢 Cici Huang、Ben Luddy、Jordan Liggitt、David Eads、Daniel Smith、Stefan Schimanski 博士、Leila Jalali 以及所有为验证规则做出贡献的人!