你正在查看的文档所针对的是 Kubernetes 版本: v1.27

Kubernetes v1.27 版本的文档已不再维护。你现在看到的版本来自于一份静态的快照。如需查阅最新文档,请点击 最新版本。

验证准入策略(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"
  validationActions: [Deny]
  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。

验证操作

每个 ValidatingAdmissionPolicyBinding 必须指定一个或多个 validationActions 来声明如何执行策略的 validations

支持的 validationActions 包括:

  • Deny: 验证失败会导致请求被拒绝。
  • Warn: 验证失败会作为警告报告给请求客户端。
  • Audit: 验证失败会包含在 API 请求的审计事件中。

例如,要同时向客户端发出验证失败的警告并记录验证失败的审计记录,请使用以下配置:

validationActions: [Warn, Audit]

DenyWarn 不能一起使用,因为这种组合会不必要地将验证失败重复输出到 API 响应体和 HTTP 警告头中。

如果 validation 求值为 false,则始终根据这些操作执行。 由 failurePolicy 定义的失败仅在 failurePolicy 设置为 Fail(或未设置)时根据这些操作执行, 否则这些失败将被忽略。

有关验证失败审计注解的详细信息,请参见 审计注解:验证失败

参数资源

参数资源允许策略配置与其定义分开。 一个策略可以定义 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"
  validationActions: [Deny]
  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"
  validationActions: [Deny]
  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"
  validationActions: [Deny]
  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。
  • authorizer - 一个 CEL 鉴权组件。可以用来为请求的主体(经过身份验证的用户)执行鉴权检查。 更多细节可以参考 Kubernetes CEL 库的文档中的 Authz
  • authorizer.requestResource - 针对请求资源(组、资源、(子资源)、命名空间、名称)所配置的鉴权检查的快捷方式。

总是可以从对象的根访问的属性有 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

匹配请求:matchConditions

如果需要进行精细的请求过滤,可以为 ValidatingAdmissionPolicy 定义 匹配条件(match conditions)。 如果发现匹配规则、objectSelectorsnamespaceSelectors 仍无法提供所需的过滤功能,则使用这些条件非常有用。 匹配条件是 CEL 表达式。 所有匹配条件必须求值为 true 时才会对资源进行评估。

以下示例说明了匹配条件的几个不同用法:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups:   ["*"]
        apiVersions: ["*"]
        operations:  ["CREATE", "UPDATE"]
        resources:   ["*"]
  matchConditions:
    - name: 'exclude-leases' # 每个匹配条件必须有一个唯一的名称
      expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # 匹配非租约资源
    - name: 'exclude-kubelet-requests'
      expression: '!("system:nodes" in request.userInfo.groups)' # 匹配非节点用户发出的请求
    - name: 'rbac' # 跳过 RBAC 请求
      expression: 'request.resource.group != "rbac.authorization.k8s.io"'
  validations:
    - expression: "!object.metadata.name.contains('demo') || object.metadata.namespace == 'demo'"

这些匹配条件可以访问与验证表达式相同的 CEL 变量。

在评估匹配条件时出现错误时,将不会评估策略。根据以下方式确定是否拒绝请求:

  1. 如果任何一个匹配条件求值结果为 false(不管其他错误),API 服务器将跳过 Webhook。

  2. 否则:

审计注解

auditAnnotations 可用于在 API 请求的审计事件中包括审计注解。

例如,以下是带有审计注解的准入策略:

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:
    - key: "high-replica-count"
      expression: "object.spec.replicas > 50"
      messageExpression: "'Deployment spec.replicas set to ' + string(object.spec.replicas)"

当使用此准入策略验证 API 请求时,生成的审计事件将如下所示:

# 记录的审计事件
{
    "kind": "Event",
    "apiVersion": "audit.k8s.io/v1",
    "annotations": {
        "demo-policy.example.com/high-replica-count": "Deployment spec.replicas set to 128"
        # 其他注解
        ...
    }
    # 其他字段
    ...
}

在此示例中,只有 Deployment 的 spec.replicas 大于 50 时才会包含注解, 否则 CEL 表达式将求值为 null,并且不会包含注解。

请注意,审计注解键以 ValidatingAdmissionWebhook 的名称和 / 为前缀。 如果另一个准入控制器(例如准入 Webhook)使用完全相同的审计注解键, 则第一个包括审计注解值的准入控制器将出现在审计事件中,而所有其他值都将被忽略。

消息表达式

为了在策略拒绝请求时返回更友好的消息,我们在 spec.validations[i].messageExpression 中使用 CEL 表达式来构造消息。 与验证表达式类似,消息表达式可以访问 objectoldObjectrequestparams。 但是,与验证不同,消息表达式必须求值为字符串。

例如,为了在策略引用参数时更好地告知用户拒绝原因,我们可以有以下验证:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "deploy-replica-policy.example.com"
spec:
  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"
    messageExpression: "'object.spec.replicas must be no greater than ' + string(params.maxReplicas)"
    reason: Invalid

在创建限制副本为 3 的 Params 对象并设置绑定之后,当我们尝试创建具有 5 个副本的 Deployment 时,我们将收到以下消息:

$ kubectl create deploy --image=nginx nginx --replicas=5
error: failed to create deployment: deployments.apps "nginx" is forbidden: ValidatingAdmissionPolicy 'deploy-replica-policy.example.com' with binding 'demo-binding-test.example.com' denied request: object.spec.replicas must be no greater than 3

这比静态消息 "too many replicas" 更具说明性。

如果既定义了消息表达式,又在 spec.validations[i].message 中定义了静态消息, 则消息表达式优先于静态消息。 但是,如果消息表达式求值失败,则将使用静态消息。 此外,如果消息表达式求值为多行字符串,则会丢弃求值结果并使用静态消息(如果存在)。 请注意,静态消息也要检查是否存在多行字符串。

类型检查

创建或更新策略定义时,验证过程将解析它包含的表达式,在发现错误时报告语法错误并拒绝该定义。 之后,引用的变量将根据 spec.matchConstraints 的匹配类型检查类型错误,包括缺少字段和类型混淆。 类型检查的结果可以从 status.typeChecking 中获得。 status.typeChecking 的存在表示类型检查已完成,而空的 status.typeChecking 表示未发现错误。

例如,给定以下策略定义:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "deploy-replica-policy.example.com"
spec:
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
  - expression: "object.replicas > 1" # 应该是 object.spec.replicas > 1
    message: "must be replicated"
    reason: Invalid

status 字段将提供以下信息:

status:
  typeChecking:
    expressionWarnings:
    - fieldRef: spec.validations[0].expression
      warning: |-
        apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
         | object.replicas > 1
         | ......^        

如果在 spec.matchConstraints 中匹配了多个资源,则所有匹配的资源都将进行检查。 例如,以下策略定义:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: ValidatingAdmissionPolicy
metadata:
  name: "replica-policy.example.com"
spec:
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments","replicasets"]
  validations:
  - expression: "object.replicas > 1" # 应该是 object.spec.replicas > 1
    message: "must be replicated"
    reason: Invalid

将具有多个类型,并在警告消息中提供每种类型的类型检查结果。

status:
  typeChecking:
    expressionWarnings:
    - fieldRef: spec.validations[0].expression
      warning: |-
        apps/v1, Kind=Deployment: ERROR: <input>:1:7: undefined field 'replicas'
         | object.replicas > 1
         | ......^
        apps/v1, Kind=ReplicaSet: ERROR: <input>:1:7: undefined field 'replicas'
         | object.replicas > 1
         | ......^        

类型检查具有以下限制:

  • 没有通配符匹配。 如果 spec.matchConstraints.resourceRules 中的任何一个 apiGroupsapiVersionsresources 包含 "*",则不会检查与 "*" 匹配的类型。
  • 匹配的类型数量最多为 10 种。这是为了防止手动指定过多类型的策略消耗过多计算资源。 按升序处理组、版本,然后是资源,忽略第 11 个及其之后的组合。
  • 类型检查不会以任何方式影响策略行为。即使类型检查检测到错误,策略也将继续评估。 如果在评估期间出现错误,则失败策略将决定其结果。
  • 类型检查不适用于 CRD(自定义资源定义),包括匹配的 CRD 类型和 paramKind 的引用。 对 CRD 的支持将在未来发布中推出。
最后修改 July 27, 2023 at 9:35 AM PST: [zh] sync deprecation-guide.md (f0da19dfa2)