动态准入控制

除了内置的准入插件之外,准入插件可以作为扩展程序开发,并在运行时配置为 Webhook 运行。此页面介绍了如何构建、配置、使用和监视准入 Webhook。

什么是准入 Webhook?

准入 Webhook 是 HTTP 回调,接收准入请求并对其进行处理。您可以定义两种类型的准入 Webhook,验证准入 Webhook修改准入 Webhook。修改准入 Webhook 会先被调用,并且可以修改发送到 API 服务器的对象以强制执行自定义默认值。在完成所有对象修改,并且 API 服务器验证传入对象之后,会调用验证准入 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

您可以通过ValidatingWebhookConfigurationMutatingWebhookConfiguration动态配置哪些资源受制于哪些准入 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

scope 字段指定是否只有集群范围的资源(“Cluster”)或命名空间范围的资源(“Namespaced”)会匹配此规则。 “*”表示没有范围限制。

当 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 格式序列化为正文。

Webhooks 可以使用其配置中的 admissionReviewVersions 字段指定它们接受的 AdmissionReview 对象的版本。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
  admissionReviewVersions: ["v1", "v1beta1"]

创建 webhook 配置时,admissionReviewVersions 是必需字段。Webhooks 必须至少支持当前和之前的 API 服务器所理解的一个 AdmissionReview 版本。

API 服务器会发送它们支持的 admissionReviewVersions 列表中的第一个 AdmissionReview 版本。如果列表中的任何版本都不被 API 服务器支持,则不允许创建配置。如果 API 服务器遇到先前创建的 webhook 配置,且该配置不支持 API 服务器知道如何发送的任何 AdmissionReview 版本,则调用 webhook 的尝试将失败,并受制于失败策略

此示例显示了对 apps/v1 Deploymentscale 子资源进行更新请求时,AdmissionReview 对象中包含的数据。

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

响应

Webhooks 以 200 HTTP 状态码、Content-Type: application/json 和包含 AdmissionReview 对象(以它们发送的版本)的主体进行响应,其中 response 节已填充并序列化为 JSON。

至少,response 节必须包含以下字段

  • uid,从发送到 webhook 的 request.uid 复制而来
  • allowed,设置为 truefalse

允许请求的 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 也可以选择修改传入的对象。这可以通过响应中的 patchpatchType 字段来完成。当前唯一支持的 patchTypeJSONPatch。有关更多详细信息,请参阅JSON patch 文档。对于 patchType: JSONPatchpatch 字段包含 JSON patch 操作的 base64 编码数组。

例如,一个设置 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="
  }
}

准入 webhooks 可以选择返回警告消息,这些消息在 HTTP Warning 标头中返回给请求客户端,警告代码为 299。警告可以与允许或拒绝的准入响应一起发送。

