验证准入策略(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.maxReplicas
。
spec.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
中的 paramKind
和 ValidatingAdmissionPolicyBinding
中的 paramRef
所引用的资源有 read
权限。
请注意,如果 paramKind
中的资源没能通过 restmapper 解析,则用户需要拥有对组的所有资源的
read
访问权限。
失效策略
failurePolicy
定义了如何处理错误配置和准入策略的 CEL 表达式取值为 error 的情况。
允许的值是 Ignore
或 Fail
。
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。
总是可以从对象的根访问的属性有 apiVersion
、kind
、metadata.name
和 metadata.generateName
。
其他元数据属性不能访问。
只有符合 [a-zA-Z_.-/][a-zA-Z0-9_.-/]*
形式的属性名称是可访问的。
可访问的属性名称在表达式中被访问时,根据以下规则进行转义:
转义序列 | 属性名称等效 |
---|---|
__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') |
列表类型为 "set" 或 "map" 的数组上的等价关系比较会忽略元素顺序,即 [1, 2] == [2, 1]。 使用 x-kubernetes-list-type 连接数组时使用列表类型的语义:
- 'set':
X + Y
执行并集,其中X
中所有元素的数组位置被保留,Y
中不相交的元素被追加,保留其元素的偏序关系。 - 'map':
X + Y
执行合并,保留X
中所有键的数组位置,但是当X
和Y
的键集相交时,其值被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 响应中。
目前支持的原因有:Unauthorized
、Forbidden
、Invalid
、RequestEntityTooLarge
。
如果未设置,将在对客户端的响应中使用 StatusReasonInvalid
。