动态准入控制
除了 编译后的准入插件,准入插件还可以作为扩展进行开发,并在运行时作为配置的 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 服务器通过 部署 API 部署在 Kubernetes 集群中。测试还会创建一个 服务 作为 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 捆绑包替换上述示例中的<CA_BUNDLE>
,该捆绑包是用于验证 Webhook 服务器证书的 PEM 编码(字段值为 Base64 编码)的 CA 捆绑包。scope
字段指定仅集群范围资源 (“Cluster”) 或命名空间范围资源 (“Namespaced”) 将匹配此规则。“*” 表示没有范围限制。
注意
当使用clientConfig.service
时,服务器证书必须对 <svc_name>.<svc_namespace>.svc
有效。注意
Webhook 调用的默认超时时间为 10 秒,您可以设置timeout
,并且建议对 Webhook 使用较短的超时时间。如果 Webhook 调用超时,则根据 Webhook 的失败策略处理请求。当 API 服务器收到与某个 rules
相匹配的请求时,API 服务器会将 admissionReview
请求发送到 clientConfig
中指定的 Webhook。
创建 Webhook 配置后,系统将花费几秒钟来遵守新的配置。
验证 API 服务器
如果您的准入 Webhook 需要身份验证,您可以配置 API 服务器使用基本身份验证、承载令牌或证书来验证它自身对 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
列表中的第一个 AdmissionReview
版本。如果列表中没有一个版本被 API 服务器支持,则不允许创建配置。如果 API 服务器遇到以前创建的但不支持 API 服务器知道的任何 AdmissionReview
版本的 Webhook 配置,则尝试调用 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 and should be included
# if any SubjectAccessReview checks are performed by the webhook.
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 contains 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.
# See http://k8s.io/docs/reference/using-api/api-concepts/#make-a-dry-run-request for more details.
dryRun: False
响应
Webhook 会以 200 HTTP 状态代码、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 代码和消息。指定的 status 对象将返回给用户。有关 status
类型的详细信息,请参阅 API 文档。禁止请求的响应示例,自定义了返回给用户的 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
设置为 3
的单一 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
标头(警告代码为 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 配置
要注册 Admission Webhook,请创建 MutatingWebhookConfiguration
或 ValidatingWebhookConfiguration
API 对象。MutatingWebhookConfiguration
或 ValidatingWebhookConfiguration
对象的名称必须是有效的 DNS 子域名。
每个配置可以包含一个或多个 Webhook。如果在一个配置中指定了多个 Webhook,则每个 Webhook 必须具有唯一的名称。这是为了使生成的审计日志和指标更容易与活动配置匹配。
每个 Webhook 定义以下内容。
匹配请求:规则
每个 Webhook 必须指定一个规则列表,用于确定是否将 API 服务器的请求发送到 Webhook。每个规则指定一个或多个操作、API 组、API 版本和资源,以及资源范围。
operations
列出要匹配的一个或多个操作。可以是"CREATE"
、"UPDATE"
、"DELETE"
、"CONNECT"
或"*"
以匹配所有操作。apiGroups
列出要匹配的一个或多个 API 组。""
是核心 API 组。"*"
匹配所有 API 组。apiVersions
列出要匹配的一个或多个 API 版本。"*"
匹配所有 API 版本。resources
列出要匹配的一个或多个资源。"*"
匹配所有资源,但不匹配子资源。"*/*"
匹配所有资源和子资源。"pods/*"
匹配 Pod 的所有子资源。"*/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 是可选时才使用对象选择器,因为最终用户可以通过设置标签来跳过 Admission Webhook。
此示例显示了一个 mutating 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
不会产生任何影响。
此示例显示了一个 mutating Webhook,它匹配在没有 "runlevel" 标签为 "0" 或 "1" 的命名空间中创建的任何命名空间资源的 CREATE
操作。
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"
此示例显示了一个 validating Webhook,它匹配在与 "environment" 为 "prod" 或 "staging" 关联的命名空间中创建的任何命名空间资源的 CREATE
操作。
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
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
请求不会被发送到 WebhookmatchPolicy: Equivalent
表示extensions/v1beta1
请求将被发送到 Webhook(对象将被转换为 Webhook 指定的版本:apps/v1
)
建议指定 Equivalent
,它可以确保当升级在 API 服务器中启用资源的新版本时,Webhook 继续拦截它们期望的资源。
当资源不再由 API 服务器提供服务时,它不再被视为与仍在提供服务的该资源的其他版本等效。例如,extensions/v1beta1
部署首先被弃用,然后被删除(在 Kubernetes v1.16 中)。
由于该删除,具有 apiGroups:["extensions"], apiVersions:["v1beta1"], resources:["deployments"]
规则的 Webhook 不会拦截通过 apps/v1
API 创建的部署。出于这个原因,Webhook 应该优先注册资源的稳定版本。
此示例显示了一个 validating Webhook,它拦截对部署的修改(无论 API 组或版本如何),并且始终被发送 apps/v1
Deployment
对象。
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"
Admission Webhook 的 matchPolicy
默认值为 Equivalent
。
匹配请求:matchConditions
Kubernetes v1.30 [stable]
如果您需要细粒度的请求过滤,则可以为 Webhook 定义匹配条件。如果您发现匹配规则、objectSelectors
和 namespaceSelectors
仍然没有提供您想要的过滤条件,那么匹配条件就很有用。匹配条件是 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()'
注意
您可以在每个 Webhook 的matchConditions
字段中定义最多 64 个元素。匹配条件可以访问以下 CEL 变量
object
- 来自传入请求的对象。对于 DELETE 请求,该值为 null。对象版本可能会根据 matchPolicy 进行转换。oldObject
- 现有的对象。对于 CREATE 请求,该值为 null。request
- AdmissionReview 的请求部分,不包括object
和oldObject
。authorizer
- CEL 授权者。可用于对请求的主体(经过身份验证的用户)执行授权检查。有关更多详细信息,请参阅 Kubernetes CEL 库文档中的 Authz。authorizer.requestResource
- 用于配置请求资源(组、资源、(子资源)、命名空间、名称)的授权检查的快捷方式。
有关 CEL 表达式的更多信息,请参阅 Kubernetes 中的通用表达式语言参考。
如果在评估匹配条件时出现错误,则 Webhook 永远不会被调用。是否拒绝请求的决定如下:
- 如果任何匹配条件评估为
false
(无论其他错误如何),API 服务器都会跳过 Webhook。 - 否则
- 对于
failurePolicy: Fail
,拒绝请求(不调用 Webhook)。 - 对于
failurePolicy: Ignore
,继续处理请求,但跳过 Webhook。
- 对于
联系 Webhook
一旦 API 服务器确定应将请求发送到 Webhook,它需要知道如何联系 Webhook。这在 Webhook 配置的 clientConfig
部分中指定。
Webhook 可以通过 URL 或服务引用进行调用,并且可以选择性地包含用于验证 TLS 连接的自定义 CA 捆绑包。
URL
url
给出 Webhook 的位置,以标准 URL 形式(scheme://host:port/path
)。
host
不应引用在集群中运行的服务;请通过指定 service
字段来使用服务引用。主机可能通过外部 DNS 在某些 API 服务器中解析(例如,kube-apiserver
无法解析集群内 DNS,因为这将是分层违规)。host
也可以是 IP 地址。
请注意,除非您非常小心地在运行 API 服务器的所有主机上运行此 Webhook,否则将 localhost
或 127.0.0.1
用作 host
是有风险的,这些 API 服务器可能需要调用此 Webhook。此类安装很可能是不可移植的或不能轻松地在新的集群中运行。
方案必须是 "https";URL 必须以 "https://" 开头。
尝试使用用户或基本身份验证(例如 user:password@
)是不允许的。片段 (#...
) 和查询参数 (?...
) 也不允许。
以下是一个 mutating Webhook 的示例,它配置为调用 URL(并期望使用系统信任根来验证 TLS 证书,因此不指定 caBundle)
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
clientConfig:
url: "https://my-webhook.example.com:9443/my-webhook-path"
服务引用
clientConfig
中的 service
部分是对此 Webhook 的服务的引用。如果 Webhook 在集群内运行,则应使用 service
而不是 url
。服务命名空间和名称是必需的。端口是可选的,默认为 443。路径是可选的,默认为 "/"。
以下是一个 mutating Webhook 的示例,它配置为调用端口 "1234" 上的子路径 "/my-path" 的服务,并使用自定义 CA 捆绑包针对 ServerName my-service-name.my-service-namespace.svc
验证 TLS 连接。
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
注意
您必须将上述示例中的<CA_BUNDLE>
替换为有效的 CA 捆绑包,该捆绑包是用于验证 Webhook 服务器证书的 PEM 编码 CA 捆绑包。副作用
Webhook 通常只对发送给它们的 AdmissionReview
的内容进行操作。但是,一些 Webhook 会在处理 Admission 请求的过程中进行带外更改。
进行带外更改(“副作用”)的 Webhook 必须还具有一个协调机制(如控制器),该机制会定期确定世界的实际状态,并将 Admission Webhook 修改的带外数据调整为反映现实情况。这是因为对 Admission Webhook 的调用不能保证被接受的对象将按原样或完全持久化。后面的 Webhook 可以修改对象的内容,在写入存储时可能会遇到冲突,或者服务器可能在持久化对象之前关闭。
此外,具有副作用的 Webhook 必须在处理 dryRun: true
Admission 请求时跳过这些副作用。Webhook 必须明确表明它在使用 dryRun
运行时不会产生副作用,否则 dry-run 请求不会被发送到 Webhook,API 请求将失败。
Webhook 使用 Webhook 配置中的 sideEffects
字段来指示它们是否具有副作用。
None
:调用 Webhook 不会产生任何副作用。NoneOnDryRun
: 调用 webhook 可能会有副作用,但如果发送带有dryRun: true
的请求到 webhook,webhook 会抑制副作用(webhook 能够感知dryRun
)。
以下是一个验证 webhook 示例,它表明在 dryRun: true
请求下没有副作用
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
sideEffects: NoneOnDryRun
超时
由于 webhook 会增加 API 请求延迟,因此它们应该尽快完成评估。timeoutSeconds
允许配置 API 服务器在将调用视为失败之前等待 webhook 响应的时间。
如果超时时间在 webhook 响应之前过期,则 webhook 调用将被忽略,或者 API 调用将根据 失败策略 被拒绝。
超时值必须在 1 到 30 秒之间。
以下是一个带有 2 秒自定义超时的验证 webhook 示例
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
timeoutSeconds: 2
准入 webhook 的超时时间默认为 10 秒。
重新调用策略
单个变异准入插件(包括 webhook)排序在所有情况下都不可行(例如,参见 https://issue.k8s.io/64333)。变异 webhook 可以为对象添加新的子结构(例如,在 pod
中添加 container
),而已经运行的其他变异插件可能对这些新结构有意见(例如,在所有容器上设置 imagePullPolicy
)。
为了允许变异准入插件观察其他插件所做的更改,如果变异 webhook 修改了对象,则内置的变异准入插件将重新运行,变异 webhook 可以指定 reinvocationPolicy
来控制是否也重新调用它们。
reinvocationPolicy
可以设置为 Never
或 IfNeeded
。默认值为 Never
。
Never
: webhook 在单个准入评估中不应被调用多次。IfNeeded
: 如果要承认的对象在初始 webhook 调用之后被其他准入插件修改,则 webhook 可以在准入评估中再次被调用。
需要注意的重要元素是
- 额外的调用次数并不保证正好为 1。
- 如果额外的调用导致对象进一步修改,则 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 服务器公开一个 Prometheus 计数器指标,记录准入 webhook 拒绝。这些指标已标记以识别 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 示例
对于
CREATE
pod 请求,将 pod 的字段.spec.securityContext.runAsNonRoot
设置为 true,以强制执行安全最佳实践。对于
CREATE
pod 请求,如果容器的字段.spec.containers[].resources.limits
未设置,则设置默认资源限制。对于
CREATE
pod 请求,如果不存在名为foo-sidecar
的容器,则注入一个名为foo-sidecar
的 sidecar 容器。
在以上情况下,webhook 可以安全地重新调用,或者承认已经设置了这些字段的对象。
非幂等变异准入 webhook 示例
对于
CREATE
pod 请求,注入一个名为foo-sidecar
的 sidecar 容器,并用当前时间戳(例如foo-sidecar-19700101-000000
)对其进行后缀。对于
CREATE
/UPDATE
pod 请求,如果 pod 设置了标签"env"
,则拒绝,否则向 pod 添加"env": "prod"
标签。对于
CREATE
pod 请求,盲目追加一个名为foo-sidecar
的 sidecar 容器,而不检查 pod 中是否已经存在foo-sidecar
容器。
在以上第一个示例中,重新调用 webhook 会导致同一个 sidecar 被多次注入到同一个 pod 中,每次都使用不同的容器名称。类似地,如果 sidecar 已经存在于用户提供的 pod 中,则 webhook 会注入重复的容器。
在以上第二个示例中,重新调用 webhook 会导致 webhook 本身输出失败。
在以上第三个示例中,重新调用 webhook 会导致 pod 规范中出现重复的容器,这将使请求无效,并被 API 服务器拒绝。
拦截对象的所有版本
建议准入 webhook 始终通过将 .webhooks[].matchPolicy
设置为 Equivalent
来拦截对象的版本。还建议准入 webhook 优先注册资源的稳定版本。无法拦截对象的版本会导致某些版本中请求的准入策略无法执行。有关示例,请参阅 匹配请求:matchPolicy。
可用性
建议准入 webhook 尽快完成评估(通常在毫秒内),因为它们会增加 API 请求延迟。建议对 webhook 使用较小的超时时间。有关更多详细信息,请参阅 超时。
建议准入 webhook 利用某种形式的负载均衡,以提供高可用性和性能优势。如果 webhook 在集群内运行,则可以在服务后面运行多个 webhook 后端,以利用服务支持的负载均衡。
保证看到对象的最终状态
需要保证它们看到对象的最终状态才能执行策略的准入 Webhook 应该使用验证准入 Webhook,因为对象可以在被变异 Webhook 看到后进行修改。
例如,变异准入 webhook 被配置为在每个 CREATE
pod 请求上注入一个名为“foo-sidecar”的 sidecar 容器。如果 sidecar 必须存在,则还应配置一个验证准入 webhook 来拦截 CREATE
pod 请求,并验证要创建的对象中是否存在具有预期配置的名称为“foo-sidecar”的容器。
避免自托管 webhook 中的死锁
在集群内部运行的 webhook 如果配置为拦截其自身 pod 启动所需资源,可能会导致其自身部署死锁。
例如,一个可变的准入 Webhook 被配置为仅在 Pod 中设置了特定标签时才允许 CREATE
Pod 请求(例如 "env": "prod"
)。Webhook 服务器运行在一个部署中,该部署没有设置 "env"
标签。当运行 Webhook 服务器 Pod 的节点变得不健康时,Webhook 部署将尝试将 Pod 调度到另一个节点。但是,由于 "env"
标签未设置,请求将被现有的 Webhook 服务器拒绝,迁移无法进行。
建议使用 namespaceSelector 排除运行 Webhook 的命名空间。
副作用
建议准入 Webhook 应尽可能避免副作用,这意味着 Webhook 仅对发送给它们的 AdmissionReview
内容进行操作,而不进行带外更改。如果 Webhook 没有副作用,则应将 .webhooks[].sideEffects
字段设置为 None
。
如果准入评估期间需要副作用,则在处理 dryRun
设置为 true
的 AdmissionReview
对象时必须抑制这些副作用,并且 .webhooks[].sideEffects
字段应设置为 NoneOnDryRun
。有关更多详细信息,请参阅 副作用。
避免对 kube-system 命名空间进行操作
kube-system
命名空间包含由 Kubernetes 系统创建的对象,例如控制平面组件的服务帐户、kube-dns
等 Pod。意外地对 kube-system
命名空间中的请求进行变异或拒绝可能会导致控制平面组件停止运行或引入未知行为。如果您的准入 Webhook 不打算修改 Kubernetes 控制平面的行为,请使用 namespaceSelector
排除对 kube-system
命名空间的拦截。