动态准入控制

除了内置准入插件之外,还可以开发准入插件作为扩展,并在运行时配置为 Webhook。本页描述了如何构建、配置、使用和监控准入 Webhook。

什么是准入 Webhook?

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

你可以通过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 服务器会向 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 Deploymentscale 子资源的请求。

{
  "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,设置为 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 状态码和消息。指定的状态对象将返回给用户。参阅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 还可以选择修改传入的对象。这是通过使用响应中的 patchpatchType 字段完成的。当前唯一支持的 patchTypeJSONPatch。参阅 JSON patch 文档以获取更多详细信息。对于 patchType: JSONPatchpatch 字段包含一个 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 个字符以内
{
  "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,请创建 MutatingWebhookConfigurationValidatingWebhookConfiguration API 对象。MutatingWebhookConfigurationValidatingWebhookConfiguration 对象的名称必须是有效的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 中的任意一项指定的 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

Webhook 可以通过指定 objectSelector,可选地根据将被发送的对象的标签来限制拦截哪些请求。如果指定,objectSelector 将针对将被发送到 Webhook 的 object 和 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

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 如何用于匹配传入请求。允许的值为 ExactEquivalent
  • 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。如果你发现匹配规则、objectSelectorsnamespaceSelectors 仍然无法提供你想要的 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()'

你可以在 matchConditions 字段中为每个 Webhook 定义最多 64 个元素。

  • Match conditions 可以访问以下 CEL 变量
  • object - 传入请求中的对象。对于 DELETE 请求,其值为 null。对象的版本可能会根据matchPolicy 进行转换。
  • oldObject - 现有对象。对于 CREATE 请求,其值为 null。
  • request - AdmissionReview 的请求部分,不包括 objectoldObject
  • authorizer - 一个 CEL Authorizer。可用于对请求的主体(已认证用户)执行授权检查。参阅 Kubernetes CEL 库文档中的 Authz 以获取更多详细信息。

authorizer.requestResource - 配置了请求资源(group、resource、(subresource)、namespace、name)的授权检查的快捷方式。

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

  1. 如果评估 match condition 时发生错误,则永远不会调用 Webhook。是否拒绝请求按如下方式确定
  2. 如果 **任何** match condition 评估为 false(无论其他错误如何),API 服务器会跳过该 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 地址。

请注意,使用 localhost127.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

你必须将上述示例中的 <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可以设置为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的配置示例,如果在调用准入Webhook时遇到错误,将拒绝API请求。

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服务器暴露了一个记录准入Webhook拒绝的Prometheus计数器指标。这些指标通过标签标识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最佳实践

最后修改日期:2025年2月3日,太平洋标准时间下午5:50:将最佳实践从动态准入控制页面移至最佳实践页面 (14220821d1)