验证准入策略(ValidatingAdmissionPolicy)

特性状态: Kubernetes v1.26 [alpha]

本页面提供验证准入策略(Validating Admission Policy)的概述。

什么是验证准入策略?

验证准入策略提供一种声明式的、进程内的替代方案来验证准入 Webhook。

验证准入策略使用通用表达语言 (Common Expression Language,CEL) 来声明策略的验证规则。 验证准入策略是高度可配置的,使配置策略的作者能够根据集群管理员的需要, 定义可以参数化并限定到资源的策略。

哪些资源构成策略

策略通常由三种资源构成:

  • ValidatingAdmissionPolicy 描述策略的抽象逻辑(想想看:“这个策略确保一个特定标签被设置为一个特定值”)。

  • 一个 ValidatingAdmissionPolicyBinding 将上述资源联系在一起,并提供作用域。 如果你只想为 Pods 设置一个 owner 标签,你就需要在这个绑定中指定这个限制。

  • 参数资源为 ValidatingAdmissionPolicy 提供信息,使其成为一个具体的声明 (想想看:“owner 标签必须被设置为以 .company.com 结尾的形式")。 参数资源的模式(Schema)使用诸如 ConfigMap 或 CRD 这类原生类型定义。 ValidatingAdmissionPolicy 对象指定它们期望参数资源所呈现的类型。

至少要定义一个 ValidatingAdmissionPolicy 和一个相对应的 ValidatingAdmissionPolicyBinding 才能使策略生效。

如果 ValidatingAdmissionPolicy 不需要参数配置,不设置 ValidatingAdmissionPolicy 中的 spec.paramKind 即可。

准备开始

  • 确保 ValidatingAdmissionPolicy 特性门控被启用。
  • 确保 admissionregistration.k8s.io/v1alpha1 API 被启用。

开始使用验证准入策略

验证准入策略是集群控制平面的一部分。你应该非常谨慎地编写和部署它们。下面介绍如何快速试验验证准入策略。

创建一个 ValidatingAdmissionPolicy

以下是一个 ValidatingAdmissionPolicy 的示例:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: ["apps"]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 5"

spec.validations 包含使用通用表达式语言 (CEL) 来验证请求的 CEL 表达式。 如果表达式的计算结果为 false,则根据 spec.failurePolicy 字段强制执行验证检查处理。

要配置一个在某集群中使用的验证准入策略,需要一个绑定。 以下是一个 ValidatingAdmissionPolicyBinding 的示例:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "demo-binding-test.example.com"
spec:
  policyName: "demo-policy.example.com"
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

尝试创建副本集合不满足验证表达式的 Deployment 时,将返回包含以下消息的错误:

ValidatingAdmissionPolicy 'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed expression: object.spec.replicas <= 5

上面提供的是一个简单的、无配置参数的 ValidatingAdmissionPolicy。

参数资源

参数资源允许策略配置与其定义分开。 一个策略可以定义 paramKind,给出参数资源的 GVK, 然后一个策略绑定可以通过名称(通过 policyName)将某策略与某特定的参数资源(通过 paramRef)联系起来。

如果需要参数配置,下面是一个带有参数配置的 ValidatingAdmissionPolicy 的例子:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "replicalimit-policy.example.com"
spec:
  failurePolicy: Fail
  paramKind:
    apiVersion: rules.example.com/v1
    kind: ReplicaLimit
  matchConstraints:
    resourceRules:
    - apiGroups: ["apps"]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["deployments"]
  validations:
    - expression: "object.spec.replicas <= params.maxReplicas"
      reason: Invalid

ValidatingAdmissionPolicy 的 spec.paramKind 字段指定用于参数化此策略的资源类型。 在这个例子中,它是由自定义资源 ReplicaLimit 配置的。 在这个例子中请注意 CEL 表达式是如何通过 CEL params 变量引用参数的,如:params.maxReplicasspec.matchConstraints 指定此策略要检查哪些资源。 请注意,诸如 ConfigMap 之类的原生类型也可以用作参数引用。

spec.validations 字段包含 CEL 表达式。 如果表达式的计算结果为 false,则根据 spec.failurePolicy 字段强制执行验证检查操作。

验证准入策略的作者负责提供 ReplicaLimit 参数 CRD。

要配置一个在某集群中使用的验证准入策略,需要创建绑定和参数资源。 以下是 ValidatingAdmissionPolicyBinding 的示例:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "replicalimit-binding-test.example.com"
spec:
  policyName: "replicalimit-policy.example.com"
  paramRef:
    name: "replica-limit-test.example.com"
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

参数资源可以如下:

apiVersion: rules.example.com/v1
kind: ReplicaLimit
metadata:
  name: "replica-limit-test.example.com"
maxReplicas: 3

此策略参数资源将限制测试环境所有名字空间中的 Deployment 最多有 3 个副本。 一个准入策略可以有多个绑定。 要绑定所有的其他环境,限制 maxReplicas 为 100,请创建另一个 ValidatingAdmissionPolicyBinding:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "replicalimit-binding-nontest"
spec:
  policyName: "replicalimit-policy.example.com"
  paramRef:
    name: "replica-limit-clusterwide.example.com"
  matchResources:
    namespaceSelector:
      matchExpressions:
      - key: environment
        operator: NotIn
        values:
        - test

并有一个参数资源,如下:

apiVersion: rules.example.com/v1
kind: ReplicaLimit
metadata:
  name: "replica-limit-clusterwide.example.com"
maxReplicas: 100

绑定可以包含相互重叠的匹配条件。策略会针对每个匹配的绑定进行计算。 在上面的例子中,nontest 策略绑定可以被定义为一个全局策略:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "replicalimit-binding-global"
spec:
  policyName: "replicalimit-policy.example.com"
  params: "replica-limit-clusterwide.example.com"
  matchResources:
    namespaceSelector:
      matchExpressions:
      - key: environment
        operator: Exists

如果参数资源尚未被绑定,代表参数资源的 params 对象将不会被设置, 所以对于需要参数资源的策略,添加一个检查来确保参数资源被绑定,这会很有用。

对于需要参数配置的场景,我们建议在 spec.validations[0].expression 中添加一个参数检查:

- expression: "params != null"
  message: "params missing but required to bind to this policy"

将可选参数作为参数资源的一部分,并且只在参数存在时执行检查操作,这样做会比较方便。 CEL 提供了 has() 方法,它检查传递给它的键是否存在。CEL 还实现了布尔短路逻辑。 如果逻辑 OR 的前半部分计算为 true,则不会计算另一半(因为无论如何整个 OR 的结果都为真)。

结合这两者,我们可以提供一种验证可选参数的方法:

!has(params.optionalNumber) || (params.optionalNumber >= 5 && params.optionalNumber <= 10)

在这里,我们首先用 !has(params.optionalNumber) 检查可选参数是否存在。

  • 如果 optionalNumber 没有被定义,那么表达式就会短路,因为 !has(params.optionalNumber) 的计算结果为 true。
  • 如果 optionalNumber 被定义了,那么将计算 CEL 表达式的后半部分, 并且 optionalNumber 将被检查以确保它包含一个 5 到 10 之间的值(含 5 到 10)。

鉴权检查

我们为参数资源引入了鉴权检查。 用户应该对 ValidatingAdmissionPolicy 中的 paramKindValidatingAdmissionPolicyBinding 中的 paramRef 所引用的资源有 read 权限。

请注意,如果 paramKind 中的资源没能通过 restmapper 解析,则用户需要拥有对组的所有资源的 read 访问权限。

失效策略

failurePolicy 定义了如何处理错误配置和准入策略的 CEL 表达式取值为 error 的情况。

允许的值是 IgnoreFail

  • Ignore 意味着调用 ValidatingAdmissionPolicy 的错误被忽略,允许 API 请求继续。
  • Fail 意味着调用 ValidatingAdmissionPolicy 的错误导致准入失败并拒绝 API 请求。

请注意,failurePolicy 是在 ValidatingAdmissionPolicy 中定义的:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
spec:
...
failurePolicy: Ignore # 默认值是 "Fail"
validations:
- expression: "object.spec.xyz == params.x"

检查表达式

spec.validations[i].expression 代表将使用 CEL 来计算表达式。 要了解更多信息,请参阅 CEL 语言规范。 CEL 表达式可以访问按 CEL 变量来组织的 Admission 请求/响应的内容,以及其他一些有用的变量 :

  • 'object' - 来自传入请求的对象。对于 DELETE 请求,该值为 null。
  • 'oldObject' - 现有对象。对于 CREATE 请求,该值为 null。
  • 'request' - 准入请求的属性。
  • 'params' - 被计算的策略绑定引用的参数资源。如果未设置 paramKind,该值为 null。

总是可以从对象的根访问的属性有 apiVersionkindmetadata.namemetadata.generateName。 其他元数据属性不能访问。

只有符合 [a-zA-Z_.-/][a-zA-Z0-9_.-/]* 形式的属性名称是可访问的。 可访问的属性名称在表达式中被访问时,根据以下规则进行转义:

转义序列 属性名称等效
__underscores__ __
__dot__ .
__dash__ -
__slash__ /
__{keyword}__ CEL 保留关键字

转义示例:

属性名 具有转义属性名称的规则
namespace self.__namespace__ > 0
x-prop self.x__dash__prop > 0
redact__d self.redact__underscores__d > 0
string self.startsWith('kube')

列表类型为 "set" 或 "map" 的数组上的等价关系比较会忽略元素顺序,即 [1, 2] == [2, 1]。 使用 x-kubernetes-list-type 连接数组时使用列表类型的语义:

  • 'set': X + Y 执行并集,其中 X 中所有元素的数组位置被保留,Y 中不相交的元素被追加,保留其元素的偏序关系。
  • 'map':X + Y 执行合并,保留 X 中所有键的数组位置,但是当 XY 的键集相交时,其值被 Y 的值覆盖。 Y 中键值不相交的元素被追加,保留其元素之间的偏序关系。

检查表达式示例

表达式 目的
object.minReplicas <= object.replicas && object.replicas <= object.maxReplicas 检查定义副本的三个字段是否大小关系正确
'Available' in object.stateCounts 检查映射中是否存在键为 Available 的条目
(size(object.list1) == 0) != (size(object.list2) == 0) 检查两个列表是否有且只有一个非空
!('MY_KEY' in object.map1) || object['MY_KEY'].matches('^[a-zA-Z]*$') 检查映射中存在特定的键时其取值符合某规则
object.envars.filter(e, e.name == 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$') 验证 listMap 中所有键名为 "MY_ENV" 的条目的 “value” 字段,确保其符合规则
has(object.expired) && object.created + object.ttl < object.expired 检查 expired 日期在 create 日期加上 ttl 时长之后
object.health.startsWith('ok') 检查 health 字符串字段的取值有 “ok” 前缀
object.widgets.exists(w, w.key == 'x' && w.foo < 10) 对于 listMap 中键为 “x” 的条目,检查该条目的 "foo" 属性的值是否小于 10
type(object) == string ? object == '100%' : object == 1000 对于 int-or-string 字段,分别处理类型为 int 或 string 的情况
object.metadata.name.startsWith(object.prefix) 检查对象名称是否以另一个字段值为前缀
object.set1.all(e, !(e in object.set2)) 检查两个 listSet 是否不相交
size(object.names) == size(object.details) && object.names.all(n, n in object.details) 检查映射 “details” 所有的键和 listSet “names” 中的条目是否一致
size(object.clusters.filter(c, c.name == object.primary)) == 1 检查 “primary” 字段的值在 listMap “clusters” 中只出现一次

了解关于 CEL 规则的更多信息, 请阅读 CEL 支持的求值表达式

spec.validation[i].reason 表示一个机器可读的描述,说明为什么这次检查失败。 如果这是列表中第一个失败的检查,其原因以及相应的 HTTP 响应代码会被用在给客户端的 HTTP 响应中。 目前支持的原因有:UnauthorizedForbiddenInvalidRequestEntityTooLarge。 如果未设置,将在对客户端的响应中使用 StatusReasonInvalid

最后修改 April 05, 2023 at 10:04 PM PST: [zh] translate page validating-admission-policy (937d9b472d)