动态准入控制
除了 编译的准入插件 之外,准入插件还可以作为扩展开发,并在运行时配置为 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 服务器
请参考 准入 Webhook 服务器 的实现,该服务器在 Kubernetes e2e 测试中得到验证。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
字段包含一个 JSON patch 操作的 Base64 编码数组。
例如,一个将 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 配置
要注册接纳 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"
表示只有集群范围的资源才会匹配此规则(命名空间 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 的对象和 oldObject 进行评估,如果任一对象匹配选择器,则认为匹配。
空对象(在创建的情况下为 oldObject
,在删除的情况下为 newObject
),或无法拥有标签的对象(如 DeploymentRollback
或 PodProxyOptions
对象)不被认为匹配。
仅当 Webhook 是选择加入时才使用对象选择器,因为最终用户可以通过设置标签来跳过接纳 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
根据命名空间的标签是否匹配选择器来决定是否在对命名空间范围的资源(或命名空间对象)的请求上运行 Webhook。如果对象本身是命名空间,则匹配将在 object.metadata.labels 上执行。如果对象是集群范围的资源,而不是命名空间,则 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"
接纳 Webhook 的 matchPolicy
默认值为 Equivalent
。
匹配请求:matchConditions
Kubernetes v1.30 [稳定]
如果您需要细粒度的请求过滤,则可以为 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 或服务引用进行调用,并且可以选择性地包含一个自定义 CA 捆绑包,用于验证 TLS 连接。
URL
url
给出了 Webhook 的位置,采用标准 URL 格式(scheme://host:port/path
)。
host
不应该引用在集群中运行的服务;请通过指定 service
字段来使用服务引用。主机可能通过外部 DNS 在某些 API 服务器中解析(例如,kube-apiserver
无法解析集群内 DNS,因为这将是分层违规)。host
也可能是 IP 地址。
请注意,除非您非常小心地将此 Webhook 运行在所有运行 API 服务器的主机上(这些 API 服务器可能需要调用此 Webhook),否则使用 localhost
或 127.0.0.1
作为 host
是有风险的。此类安装很可能不可移植,或者不容易在新的集群中运行。
方案必须是“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 捆绑包替换上面的示例中的<CA_BUNDLE>
,该捆绑包是用于验证 Webhook 服务器证书的 PEM 编码的 CA 捆绑包。副作用
Webhook 通常只对发送给它们的 AdmissionReview
的内容进行操作。然而,一些 Webhook 会在处理接纳请求的过程中进行带外更改。
进行带外更改(“副作用”)的 Webhook 还必须具有一个协调机制(如控制器),该机制定期确定世界的实际状态,并调整接纳 Webhook 修改的带外数据以反映现实。这是因为对接纳 Webhook 的调用不能保证被接纳的对象将按原样或完全持久化。后面的 Webhook 可以修改对象的内容,在写入存储时可能会遇到冲突,或者服务器可能会在持久化对象之前关闭。
此外,具有副作用的 Webhook 在处理 dryRun: true
准入请求时必须跳过这些副作用。Webhook 必须明确表明它在使用 dryRun
运行时不会产生副作用,否则 dry-run 请求将不会发送到 Webhook,并且 API 请求将失败。
Webhook 使用 Webhook 配置中的 sideEffects
字段来指示它们是否具有副作用。
None
:调用 Webhook 不会产生任何副作用。NoneOnDryRun
:调用 Webhook 可能会有副作用,但如果向 Webhook 发送了带有dryRun: true
的请求,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 可能会作为准入评估的一部分再次被调用。
需要注意的重要因素是
- 额外的调用次数不保证正好为一次。
- 如果额外的调用导致对对象进行进一步修改,则不保证再次调用 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 请求,如果 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 中设置了特定标签(例如 "env": "prod"
)时才允许 CREATE
Pod 请求。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
命名空间。