Admission Webhook 最佳实践
此页面提供了在 Kubernetes 中设计准入 Webhook 时的最佳实践和注意事项。这些信息面向运行准入 Webhook 服务器或修改或验证您的 API 请求的第三方应用程序的集群操作员。
在阅读此页面之前,请确保您熟悉以下概念
良好 Webhook 设计的重要性
当任何创建、更新或删除请求发送到 Kubernetes API 时,准入控制会发生。准入控制器会拦截与您定义的特定标准匹配的请求。然后,这些请求会发送到变异准入 Webhook 或验证准入 Webhook。这些 Webhook 通常用于确保对象规范中的特定字段存在或具有特定的允许值。
Webhook 是一种扩展 Kubernetes API 的强大机制。设计不良的 Webhook 经常会导致工作负载中断,因为 Webhook 对集群中的对象具有很大的控制权。与其他 API 扩展机制一样,Webhook 难以大规模测试与所有工作负载、其他 Webhook、插件和扩展的兼容性。
此外,每次发布时,Kubernetes 都会添加或修改 API,包括新特性、将特性提升到 beta 或稳定状态以及弃用。即使是稳定的 Kubernetes API 也可能会发生变化。例如,Pod API 在 v1.29 中发生了变化,以添加 边车容器 功能。虽然 Kubernetes 对象由于新的 Kubernetes API 而进入损坏状态的情况很少发生,但对于早期版本的 API 正常工作的 Webhook 可能无法协调对该 API 的最新更改。这可能导致集群升级到新版本后出现意外行为。
此页面描述了常见的 Webhook 失败场景以及如何通过谨慎而周密地设计和实现 Webhook 来避免这些场景。
确定您是否使用准入 Webhook
即使您不运行自己的准入 Webhook,您在集群中运行的某些第三方应用程序也可能使用变异或验证准入 Webhook。
要检查您的集群是否有任何变异准入 Webhook,请运行以下命令
kubectl get mutatingwebhookconfigurations
输出列出了集群中的任何变异准入控制器。
要检查您的集群是否有任何验证准入 Webhook,请运行以下命令
kubectl get validatingwebhookconfigurations
输出列出了集群中的任何验证准入控制器。
选择准入控制机制
Kubernetes 包含多种准入控制和策略执行选项。了解何时使用特定选项可以帮助您提高延迟和性能,减少管理开销,并避免版本升级期间出现问题。下表描述了允许您在准入期间变异或验证资源的机制
| 机制 | 描述 | 使用场景 |
|---|---|---|
| 变异准入 Webhook | 在准入之前拦截 API 请求,并使用自定义逻辑进行修改。 |
|
| 变异准入策略 | 在准入之前拦截 API 请求,并使用通用表达式语言 (CEL) 表达式进行修改。 |
|
| 验证准入 Webhook | 在准入之前拦截 API 请求,并根据复杂的策略声明进行验证。 |
|
| 验证准入策略 | 在准入之前拦截 API 请求,并根据 CEL 表达式进行验证。 |
|
通常,当您想要一种可扩展的方式来声明或配置逻辑时,请使用Webhook 准入控制。当您想要声明更简单的逻辑而无需运行 Webhook 服务器的开销时,请使用内置的基于 CEL 的准入控制。Kubernetes 项目建议尽可能使用基于 CEL 的准入控制。
为 CustomResourceDefinitions 使用内置验证和默认值
如果您使用 CustomResourceDefinitions,请勿使用准入 Webhook 来验证 CustomResource 规范中的值或为字段设置默认值。Kubernetes 允许您在创建 CustomResourceDefinitions 时定义验证规则和默认字段值。
要了解更多信息,请参阅以下资源
性能和延迟
本节描述了提高性能和降低延迟的建议。总而言之,如下所示
- 合并 Webhook 并限制每个 Webhook 的 API 调用次数。
- 使用审核日志检查重复执行相同操作的 Webhook。
- 使用负载均衡器实现 Webhook 可用性。
- 为每个 Webhook 设置较小的超时值。
- 在 Webhook 设计期间考虑集群可用性需求。
设计低延迟的准入 Webhook
变异准入 Webhook 顺序调用。根据变异 Webhook 的设置,某些 Webhook 可能会被调用多次。每次变异 Webhook 调用都会增加准入过程的延迟。这与并行调用的验证 Webhook 不同。
在设计变异 Webhook 时,请考虑您的延迟要求和容忍度。集群中的变异 Webhook 越多,延迟增加的可能性就越大。
考虑以下因素以减少延迟
- 合并对不同对象执行类似变异的 Webhook。
- 减少变异 Webhook 服务器逻辑中发出的 API 调用次数。
- 限制每个变异 Webhook 的匹配条件,以减少由特定 API 请求触发的 Webhook 数量。
- 将小型 Webhook 合并到一个服务器和配置中,以帮助进行排序和组织。
防止由竞争控制器引起的循环
考虑集群中可能与 Webhook 进行的变异冲突的任何其他组件。例如,如果您的 Webhook 添加了一个标签,而另一个控制器将其删除,您的 Webhook 就会再次被调用。这会导致循环。
要检测这些循环,请尝试以下操作
更新您的集群审核策略以记录审核事件。使用以下参数
level:RequestResponseverbs:["patch"]omitStages:RequestReceived
设置审核规则以针对您的 Webhook 变异的特定资源创建事件。
检查您的审核事件,查看是否对同一对象重复调用 Webhook 并应用相同的补丁,或者对象上的字段被多次更新和还原。
设置较小的超时值
准入 Webhook 应尽快评估(通常在毫秒级),因为它们会增加 API 请求延迟。为 Webhook 使用较小的超时时间。
有关详细信息,请参阅 超时。
使用负载均衡器确保 Webhook 可用性
准入 Webhook 应利用某种形式的负载均衡来提供高可用性和性能优势。如果 Webhook 在集群内运行,则可以在 ClusterIP 类型的 Service 后面运行多个 Webhook 后端。
使用高可用性部署模型
在设计 webhook 时,请考虑集群的可用性需求。例如,在节点停机或区域中断期间,Kubernetes 会将 Pod 标记为 NotReady,以便负载均衡器将流量路由到可用的区域和节点。这些 Pod 的更新可能会触发您的 mutating webhook。根据受影响的 Pod 数量,mutating webhook 服务器可能会超时或导致 Pod 处理延迟。因此,流量无法像您需要的那样快速地重新路由。
在编写 webhook 时,请考虑类似于上述示例的情况。排除 Kubernetes 响应不可避免的事件导致的操作。
请求过滤
本节提供有关过滤哪些请求触发特定 webhook 的建议。 总结如下
- 限制 webhook 范围,以避免系统组件和只读请求。
- 将 webhook 限制到特定的命名空间。
- 使用匹配条件执行细粒度的请求过滤。
- 匹配对象的任何版本。
限制每个 webhook 的范围
仅当 API 请求与相应的 webhook 配置匹配时,才会调用 Admission webhook。 限制每个 webhook 的范围以减少对 webhook 服务器的不必要调用。 考虑以下范围限制
- 避免匹配
kube-system命名空间中的对象。 如果您在kube-system命名空间中运行自己的 Pod,请使用objectSelector以避免修改关键工作负载。 - 不要修改节点租约,这些租约以 Lease 对象的形式存在于
kube-node-lease系统命名空间中。 修改节点租约可能会导致节点升级失败。 仅在确信控制不会使您的集群面临风险的情况下,才对该命名空间中的 Lease 对象应用验证控制。 - 不要修改 TokenReview 或 SubjectAccessReview 对象。 这些始终是只读请求。 修改这些对象可能会破坏您的集群。
- 使用
namespaceSelector将每个 webhook 限制到特定的命名空间。
使用匹配条件过滤特定请求
Admission 控制器支持多个字段,您可以使用这些字段来匹配满足特定标准的请求。 例如,您可以使用 namespaceSelector 来过滤目标特定命名空间的请求。
为了进行更细粒度的请求过滤,请在 webhook 配置中使用 matchConditions 字段。 此字段允许您编写多个 CEL 表达式,这些表达式必须评估为 true 才能触发您的 admission webhook。 使用 matchConditions 可能会显著减少对 webhook 服务器的调用次数。
有关详细信息,请参阅 匹配请求:matchConditions。
匹配 API 的所有版本
默认情况下,admission webhook 运行在影响指定资源的任何 API 版本上。 webhook 配置中的 matchPolicy 字段控制此行为。 在 matchPolicy 字段中指定值 Equivalent 或省略该字段,以允许 webhook 运行在任何 API 版本上。
有关详细信息,请参阅 匹配请求:matchPolicy。
Mutation 范围和字段注意事项
本节提供有关 mutation 范围和对象字段的任何特殊注意事项的建议。 总结如下
- 仅修补需要修补的字段。
- 不要覆盖数组值。
- 尽可能避免 mutation 中的副作用。
- 避免自我 mutation。
- Fail open 并验证最终状态。
- 为后续版本中的字段更新做好计划。
- 防止 webhook 自我触发。
- 不要更改不可变对象。
仅修补必需的字段
Admission webhook 服务器发送 HTTP 响应,以指示如何处理特定的 Kubernetes API 请求。 此响应是 AdmissionReview 对象。 mutating webhook 可以使用响应中的 patchType 字段和 patch 字段添加特定字段以进行 mutation,从而允许 admission。 确保您仅修改需要更改的字段。
例如,考虑一个配置为确保 web-server Deployments 至少具有三个副本的 mutating webhook。 当对 Deployment 对象发出的请求匹配您的 webhook 配置时,webhook 应该仅更新 spec.replicas 字段中的值。
不要覆盖数组值
Kubernetes 对象规范中的字段可能包含数组。 一些数组包含键值对(例如容器规范中的 envVar 字段),而其他数组则未键入(例如 Pod 规范中的 readinessGates 字段)。 在某些情况下,数组字段中值的顺序可能很重要。 例如,容器规范的 args 字段中的参数顺序可能会影响容器。
在修改数组时,请考虑以下事项
- 尽可能使用
addJSONPatch 操作而不是replace,以避免意外替换必需的值。 - 将不使用键值对的数组视为集合。
- 确保您修改的字段中的值不需要按特定顺序排列。
- 除非绝对必要,否则不要覆盖现有的键值对。
- 小心修改标签字段。 意外的修改可能会导致标签选择器中断,从而导致意外行为。
避免副作用
确保您的 webhook 仅对发送给它们的 AdmissionReview 的内容进行操作,并且不要进行带外更改。 这些额外的更改,称为副作用,如果未正确协调,可能会在 admission 期间导致冲突。 如果 webhook 没有副作用,则应将 .webhooks[].sideEffects 字段设置为 None。
如果需要在 admission 评估期间需要副作用,则必须在处理 dryRun 设置为 true 的 AdmissionReview 对象时抑制它们,并且应将 .webhooks[].sideEffects 字段设置为 NoneOnDryRun。
有关详细信息,请参阅 副作用。
避免自我 mutation
在集群内运行的 webhook 可能会在其自身的部署中导致死锁,如果它被配置为拦截启动其自身的 Pod 所需的资源。
例如,mutating admission webhook 被配置为仅在 Pod 中设置了某个标签(例如 env: prod)时才允许 创建 Pod 请求。 webhook 服务器在未设置 env 标签的 Deployment 中运行。
当运行 webhook 服务器 Pod 的节点变得不健康时,webhook Deployment 会尝试将 Pod 重新安排到另一个节点。 但是,现有的 webhook 服务器会拒绝这些请求,因为未设置 env 标签。 因此,迁移无法发生。
使用 namespaceSelector 排除运行 webhook 的命名空间。
避免依赖循环
依赖循环可能发生在以下场景中
- 两个 webhook 检查彼此的 Pod。 如果两个 webhook 同时变得不可用,则任何一个 webhook 都无法启动。
- 您的 webhook 拦截集群附加组件,例如网络插件或存储插件,而您的 webhook 依赖于这些插件。 如果 webhook 和依赖的附加组件都变得不可用,则两个组件都无法正常运行。
为了避免这些依赖循环,请尝试以下操作
- 使用 ValidatingAdmissionPolicies 以避免引入依赖关系。
- 防止 webhook 验证或修改其他 webhook。 考虑 排除特定的命名空间 以触发您的 webhook。
- 使用
objectSelector防止您的 webhook 对依赖的附加组件起作用。
Fail open 并验证最终状态
Mutating admission webhook 支持 failurePolicy 配置字段。 此字段指示 webhook 失败时 API 服务器是否应允许或拒绝请求。 Webhook 失败可能是由于超时或服务器逻辑中的错误引起的。
默认情况下,admission webhook 将 failurePolicy 字段设置为 Fail。 如果 webhook 失败,API 服务器将拒绝请求。 但是,默认情况下拒绝请求可能会导致在 webhook 停机期间拒绝符合要求的请求。
通过将 failurePolicy 字段设置为 Ignore,让您的 mutating webhook “fail open”。 使用验证控制器检查请求的状态,以确保它们符合您的策略。
这种方法具有以下好处
- mutating webhook 停机不会影响符合要求的资源部署。
- 策略执行发生在验证 admission 控制期间。
- mutating webhook 不会干扰集群中的其他控制器。
为字段的未来更新做好计划
通常,在设计 webhook 时,假设 Kubernetes API 可能会在后续版本中发生变化。 不要编写假定 API 稳定性不变的服务器。 例如,Kubernetes 中 sidecar 容器的发布在 Pod API 中添加了一个 restartPolicy 字段。
防止 webhook 触发自身
响应广泛的 API 请求范围的 mutating webhook 可能会无意中触发自身。 例如,考虑一个配置为响应集群中所有请求的 webhook。 如果您配置 webhook 以针对每次 mutation 创建 Event 对象,它将响应其自身的 Event 对象创建请求。
为了避免这种情况,请考虑在 webhook 创建的任何资源中设置唯一的标签。 从 webhook 匹配条件中排除此标签。
不要更改不可变对象
API 服务器中的一些 Kubernetes 对象无法更改。例如,当你部署一个 静态 Pod 时,节点上的 kubelet 会在 API 服务器中创建一个 镜像 Pod 来跟踪静态 Pod。但是,对镜像 Pod 的更改不会传播到静态 Pod。
不要尝试在准入期间修改这些对象。所有镜像 Pod 都有 kubernetes.io/config.mirror 注解。为了在忽略注解的安全风险降低的同时排除镜像 Pod,只允许静态 Pod 在特定的命名空间中运行。
变更 webhook 的排序和幂等性
本节提供关于 webhook 顺序和设计幂等 webhook 的建议。 总结如下:
- 不要依赖特定的执行顺序。
- 在准入前验证变更。
- 检查变更是否被其他控制器覆盖。
- 确保变更 webhook 集合是幂等的,而不仅仅是单个 webhook。
不要依赖变更 webhook 的调用顺序
变更准入 webhook 的执行顺序并不一致。各种因素可能会改变特定 webhook 被调用的时间。不要依赖你的 webhook 在准入过程中的特定点运行。其他 webhook 仍然可能修改你的已修改对象。
以下建议可能有助于最大限度地降低意外更改的风险
确保你的集群中的变更 webhook 是幂等的
每个变更准入 webhook 都应该是幂等的。webhook 应该能够在它已经修改过的对象上运行,而不会在原始更改之外进行任何额外的更改。
此外,你的集群中的所有变更 webhook 作为集合也应该是幂等的。在准入控制的变更阶段结束后,每个单独的变更 webhook 都应该能够在对象上运行,而不会对对象进行任何额外的更改。
根据你的环境,在规模上确保幂等性可能具有挑战性。以下建议可能有所帮助
- 使用验证准入控制器来验证关键工作负载的最终状态。
- 在暂存集群中测试你的部署,以查看是否有任何对象被同一个 webhook 多次修改。
- 确保每个变更 webhook 的范围都是具体的且有限的。
以下示例显示了幂等变更逻辑
对于一个 创建 Pod 请求,将 Pod 的
.spec.securityContext.runAsNonRoot字段设置为 true。对于一个 创建 Pod 请求,如果容器的
.spec.containers[].resources.limits字段未设置,则设置默认资源限制。对于一个 创建 Pod 请求,如果不存在名为
foo-sidecar的容器,则注入一个名为foo-sidecar的边车容器。
在这些情况下,webhook 可以安全地被重新调用,或者准入已经设置了字段的对象。
以下示例显示了非幂等变更逻辑
对于一个 创建 Pod 请求,注入一个名为
foo-sidecar的边车容器,并附加当前时间戳(例如foo-sidecar-19700101-000000)。重新调用 webhook 可能会导致相同的边车被多次注入到 Pod 中,每次都使用不同的容器名称。 同样,如果边车已经存在于用户提供的 Pod 中,webhook 可能会注入重复的容器。
对于一个 创建/更新 Pod 请求,如果 Pod 具有标签
env,则拒绝,否则将标签env: prod添加到 Pod。重新调用 webhook 将导致 webhook 拒绝其自身的输出。
对于一个 创建 Pod 请求,附加一个名为
foo-sidecar的边车容器,而不检查是否存在foo-sidecar容器。重新调用 webhook 将导致 Pod 中出现重复的容器,这使得请求无效并被 API 服务器拒绝。
变更测试和验证
本节提供关于测试你的变更 webhook 和验证变更对象建议。 总结如下:
- 在暂存环境中测试 webhook。
- 避免违反验证规则的变更。
- 测试次要版本升级是否存在回归和冲突。
- 在准入前验证变更对象。
在暂存环境中测试 webhook
强大的测试应该是你发布新或更新的 webhook 的核心部分。如果可能,在与你的生产集群密切相似的暂存环境中测试对你的集群 webhook 的任何更改。 至少,考虑使用像 minikube 或 kind 这样的工具来创建一个小的测试集群,用于 webhook 更改。
确保变更不会违反验证规则
你的变更 webhook 不应该破坏对象在准入前适用的任何验证规则。 例如,考虑一个将 Pod 的默认 CPU 请求设置为特定值的变更 webhook。 如果该 Pod 的 CPU 限制设置为低于变更请求的值,则 Pod 将无法通过准入。
将每个变更 webhook 与你的集群中运行的验证规则进行测试。
测试次要版本升级以确保一致的行为
在将你的生产集群升级到新的次要版本之前,在暂存环境中测试你的 webhook 和工作负载。 比较结果,以确保你的 webhook 在升级后继续按预期运行。
此外,使用以下资源来了解 API 更改
在准入前验证变更
变更 webhook 在完成执行后,验证 webhook 才会运行。 变更应用于对象的顺序没有稳定性。 因此,你的变更可能会被稍后运行的变更 webhook 覆盖。
添加一个验证准入控制器,例如 ValidatingAdmissionWebhook 或 ValidatingAdmissionPolicy 到你的集群,以确保你的变更仍然存在。 例如,考虑一个将 restartPolicy: Always 字段插入到特定的 init 容器中,以使其作为边车容器运行的变更 webhook。 你可以运行一个验证 webhook 来确保在所有变更完成后,这些 init 容器保留了 restartPolicy: Always 配置。
有关详细信息,请参阅以下资源
变更 webhook 部署
本节提供关于部署你的变更准入 webhook 的建议。 总结如下:
- 通过命名空间逐步推出 webhook 配置并监控问题。
- 限制编辑 webhook 配置资源的访问权限。
- 如果服务器在集群中,则限制对运行 webhook 服务器的命名空间的访问权限。
安装并启用变更 webhook
当你准备好将你的变更 webhook 部署到集群时,请使用以下操作顺序
- 安装 webhook 服务器并启动它。
- 将 MutatingWebhookConfiguration 清单中的
failurePolicy字段设置为 Ignore。 这让你避免由配置错误的 webhook 引起的破坏。 - 将 MutatingWebhookConfiguration 清单中的
namespaceSelector字段设置为一个测试命名空间。 - 将 MutatingWebhookConfiguration 部署到你的集群。
监控测试命名空间中的 webhook,以检查任何问题,然后将 webhook 推广到其他命名空间。 如果 webhook 拦截了它不应该拦截的 API 请求,请暂停推广并调整 webhook 配置的范围。
限制对变更 webhook 的编辑访问权限
变更 webhook 是强大的 Kubernetes 控制器。 使用 RBAC 或其他授权机制来限制对你的 webhook 配置和服务器的访问权限。 对于 RBAC,确保以下访问权限仅对受信任的实体可用
- 动词:创建、更新、补丁、删除、deletecollection
- API 组:
admissionregistration.k8s.io/v1 - API 类型:MutatingWebhookConfigurations
如果你的变更 webhook 服务器在集群中运行,则限制对创建或修改该命名空间中的任何资源的访问权限。
良好实现的示例
以下项目是“良好”自定义 webhook 服务器实现的示例。 你可以使用它们作为设计自己的 webhook 的起点。 不要直接使用这些示例; 将它们用作起点,并设计你的 webhook 以在你的特定环境中良好运行。
接下来
此页面上的项目引用了提供 Kubernetes 所需功能的第三方产品或项目。 Kubernetes 项目作者不对这些第三方产品或项目负责。 请参阅 CNCF 网站指南 以获取更多详细信息。
在提出添加额外的第三方链接的更改之前,您应该阅读 内容指南。