如果您正在实现返回警告的 webhook

  • 请勿在消息中包含 "Warning:" 前缀
  • 使用警告消息来描述客户端在发出 API 请求时应纠正或注意的问题
  • 如果可能,请将警告限制为 120 个字符
{
  "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 配置

要注册准入 webhooks,请创建 MutatingWebhookConfigurationValidatingWebhookConfiguration API 对象。MutatingWebhookConfigurationValidatingWebhookConfiguration 对象的名称必须是有效的DNS 子域名

每个配置可以包含一个或多个 webhooks。如果在单个配置中指定了多个 webhooks,则每个 webhook 都必须指定一个唯一的名称。这是为了使生成的审计日志和指标更容易与活动的配置相匹配。

每个 webhook 定义了以下内容。

匹配请求:规则

每个 webhook 必须指定一个规则列表,用于确定是否应将对 API 服务器的请求发送到 webhook。每个规则指定一个或多个操作、apiGroups、apiVersions 和资源以及资源范围

  • operations 列出了要匹配的一个或多个操作。可以是 "CREATE""UPDATE""DELETE""CONNECT""*" 以匹配所有操作。

  • apiGroups 列出了要匹配的一个或多个 API 组。"" 是核心 API 组。"*" 匹配所有 API 组。

  • apiVersions 列出了要匹配的一个或多个 API 版本。"*" 匹配所有 API 版本。

  • resources 列出了要匹配的一个或多个资源。

    • "*" 匹配所有资源,但不匹配子资源。
    • "*/*" 匹配所有资源和子资源。
    • "pods/*" 匹配 pods 的所有子资源。
    • "*/status" 匹配所有状态子资源。
  • scope 指定要匹配的范围。有效值为 "Cluster""Namespaced""*"。子资源匹配其父资源的范围。默认为 "*"

    • "Cluster" 表示只有集群范围的资源才会匹配此规则(命名空间 API 对象是集群范围的)。
    • "Namespaced" 表示只有命名空间资源才会匹配此规则。
    • "*" 表示没有范围限制。

如果传入的请求与 webhook 的任何 rules 的一个指定 operationsgroupsversionsresourcesscope 匹配,则该请求将发送到 webhook。

以下是可用于指定应拦截哪些资源的其他规则示例。

匹配对 apps/v1apps/v1beta1 deploymentsreplicasetsCREATEUPDATE 请求

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

Webhooks 可以选择根据它们将发送的对象的标签来限制拦截哪些请求,方法是指定一个 objectSelector。如果指定了,则 objectSelector 会针对将发送到 webhook 的对象和 oldObject 进行评估,并且如果任一对象匹配选择器,则认为匹配。

一个空对象(在创建的情况下为 oldObject,或在删除的情况下为 newObject),或一个无法拥有标签的对象(例如 DeploymentRollbackPodProxyOptions 对象)不被视为匹配。

仅当 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

Webhooks 可以选择根据包含的命名空间的标签来限制拦截哪些对命名空间资源的请求,方法是指定一个 namespaceSelector

namespaceSelector 根据命名空间的标签是否匹配选择器,决定是否对命名空间资源的请求(或命名空间对象)运行 webhook。如果对象本身是命名空间,则匹配在 object.metadata.labels 上执行。如果对象是命名空间之外的集群范围资源,则 namespaceSelector 不起作用。

此示例显示了一个变异 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"

此示例显示了一个验证 webhook,它匹配一个在与 “prod” 或 “staging” 的 “environment” 关联的命名空间内的任何命名空间资源的 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 来匹配传入的请求。允许的值为 ExactEquivalent

  • Exact 表示只有当请求与指定规则完全匹配时才应拦截该请求。
  • Equivalent 表示如果请求修改了 rules 中列出的资源,即使是通过另一个 API 组或版本,也应拦截该请求。

在上面给出的示例中,仅注册 apps/v1 的 webhook 可以使用 matchPolicy

  • matchPolicy: Exact 表示不会将 extensions/v1beta1 请求发送到 webhook
  • matchPolicy: Equivalent 表示会将 extensions/v1beta1 请求发送到 webhook(对象已转换为 webhook 指定的版本:apps/v1

建议指定 Equivalent,并确保当升级在 API 服务器中启用资源的新版本时,webhooks 继续拦截它们期望的资源。

当资源停止由 API 服务器提供服务时,它不再被认为与仍提供服务的该资源的其他版本等效。例如,extensions/v1beta1 deployments 首先被弃用,然后被删除(在 Kubernetes v1.16 中)。

自删除以来,具有 apiGroups:["extensions"], apiVersions:["v1beta1"], resources:["deployments"] 规则的 webhook 不会拦截通过 apps/v1 API 创建的部署。因此,webhooks 应首选注册资源的稳定版本。

此示例显示了一个验证 webhook,它拦截对 deployments 的修改(无论 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"

准入 webhooks 的 matchPolicy 默认为 Equivalent

匹配请求:matchConditions

功能状态: Kubernetes v1.30 [稳定](默认启用:true)

如果需要细粒度的请求过滤,可以为 webhooks 定义匹配条件。如果您发现匹配规则、objectSelectorsnamespaceSelectors 仍然无法提供您想要的 HTTP 调用时的过滤,这些条件很有用。匹配条件是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()'

匹配条件可以访问以下 CEL 变量

  • object - 来自传入请求的对象。对于 DELETE 请求,该值为 null。对象版本可以根据matchPolicy进行转换。
  • oldObject - 现有的对象。对于 CREATE 请求,该值为 null。
  • request - AdmissionReview 的请求部分,不包括 objectoldObject
  • authorizer - CEL Authorizer。可用于对请求的主体(已验证的用户)执行授权检查。有关更多详细信息,请参阅 Kubernetes CEL 库文档中的 Authz
  • authorizer.requestResource - 使用请求资源(组、资源、(子资源)、命名空间、名称)配置的授权检查的快捷方式。

有关 CEL 表达式的更多信息,请参阅 Kubernetes 中的通用表达式语言参考

如果在评估匹配条件时发生错误,则永远不会调用 Webhook。是否拒绝请求的决定方式如下:

  1. 如果任何匹配条件的评估结果为 false (无论其他错误如何),API 服务器都会跳过 Webhook。
  2. 否则

联系 Webhook

一旦 API 服务器确定应该将请求发送到 Webhook,它就需要知道如何联系 Webhook。这在 Webhook 配置的 clientConfig 节中指定。

Webhook 可以通过 URL 或服务引用进行调用,并且可以选择包含自定义 CA 捆绑包以用于验证 TLS 连接。

URL

url 以标准 URL 形式(scheme://host:port/path)给出 Webhook 的位置。

host 不应引用群集中运行的服务;请通过指定 service 字段来使用服务引用。在某些 API 服务器中,主机可能会通过外部 DNS 解析(例如,kube-apiserver 无法解析集群内 DNS,因为这将是分层违规)。 host 也可以是 IP 地址。

请注意,除非您非常小心地在所有运行可能需要调用此 Webhook 的 API 服务器的主机上运行此 Webhook,否则使用 localhost127.0.0.1 作为 host 是有风险的。此类安装可能无法移植或无法在新集群中轻松运行。

方案必须是 "https";URL 必须以 "https://" 开头。

不允许尝试使用用户或基本身份验证(例如 user:password@)。也不允许使用片段 (#...) 和查询参数 (?...)。

这是一个配置为调用 URL 的 mutating Webhook 的示例(并期望使用系统信任根验证 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。路径是可选的,默认为 "/"。

这是一个配置为在子路径 "/my-path" 上调用端口 "1234" 上的服务,并使用自定义 CA 捆绑包针对 ServerName my-service-name.my-service-namespace.svc 验证 TLS 连接的 mutating Webhook 的示例

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

副作用

Webhook 通常只对发送给它们的 AdmissionReview 的内容进行操作。但是,一些 Webhook 会在处理准入请求时进行带外更改。

进行带外更改(“副作用”)的 Webhook 还必须具有一个协调机制(如控制器),该机制会定期确定世界的实际状态,并调整准入 Webhook 修改的带外数据以反映实际情况。这是因为调用准入 Webhook 并不能保证被准入的对象将按原样持久化,甚至根本不会持久化。后面的 Webhook 可以修改对象的内容,在写入存储时可能会遇到冲突,或者服务器可能会在持久化对象之前断电。

此外,具有副作用的 Webhook 必须在处理 dryRun: true 准入请求时跳过这些副作用。Webhook 必须明确指示在使用 dryRun 运行时它不会产生副作用,否则干运行请求将不会发送到 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 可以设置为 NeverIfNeeded。它默认为 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 的无法识别的错误和超时错误。允许的值为 IgnoreFail

  • Ignore 表示忽略调用 Webhook 时发生的错误,并允许 API 请求继续。
  • Fail 表示调用 Webhook 时发生的错误会导致准入失败并拒绝 API 请求。

这是一个配置为在调用准入 Webhook 时遇到错误时拒绝 API 请求的变异 Webhook

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
  failurePolicy: Fail

准入 Webhook 的默认 failurePolicyFail

监控准入 Webhook

API 服务器提供了监控准入 Webhook 行为的方法。这些监控机制可帮助集群管理员回答以下问题

  1. 哪个变异 Webhook 在 API 请求中变异了对象?

  2. 变异 Webhook 对对象应用了什么更改?

  3. 哪些 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:请求的操作类型,可以是 CREATEUPDATEDELETECONNECT 之一。

  • type:准入 Webhook 类型,可以是 admitvalidating 之一。

  • 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 的示例

  1. 对于 CREATE Pod 请求,将 Pod 的 .spec.securityContext.runAsNonRoot 字段设置为 true,以强制执行安全最佳实践。

  2. 对于 CREATE Pod 请求,如果容器的 .spec.containers[].resources.limits 字段未设置,则设置默认资源限制。

  3. 对于 CREATE Pod 请求,如果不存在名为 foo-sidecar 的容器,则注入一个名为 foo-sidecar 的 sidecar 容器。

在上述情况下,可以安全地重新调用 Webhook,或准入已经设置了这些字段的对象。

非幂等的修改型准入 Webhook 的示例

  1. 对于 CREATE Pod 请求,注入一个名称为 foo-sidecar 的 sidecar 容器,并在其后添加当前时间戳后缀(例如 foo-sidecar-19700101-000000)。

  2. 对于 CREATE/UPDATE Pod 请求,如果 Pod 设置了标签 "env",则拒绝该请求,否则向 Pod 添加一个 "env": "prod" 标签。

  3. 对于 CREATE Pod 请求,盲目地添加一个名为 foo-sidecar 的 sidecar 容器,而不检查 Pod 中是否已经存在 foo-sidecar 容器。

在上面的第一种情况下,重新调用 Webhook 可能导致同一个 sidecar 被多次注入到 Pod 中,每次的容器名称都不同。同样,如果用户提供的 Pod 中已经存在 sidecar,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 中的死锁

如果配置为拦截启动自身 Pod 所需的资源,则在集群内部运行的 Webhook 可能会导致其自身部署的死锁。

例如,配置一个修改型准入 Webhook,仅当 Pod 中设置了特定标签(例如 "env": "prod")时才允许 CREATE Pod 请求。Webhook 服务器在不设置 "env" 标签的部署中运行。当运行 Webhook 服务器 Pod 的节点变得不健康时,Webhook 部署将尝试将 Pod 重新调度到另一个节点。但是,由于未设置 "env" 标签,现有 Webhook 服务器将拒绝该请求,并且无法进行迁移。

建议使用 namespaceSelector 排除 Webhook 运行的命名空间。

副作用

建议准入 Webhook 应尽可能避免副作用,这意味着 Webhook 仅在其接收的 AdmissionReview 内容上运行,并且不进行带外更改。如果 Webhook 没有副作用,则 .webhooks[].sideEffects 字段应设置为 None

如果在准入评估期间需要副作用,则在处理 dryRun 设置为 trueAdmissionReview 对象时必须抑制它们,并且 .webhooks[].sideEffects 字段应设置为 NoneOnDryRun。有关详细信息,请参阅副作用

避免在 kube-system 命名空间上操作

kube-system 命名空间包含 Kubernetes 系统创建的对象,例如控制平面组件的服务帐户、kube-dns 等 Pod。意外地修改或拒绝 kube-system 命名空间中的请求可能会导致控制平面组件停止运行或引入未知行为。如果你的准入 Webhook 不打算修改 Kubernetes 控制平面的行为,请使用 namespaceSelectorkube-system 命名空间排除在拦截之外。

上次修改时间为 2024 年 7 月 16 日下午 1:26(太平洋标准时间):Update extensible-admission-controllers.md (80d2f96c93)