动态准入控制
除了内置准入插件之外,还可以开发准入插件作为扩展,并在运行时配置为 Webhook。本页描述了如何构建、配置、使用和监控准入 Webhook。
什么是准入 Webhook?
准入 Webhook 是接收准入请求并对其进行处理的 HTTP 回调。你可以定义两种类型的准入 Webhook:验证性准入 Webhook 和修改性准入 Webhook。修改性准入 Webhook 最先被调用,可以修改发送到 API 服务器的对象以强制执行自定义默认值。所有对象修改完成后,并且传入对象已由 API 服务器验证后,调用验证性准入 Webhook,它们可以拒绝请求以强制执行自定义策略。
注意
需要确保看到对象的最终状态以强制执行策略的准入 Webhook 应使用验证性准入 Webhook,因为对象在被修改性 Webhook 查看后可能会被修改。实验准入 Webhook
准入 Webhook 本质上是集群控制平面的一部分。你应该非常谨慎地编写和部署它们。如果你打算编写/部署生产级准入 Webhook,请阅读用户指南以获取相关说明。接下来,我们将介绍如何快速实验准入 Webhook。
前提条件
确保已启用 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 准入控制器。此处是通常推荐启用的一组准入控制器。
确保已启用
admissionregistration.k8s.io/v1
API。
编写准入 Webhook 服务器
请参考在 Kubernetes e2e 测试中验证过的准入 Webhook 服务器的实现。该 Webhook 处理 API 服务器发送的 AdmissionReview
请求,并以 AdmissionReview
对象形式返回其决定,版本与收到的版本相同。
参阅Webhook 请求章节以了解发送给 Webhook 的数据的详细信息。
参阅Webhook 响应章节以了解 Webhook 预期返回的数据。
示例准入 Webhook 服务器将 ClientAuth
字段置空,默认为 NoClientCert
。这意味着 Webhook 服务器不认证客户端(假定为 API 服务器)的身份。如果你需要双向 TLS 或其他方式来认证客户端,请参阅如何认证 API 服务器。
部署准入 Webhook 服务
e2e 测试中的 Webhook 服务器通过Deployment API 部署在 Kubernetes 集群中。测试还创建了一个Service 作为 Webhook 服务器的前端。参阅代码。
你也可以将 Webhook 部署在集群外部。你需要相应地更新 Webhook 配置。
动态配置准入 Webhook
你可以通过ValidatingWebhookConfiguration 或MutatingWebhookConfiguration 动态配置哪些资源受哪些准入 Webhook 的处理。
以下是一个示例 ValidatingWebhookConfiguration
,修改性 Webhook 配置类似。参阅Webhook 配置章节以了解每个配置字段的详细信息。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "pod-policy.example.com"
webhooks:
- name: "pod-policy.example.com"
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: "example-namespace"
name: "example-service"
caBundle: <CA_BUNDLE>
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
注意
你必须将上述示例中的<CA_BUNDLE>
替换为一个有效的 CA Bundle,它是 PEM 编码的(字段值是 Base64 编码的)CA Bundle,用于验证 Webhook 的服务器证书。scope
字段指定是只有集群作用域资源("Cluster")还是命名空间作用域资源("Namespaced")将匹配此规则。"∗" 表示没有作用域限制。
注意
使用clientConfig.service
时,服务器证书必须对 <svc_name>.<svc_namespace>.svc
有效。注意
Webhook 调用的默认超时时间为 10 秒。你可以设置timeout
,并且鼓励为 Webhook 使用较短的超时。如果 Webhook 调用超时,请求将根据 Webhook 的失败策略进行处理。当 API 服务器收到与 rules
中任何一项指定的规则匹配的请求时,API 服务器会向 Webhook 发送一个 admissionReview
请求,如 clientConfig
中指定的那样。
创建 Webhook 配置后,系统需要几秒钟来应用新配置。
认证 API 服务器
如果你的准入 Webhook 需要认证,你可以配置 API 服务器使用基本认证、Bearer Token 或证书来向 Webhook 进行自身认证。完成配置需要三个步骤。
启动 API 服务器时,通过
--admission-control-config-file
标志指定准入控制配置文件的位置。在准入控制配置文件中,指定 MutatingAdmissionWebhook 控制器和 ValidatingAdmissionWebhook 控制器应从哪里读取凭证。凭证存储在 kubeConfig 文件中(是的,与 kubectl 使用的模式相同),所以字段名是
kubeConfigFile
。以下是一个示例准入控制配置文件
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig-file>"
- name: MutatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig-file>"
# Deprecated in v1.17 in favor of apiserver.config.k8s.io/v1
apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
# Deprecated in v1.17 in favor of apiserver.config.k8s.io/v1, kind=WebhookAdmissionConfiguration
apiVersion: apiserver.config.k8s.io/v1alpha1
kind: WebhookAdmission
kubeConfigFile: "<path-to-kubeconfig-file>"
- name: MutatingAdmissionWebhook
configuration:
# Deprecated in v1.17 in favor of apiserver.config.k8s.io/v1, kind=WebhookAdmissionConfiguration
apiVersion: apiserver.config.k8s.io/v1alpha1
kind: WebhookAdmission
kubeConfigFile: "<path-to-kubeconfig-file>"
有关 AdmissionConfiguration
的更多信息,请参阅AdmissionConfiguration (v1) 参考。参阅Webhook 配置章节以了解每个配置字段的详细信息。
在 kubeConfig 文件中,提供凭证
apiVersion: v1
kind: Config
users:
# name should be set to the DNS name of the service or the host (including port) of the URL the webhook is configured to speak to.
# If a non-443 port is used for services, it must be included in the name when configuring 1.16+ API servers.
#
# For a webhook configured to speak to a service on the default port (443), specify the DNS name of the service:
# - name: webhook1.ns1.svc
# user: ...
#
# For a webhook configured to speak to a service on non-default port (e.g. 8443), specify the DNS name and port of the service in 1.16+:
# - name: webhook1.ns1.svc:8443
# user: ...
# and optionally create a second stanza using only the DNS name of the service for compatibility with 1.15 API servers:
# - name: webhook1.ns1.svc
# user: ...
#
# For webhooks configured to speak to a URL, match the host (and port) specified in the webhook's URL. Examples:
# A webhook with `url: https://www.example.com`:
# - name: www.example.com
# user: ...
#
# A webhook with `url: https://www.example.com:443`:
# - name: www.example.com:443
# user: ...
#
# A webhook with `url: https://www.example.com:8443`:
# - name: www.example.com:8443
# user: ...
#
- name: 'webhook1.ns1.svc'
user:
client-certificate-data: "<pem encoded certificate>"
client-key-data: "<pem encoded key>"
# The `name` supports using * to wildcard-match prefixing segments.
- name: '*.webhook-company.org'
user:
password: "<password>"
username: "<name>"
# '*' is the default match.
- name: '*'
user:
token: "<token>"
当然,你需要设置 Webhook 服务器来处理这些认证请求。
Webhook 请求和响应
请求
Webhook 作为 POST 请求发送,带 Content-Type: application/json
,请求体中包含一个 admission.k8s.io
API 组中的 AdmissionReview
API 对象,序列化为 JSON。
Webhook 可以通过其配置中的 admissionReviewVersions
字段指定它们接受哪个版本的 AdmissionReview
对象。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
admissionReviewVersions: ["v1", "v1beta1"]
admissionReviewVersions
是创建 Webhook 配置时的必需字段。Webhook 被要求至少支持当前和之前 API 服务器理解的一个 AdmissionReview
版本。
API 服务器会发送 admissionReviewVersions
字段列表中的第一个支持的版本。如果列表中的任何版本都不受 API 服务器支持,则不允许创建该配置。如果 API 服务器遇到一个先前创建的 Webhook 配置,但该配置不支持 API 服务器已知任何 AdmissionReview
服务器知道如何发送的版本,则尝试调用 Webhook 将会失败并受到失败策略的影响。
这个示例展示了一个 AdmissionReview
对象中包含的数据,用于更新 apps/v1
Deployment
的 scale
子资源的请求。
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
# Random uid uniquely identifying this admission call
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002",
# Fully-qualified group/version/kind of the incoming object
"kind": {
"group": "autoscaling",
"version": "v1",
"kind": "Scale"
},
# Fully-qualified group/version/kind of the resource being modified
"resource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
# Subresource, if the request is to a subresource
"subResource": "scale",
# Fully-qualified group/version/kind of the incoming object in the original request to the API server
# This only differs from `kind` if the webhook specified `matchPolicy: Equivalent` and the original
# request to the API server was converted to a version the webhook registered for
"requestKind": {
"group": "autoscaling",
"version": "v1",
"kind": "Scale"
},
# Fully-qualified group/version/kind of the resource being modified in the original request to the API server
# This only differs from `resource` if the webhook specified `matchPolicy: Equivalent` and the original
# request to the API server was converted to a version the webhook registered for
"requestResource": {
"group": "apps",
"version": "v1",
"resource": "deployments"
},
# Subresource, if the request is to a subresource
# This only differs from `subResource` if the webhook specified `matchPolicy: Equivalent` and the original
# request to the API server was converted to a version the webhook registered for
"requestSubResource": "scale",
# Name of the resource being modified
"name": "my-deployment",
# Namespace of the resource being modified, if the resource is namespaced (or is a Namespace object)
"namespace": "my-namespace",
# operation can be CREATE, UPDATE, DELETE, or CONNECT
"operation": "UPDATE",
"userInfo": {
# Username of the authenticated user making the request to the API server
"username": "admin",
# UID of the authenticated user making the request to the API server
"uid": "014fbff9a07c",
# Group memberships of the authenticated user making the request to the API server
"groups": [
"system:authenticated",
"my-admin-group"
],
# Arbitrary extra info associated with the user making the request to the API server
# This is populated by the API server authentication layer
"extra": {
"some-key": [
"some-value1",
"some-value2"
]
}
},
# object is the new object being admitted. It is null for DELETE operations
"object": {
"apiVersion": "autoscaling/v1",
"kind": "Scale"
},
# oldObject is the existing object. It is null for CREATE and CONNECT operations
"oldObject": {
"apiVersion": "autoscaling/v1",
"kind": "Scale"
},
# options contain the options for the operation being admitted, like meta.k8s.io/v1 CreateOptions,
# UpdateOptions, or DeleteOptions. It is null for CONNECT operations
"options": {
"apiVersion": "meta.k8s.io/v1",
"kind": "UpdateOptions"
},
# dryRun indicates the API request is running in dry run mode and will not be persisted
# Webhooks with side effects should avoid actuating those side effects when dryRun is true
"dryRun": false
}
}
响应
Webhook 响应 HTTP 状态码 200,Content-Type: application/json
,请求体中包含一个 AdmissionReview
对象(版本与收到的版本相同),其中 response
节已填充,并序列化为 JSON。
至少,response
节必须包含以下字段
uid
,复制自发送给 Webhook 的request.uid
allowed
,设置为true
或false
Webhook 允许请求的最小响应示例
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true
}
}
Webhook 拒绝请求的最小响应示例
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": false
}
}
拒绝请求时,Webhook 可以使用 status
字段自定义返回给用户的 HTTP 状态码和消息。指定的状态对象将返回给用户。参阅API 文档以了解 status
类型的详细信息。拒绝请求的响应示例,自定义呈现给用户的 HTTP 状态码和消息
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": false,
"status": {
"code": 403,
"message": "You cannot do this because it is Tuesday and your name starts with A"
}
}
}
允许请求时,修改性准入 Webhook 还可以选择修改传入的对象。这是通过使用响应中的 patch
和 patchType
字段完成的。当前唯一支持的 patchType
是 JSONPatch
。参阅 JSON patch 文档以获取更多详细信息。对于 patchType: JSONPatch
,patch
字段包含一个 Base64 编码的 JSON patch 操作数组。
例如,一个设置 spec.replicas
的单个 Patch 操作是 [{"op": "add", "path": "/spec/replicas", "value": 3}]
Base64 编码后,这将是 W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0=
因此,Webhook 添加该标签的响应将是
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true,
"patchType": "JSONPatch",
"patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
}
}
准入 Webhook 可以选择返回警告消息,这些消息会以 HTTP Warning
Header 的形式返回给请求客户端,警告码为 299。警告可以随允许或拒绝的准入响应一起发送。
如果你正在实现一个返回警告的 Webhook
- 不要在消息中包含 "Warning:" 前缀
- 使用警告消息描述发出 API 请求的客户端应纠正或注意的问题
- 如果可能,将警告限制在 120 个字符以内
注意
超过 256 个字符的单个警告消息在返回客户端之前可能会被 API 服务器截断。如果添加的警告消息总计超过 4096 个字符(来自所有来源),额外的警告消息将被忽略。{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "<value from request.uid>",
"allowed": true,
"warnings": [
"duplicate envvar entries specified with name MY_ENV",
"memory request less than 4MB specified for container mycontainer, which will not start successfully"
]
}
}
Webhook 配置
要注册准入 Webhook,请创建 MutatingWebhookConfiguration
或 ValidatingWebhookConfiguration
API 对象。MutatingWebhookConfiguration
或 ValidatingWebhookConfiguration
对象的名称必须是有效的DNS 子域名称。
每个配置可以包含一个或多个 Webhook。如果在单个配置中指定了多个 Webhook,每个都必须给一个唯一的名称。这是为了使生成的审计日志和指标更容易与活动配置匹配。
每个 Webhook 定义了以下内容。
匹配请求:规则
每个 Webhook 必须指定一个规则列表,用于确定是否应将发往 API 服务器的请求发送到该 Webhook。每条规则指定一个或多个操作、apiGroups、apiVersions、资源以及资源作用域
operations
列出一个或多个要匹配的操作。可以是"CREATE"
、"UPDATE"
、"DELETE"
、"CONNECT"
或"*"
匹配所有。apiGroups
列出一个或多个要匹配的 API 组。""
是核心 API 组。"*"
匹配所有 API 组。apiVersions
列出一个或多个要匹配的 API 版本。"*"
匹配所有 API 版本。resources
列出一个或多个要匹配的资源。"*"
匹配所有资源,但不匹配子资源。"*/*"
匹配所有资源和子资源。"pods/*"
匹配 Pod 的所有子资源。"*/status"
匹配所有 status 子资源。
scope
指定要匹配的作用域。有效值为"Cluster"
、"Namespaced"
和"*"
。子资源匹配其父资源的范围。默认为"*"
。"Cluster"
表示只有集群作用域资源将匹配此规则(Namespace API 对象是集群作用域的)。"Namespaced"
表示只有命名空间作用域资源将匹配此规则。"*"
表示没有作用域限制。
如果传入请求匹配 Webhook 任何 rules
中的任意一项指定的 operations
、groups
、versions
、resources
和 scope
,该请求就会被发送到 Webhook。
以下是其他示例规则,可用于指定应拦截哪些资源。
匹配发往 apps/v1
和 apps/v1beta1
中的 deployments
和 replicasets
的 CREATE
或 UPDATE
请求
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
...
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["apps"]
apiVersions: ["v1", "v1beta1"]
resources: ["deployments", "replicasets"]
scope: "Namespaced"
...
匹配所有 API 组和版本中所有资源(但不包括子资源)的创建请求
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "*"
匹配所有 API 组和版本中所有 status
子资源的更新请求
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
rules:
- operations: ["UPDATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*/status"]
scope: "*"
匹配请求:objectSelector
Webhook 可以通过指定 objectSelector
,可选地根据将被发送的对象的标签来限制拦截哪些请求。如果指定,objectSelector 将针对将被发送到 Webhook 的 object 和 oldObject 进行评估,如果任一对象匹配选择器,则视为匹配。
一个空对象(对于创建,是 oldObject
;对于删除,是 newObject
),或一个不能拥有标签的对象(例如 DeploymentRollback
或 PodProxyOptions
对象)不被视为匹配。
仅当 Webhook 是可选择加入时才使用对象选择器,因为终端用户可能通过设置标签来跳过准入 Webhook。
这个示例展示了一个修改性 Webhook,它将匹配具有标签 foo: bar
的任何资源(但不包括子资源)的 CREATE
请求。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
objectSelector:
matchLabels:
foo: bar
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "*"
参阅标签概念以获取更多标签选择器示例。
匹配请求:namespaceSelector
Webhook 可以通过指定 namespaceSelector
,可选地根据包含命名空间的标签来限制拦截哪些命名空间作用域的资源的请求。
根据命名空间的标签是否匹配选择器,namespaceSelector
决定是否对命名空间作用域资源的请求(或 Namespace 对象)运行 Webhook。如果对象本身是一个命名空间,匹配是针对 object.metadata.labels 进行的。如果对象是除了 Namespace 之外的集群作用域资源,namespaceSelector
没有效果。
这个示例展示了一个修改性 Webhook,它匹配任何命名空间作用域资源的 CREATE
请求,前提是该请求所在的命名空间没有 "runlevel" 标签且其值为 "0" 或 "1"。例如,它会匹配带有标签 runlevel: "2"
的 Namespace 中的 Pod。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
namespaceSelector:
matchExpressions:
- key: runlevel
operator: NotIn
values: ["0","1"]
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "Namespaced"
这个示例展示了一个验证性 Webhook,它匹配任何命名空间作用域资源的 CREATE
请求,前提是该请求所在的命名空间与 "prod" 或 "staging" 环境相关联。例如,它会匹配带有标签 environment: "prod"
的 Namespace 中的 Pod。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["prod","staging"]
rules:
- operations: ["CREATE"]
apiGroups: ["*"]
apiVersions: ["*"]
resources: ["*"]
scope: "Namespaced"
参阅标签概念以获取更多标签选择器示例。
匹配请求:matchPolicy
特性状态:Kubernetes v1.30 [稳定]
(默认启用: true)
API 服务器可以通过多个 API 组或版本提供对象。
例如,如果一个 Webhook 只指定了针对某些 API 组/版本(例如 apiGroups:["apps"], apiVersions:["v1","v1beta1"]
)的规则,而通过另一个 API 组/版本(例如 extensions/v1beta1
)修改该资源的请求,该请求将不会发送到该 Webhook。
matchPolicy
字段让 Webhook 可以定义其rules
如何用于匹配传入请求。允许的值为Exact
或Equivalent
。Exact
表示仅当请求与指定规则完全匹配时才应拦截。
Equivalent
表示如果请求修改了 rules
中列出的资源,即使是通过另一个 API 组或版本,也应拦截。
- 在上述示例中,仅注册了
apps/v1
的 Webhook 可以使用matchPolicy
matchPolicy: Exact
将意味着extensions/v1beta1
请求将不会发送到该 Webhook
matchPolicy: Equivalent
将意味着 extensions/v1beta1
请求将被发送到该 Webhook(并将对象转换为 Webhook 指定的版本:apps/v1
)
建议指定 Equivalent
,并确保在升级在 API 服务器中启用资源的新版本时,Webhook 能够继续拦截预期的资源。
当一个资源停止由 API 服务器提供服务时,它不再被视为与仍然提供服务的该资源的其他版本等效。例如,extensions/v1beta1
的 Deployment 最初被弃用,然后被移除(在 Kubernetes v1.16 中)。
自移除后,具有 apiGroups:["extensions"], apiVersions:["v1beta1"], resources:["deployments"]
规则的 Webhook 不会拦截通过 apps/v1
API 创建的 Deployment。因此,Webhook 应优先注册资源的稳定版本。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ["CREATE","UPDATE","DELETE"]
apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
scope: "Namespaced"
这个示例展示了一个验证性 Webhook,它拦截对 Deployment 的修改(无论 API 组或版本如何),并始终会收到一个 apps/v1
Deployment
对象。
准入 Webhook 的 matchPolicy
字段的默认值是 Equivalent
。
matchConditions
特性状态: Kubernetes v1.30 [稳定]
(默认启用: true)
如果你需要细粒度的请求过滤,可以为 Webhook 定义 match conditions。如果你发现匹配规则、objectSelectors
和 namespaceSelectors
仍然无法提供你想要的 HTTP 调用时机过滤功能,这些条件非常有用。Match conditions 是 CEL 表达式。所有匹配条件必须评估为 true,Webhook 才会被调用。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ['CREATE','UPDATE']
apiGroups: ['*']
apiVersions: ['*']
resources: ['*']
failurePolicy: 'Ignore' # Fail-open (optional)
sideEffects: None
clientConfig:
service:
namespace: my-namespace
name: my-webhook
caBundle: '<omitted>'
# You can have up to 64 matchConditions per webhook
matchConditions:
- name: 'exclude-leases' # Each match condition must have a unique name
expression: '!(request.resource.group == "coordination.k8s.io" && request.resource.resource == "leases")' # Match non-lease resources.
- name: 'exclude-kubelet-requests'
expression: '!("system:nodes" in request.userInfo.groups)' # Match requests made by non-node users.
- name: 'rbac' # Skip RBAC requests, which are handled by the second webhook.
expression: 'request.resource.group != "rbac.authorization.k8s.io"'
# This example illustrates the use of the 'authorizer'. The authorization check is more expensive
# than a simple expression, so in this example it is scoped to only RBAC requests by using a second
# webhook. Both webhooks can be served by the same endpoint.
- name: rbac.my-webhook.example.com
matchPolicy: Equivalent
rules:
- operations: ['CREATE','UPDATE']
apiGroups: ['rbac.authorization.k8s.io']
apiVersions: ['*']
resources: ['*']
failurePolicy: 'Fail' # Fail-closed (the default)
sideEffects: None
clientConfig:
service:
namespace: my-namespace
name: my-webhook
caBundle: '<omitted>'
# You can have up to 64 matchConditions per webhook
matchConditions:
- name: 'breakglass'
# Skip requests made by users authorized to 'breakglass' on this webhook.
# The 'breakglass' API verb does not need to exist outside this check.
expression: '!authorizer.group("admissionregistration.k8s.io").resource("validatingwebhookconfigurations").name("my-webhook.example.com").check("breakglass").allowed()'
注意
以下是一个示例,说明了 match conditions 的几种不同用法你可以在 matchConditions
字段中为每个 Webhook 定义最多 64 个元素。
- Match conditions 可以访问以下 CEL 变量
object
- 传入请求中的对象。对于 DELETE 请求,其值为 null。对象的版本可能会根据matchPolicy 进行转换。oldObject
- 现有对象。对于 CREATE 请求,其值为 null。request
- AdmissionReview 的请求部分,不包括object
和oldObject
。authorizer
- 一个 CEL Authorizer。可用于对请求的主体(已认证用户)执行授权检查。参阅 Kubernetes CEL 库文档中的 Authz 以获取更多详细信息。
authorizer.requestResource
- 配置了请求资源(group、resource、(subresource)、namespace、name)的授权检查的快捷方式。
有关 CEL 表达式的更多信息,请参阅Kubernetes 中的通用表达式语言参考。
- 如果评估 match condition 时发生错误,则永远不会调用 Webhook。是否拒绝请求按如下方式确定
- 如果 **任何** match condition 评估为
false
(无论其他错误如何),API 服务器会跳过该 Webhook。- 否则
- 对于
failurePolicy: Fail
,拒绝请求(不调用 Webhook)。
对于failurePolicy: Ignore
,继续处理请求但跳过该 Webhook。
联系 Webhook
一旦 API 服务器确定应将请求发送到某个 Webhook,它需要知道如何联系该 Webhook。这在 Webhook 配置的 clientConfig
节中指定。
Webhook 可以通过 URL 或 Service 引用进行调用,并可选地包含一个自定义 CA Bundle,用于验证 TLS 连接。
URL
url
以标准 URL 格式(scheme://host:port/path
)提供 Webhook 的位置。
host
不应指向集群中运行的 Service;应使用 Service 引用,通过指定 service
字段而非直接指定 host。在某些 API 服务器中,host 可能会通过外部 DNS 进行解析(例如,kube-apiserver
无法解析集群内 DNS,因为这会违反分层原则)。host
也可以是 IP 地址。
请注意,使用 localhost
或 127.0.0.1
作为 host
是有风险的,除非你非常小心地在所有运行可能需要调用此 Webhook 的 API 服务器的主机上运行此 Webhook。此类安装可能不可移植或不易在新集群中运行。
方案必须是 "https";URL 必须以 "https://" 开头。
尝试使用用户或基本认证(例如 user:password@
)不被允许。Fragment(#...
)和查询参数(?...
)也不允许。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
clientConfig:
url: "https://my-webhook.example.com:9443/my-webhook-path"
以下是一个配置为调用 URL 的修改性 Webhook 示例(并期望使用系统信任根验证 TLS 证书,因此不指定 caBundle)
Service 引用
Webhook 配置中 clientConfig
中的 service
节是对此 Webhook 的 Service 引用。如果 Webhook 在集群内部运行,则应使用 service
而不是 url
。Service 的命名空间和名称是必需的。端口是可选的,默认为 443。路径是可选的,默认为 "/"。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
clientConfig:
caBundle: <CA_BUNDLE>
service:
namespace: my-service-namespace
name: my-service-name
path: /my-path
port: 1234
注意
以下是一个配置为调用端口为 "1234"、子路径为 "/my-path" 的 Service 的修改性 Webhook 示例,并使用自定义 CA Bundle 针对 ServerNamemy-service-name.my-service-namespace.svc
验证 TLS 连接。你必须将上述示例中的 <CA_BUNDLE>
替换为一个有效的 PEM 编码的 CA Bundle,用于验证 Webhook 的服务器证书。
副作用
Webhook 通常仅对发送给它们的 AdmissionReview
中包含的内容进行操作。然而,一些 Webhook 在处理准入请求时会进行带外更改(即副作用)。
会进行带外更改(“副作用”)的 Webhook 还必须有一个协调机制(例如控制器),该机制定期确定世界的实际状态,并调整被准入 Webhook 修改的带外数据以反映实际情况。这是因为对准入 Webhook 的调用不能保证被准入的对象会按原样持久化,或者根本不会持久化。后续的 Webhook 可以修改对象的内容,写入存储时可能遇到冲突,或者服务器在持久化对象之前可能断电。
此外,具有副作用的 Webhook 在处理 dryRun: true
准入请求时必须跳过这些副作用。Webhook 必须明确指明当使用 dryRun
运行时不会产生副作用,否则 dry-run 请求将不会发送到 Webhook,而是 API 请求会失败。
- Webhook 使用其配置中的
sideEffects
字段来指明它们是否有副作用。 None
: 调用该 Webhook 将不会有副作用。
NoneOnDryRun
: 调用该 Webhook 可能会有副作用,但如果一个带有 dryRun: true
的请求发送到 Webhook,Webhook 将会抑制副作用(Webhook 是 dryRun
感知的)。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
sideEffects: NoneOnDryRun
以下是一个验证性 Webhook 示例,指明它在处理 dryRun: true
请求时没有副作用。
超时
因为 Webhook 会增加 API 请求的延迟,它们应该尽可能快地进行评估。timeoutSeconds
允许配置 API 服务器在将调用视为失败之前,应等待 Webhook 响应多长时间。
如果在 Webhook 响应之前超时到期,Webhook 调用将被忽略或 API 调用将被拒绝,具体取决于失败策略。
超时值必须介于 1 到 30 秒之间。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
timeoutSeconds: 2
以下是一个具有自定义超时时间为 2 秒的验证性 Webhook 示例。
准入 Webhook 的超时默认值为 10 秒。
重复调用策略
为使变更准入插件能够观察到其他插件所做的更改,如果变更Webhook修改了一个对象,内置的变更准入插件会重新运行。同时,变更Webhook可以指定reinvocationPolicy
来控制它们是否也重新调用。
reinvocationPolicy
可以设置为Never
或IfNeeded
。默认值为Never
。
Never
:在单次准入评估中,该Webhook不能被调用多次。IfNeeded
:如果在初始Webhook调用后,被准入的对象被其他准入插件修改,该Webhook可能会作为准入评估的一部分再次被调用。
需要注意的重要元素包括:
- 额外调用的次数不保证恰好为一次。
- 如果额外调用导致对象进一步修改,不保证Webhook会被再次调用。
- 使用此选项的Webhook可能会被重新排序,以最大限度地减少额外调用的次数。
- 为了在所有变更保证完成后验证对象,请改用校验准入Webhook(推荐用于具有副作用的Webhook)。
以下是一个变更Webhook的示例,该Webhook选择在后续准入插件修改对象时被重新调用。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
reinvocationPolicy: IfNeeded
变更Webhook必须是幂等的,能够成功处理它们已经准入并可能修改过的对象。这对于所有变更准入Webhook来说都是必需的,因为它们对对象所做的任何更改都可能已经存在于用户提供的对象中,但这对于选择重新调用的Webhook来说至关重要。
故障策略
failurePolicy
定义了如何处理来自准入Webhook的未知错误和超时错误。允许的值为Ignore
或Fail
。
Ignore
表示忽略调用Webhook时发生的错误,并允许API请求继续。Fail
表示调用Webhook时发生的错误会导致准入失败,并拒绝API请求。
以下是一个变更Webhook的配置示例,如果在调用准入Webhook时遇到错误,将拒绝API请求。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
failurePolicy: Fail
准入Webhook的默认failurePolicy
为Fail
。
监控准入Webhook
API服务器提供了监控准入Webhook行为的方法。这些监控机制有助于集群管理员回答以下问题:
哪个变更Webhook修改了API请求中的对象?
变更Webhook对对象应用了哪些更改?
哪些Webhook经常拒绝API请求?拒绝的原因是什么?
变更Webhook审计注解
有时了解哪个变更Webhook修改了API请求中的对象以及Webhook应用了哪些更改会很有用。
Kubernetes API服务器在每次调用变更Webhook时都会执行审计。每次调用都会生成一个审计注解,记录请求对象是否因调用而被修改,并且可以选择生成一个注解,记录Webhook准入响应中应用的补丁。这些注解设置在给定请求在其执行给定阶段的审计事件中,然后根据特定策略进行预处理并写入后端。
事件的审计级别决定了哪些注解会被记录:
在
Metadata
或更高审计级别,会记录一个键为mutation.webhook.admission.k8s.io/round_{round idx}_index_{order idx}
的注解,其JSON载荷指示Webhook被调用处理给定请求,以及它是否修改了对象。例如,以下注解记录了一个被重新调用的Webhook。该Webhook在变更Webhook链中排序第三,并且在调用期间没有修改请求对象。
# the audit event recorded { "kind": "Event", "apiVersion": "audit.k8s.io/v1", "annotations": { "mutation.webhook.admission.k8s.io/round_1_index_2": "{\"configuration\":\"my-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook.example.com\",\"mutated\": false}" # other annotations ... } # other fields ... }
# the annotation value deserialized { "configuration": "my-mutating-webhook-configuration.example.com", "webhook": "my-webhook.example.com", "mutated": false }
以下注解记录了一个在第一轮被调用的Webhook。该Webhook在变更Webhook链中排序第一,并且在调用期间修改了请求对象。
# the audit event recorded { "kind": "Event", "apiVersion": "audit.k8s.io/v1", "annotations": { "mutation.webhook.admission.k8s.io/round_0_index_0": "{\"configuration\":\"my-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook-always-mutate.example.com\",\"mutated\": true}" # other annotations ... } # other fields ... }
# the annotation value deserialized { "configuration": "my-mutating-webhook-configuration.example.com", "webhook": "my-webhook-always-mutate.example.com", "mutated": true }
在
Request
或更高审计级别,会记录一个键为patch.webhook.admission.k8s.io/round_{round idx}_index_{order idx}
的注解,其JSON载荷指示Webhook被调用处理给定请求,以及应用到请求对象的补丁。例如,以下注解记录了一个被重新调用的Webhook。该Webhook在变更Webhook链中排序第四,并响应了一个应用到请求对象的JSON补丁。
# the audit event recorded { "kind": "Event", "apiVersion": "audit.k8s.io/v1", "annotations": { "patch.webhook.admission.k8s.io/round_1_index_3": "{\"configuration\":\"my-other-mutating-webhook-configuration.example.com\",\"webhook\":\"my-webhook-always-mutate.example.com\",\"patch\":[{\"op\":\"add\",\"path\":\"/data/mutation-stage\",\"value\":\"yes\"}],\"patchType\":\"JSONPatch\"}" # other annotations ... } # other fields ... }
# the annotation value deserialized { "configuration": "my-other-mutating-webhook-configuration.example.com", "webhook": "my-webhook-always-mutate.example.com", "patchType": "JSONPatch", "patch": [ { "op": "add", "path": "/data/mutation-stage", "value": "yes" } ] }
准入Webhook指标
API服务器通过`/metrics`端点暴露Prometheus指标,这些指标可用于监控和诊断API服务器状态。以下指标记录了与准入Webhook相关的状态。
API服务器准入Webhook拒绝计数
有时了解哪些准入Webhook经常拒绝API请求以及拒绝的原因会很有用。
API服务器暴露了一个记录准入Webhook拒绝的Prometheus计数器指标。这些指标通过标签标识Webhook拒绝的原因:
name
:拒绝请求的Webhook名称。operation
:请求的操作类型,可以是CREATE
、UPDATE
、DELETE
和CONNECT
中的一个。type
:准入Webhook类型,可以是admit
和validating
中的一个。error_type
:标识在导致拒绝的Webhook调用期间是否发生错误。其值可以是以下之一:calling_webhook_error
:准入Webhook发生未知错误或超时错误,并且Webhook的故障策略设置为Fail
。no_error
:没有错误发生。Webhook在准入响应中通过设置allowed: false
拒绝了请求。指标标签rejection_code
记录了准入响应中设置的.status.code
。apiserver_internal_error
:API服务器内部错误。
rejection_code
:Webhook拒绝请求时,在准入响应中设置的HTTP状态码。
拒绝计数指标示例
# HELP apiserver_admission_webhook_rejection_count [ALPHA] Admission webhook rejection count, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify an error type (calling_webhook_error or apiserver_internal_error if an error occurred; no_error otherwise) and optionally a non-zero rejection code if the webhook rejects the request with an HTTP status code (honored by the apiserver when the code is greater or equal to 400). Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.
# TYPE apiserver_admission_webhook_rejection_count counter
apiserver_admission_webhook_rejection_count{error_type="calling_webhook_error",name="always-timeout-webhook.example.com",operation="CREATE",rejection_code="0",type="validating"} 1
apiserver_admission_webhook_rejection_count{error_type="calling_webhook_error",name="invalid-admission-response-webhook.example.com",operation="CREATE",rejection_code="0",type="validating"} 1
apiserver_admission_webhook_rejection_count{error_type="no_error",name="deny-unwanted-configmap-data.example.com",operation="CREATE",rejection_code="400",type="validating"} 13
最佳实践和警告
有关编写变更准入Webhook的建议和注意事项,请参阅准入Webhook最佳实践。