惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
AWS News Blog
AWS News Blog
Google DeepMind News
Google DeepMind News
U
Unit 42
博客园 - 叶小钗
博客园 - 聂微东
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
D
DataBreaches.Net
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
美团技术团队
The Cloudflare Blog
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
S
Schneier on Security
C
Check Point Blog
Project Zero
Project Zero
The Hacker News
The Hacker News
Scott Helme
Scott Helme
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cisco Talos Blog
Cisco Talos Blog
P
Privacy International News Feed
SecWiki News
SecWiki News
Latest news
Latest news
MongoDB | Blog
MongoDB | Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
H
Help Net Security
TaoSecurity Blog
TaoSecurity Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Last Week in AI
Last Week in AI
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
PCI Perspectives
PCI Perspectives
大猫的无限游戏
大猫的无限游戏
T
Troy Hunt's Blog
H
Hacker News: Front Page
Vercel News
Vercel News

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— VictoriaLogs 协同:Metrics 到 Logs 的一体化监控 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 源码阅读路线图:如何高效阅读 VM 源码 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开源生态:VM 在 CNCF 生态中的位置 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 所有权详解 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战 DeepSeek-R1 评估与系统(Evaluation & Systems)【左扬精讲】—— 从 GSM8K/MMLU 到 LLM-as-Judge 的工业级评估方法论 DeepSeek-R1 模型训练与算法【左扬精讲】—— GRPO 进阶算法:DAPO / PRIME / RLVR / PRM 四大 2025 前沿改进 DeepSeek-R1 模型训练与算法【左扬精讲】—— 数据蒸馏:用 DeepSeek-R1-671B 生成 800K 高质量 CoT 样本的完整流水线 DeepSeek-R1 优化与微调实战【左扬精讲】—— 从 R1 强化学习新范式到 GRPO 微调一站式入门 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 七):自定义插件开发实战 —— 手写一个 Score 插件并注册到集群 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 五):SchedulingQueue 与 QueueingHint —— 三段队列的细节、v1.36 新引入的 QueueingHint 工作机制 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 k8s 源码级精讲(二十六):调度器内置插件逐个精读 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):Scheduling Framework 扩展点逐个源码拆解 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制 Kubernetes 编程 / client-go 专题【左扬精讲】—— 四种客户端:为什么、怎么选、怎么用 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime、kubebuilder、operator-sdk 三大框架深度对比 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入理解 ManagedFields 字段冲突协调机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Finalizers 深度解析:对象的生命周期与删除控制 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference 字段与级联删除机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入学习 Server-Side Apply:managedFields 替代 last-applied-configuration 的演进方向 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Annotations 与元数据体系(Operator 专题) Kubernetes 编程 / Operator 专题【左扬精讲】—— RESTMapper:把 Group / Version / Kind / Resource 四元组翻译成 REST 路径的"查字典"大师 Kubernetes 编程 / Operator 专题【左扬精讲】—— Converter 资源版本转换器 Kubernetes 编程 / Operator 专题【左扬精讲】—— Application 业务扩展:从单 Deployment 到多 Workload 的复合 Operator 演进 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference / Finalizer / 准入控制:k8s 资源生命周期的三大支柱 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime 框架内幕:从 Manager 到 Reconcile 的全栈拆解 Kubernetes 编程 / Operator 专题【左扬精讲】—— 生产级 Operator 最佳实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— application-operator Reconcile 循环源码精讲:从 client-go Informer 到 workqueue 的全链路解剖 Kubernetes 编程 / Operator 专题【左扬精讲】—— 从零搭建一个 application-operator 新项目:脚手架、API 设计与基于原生 DeploymentStatus/ServiceStatus 的状态建模 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3(上篇)—— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署
左扬 · 2026-06-22 · via 博客园 - 左扬

kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署

读完本篇,你应该能回答:

Kubernetes API 访问控制的完整链路是什么?

MutatingWebhook 和 ValidatingWebhook 在请求处理流程中处于什么位置?

AdmissionReview 的请求体和响应体各包含哪些字段?

MutatingWebhook 如何通过 JSON Patch 实现对象修改?

ValidatingWebhook 的并行调用和短路逻辑是什么?

Webhook 如何处理 fail-open 和 fail-closed?

如何在 Kubernetes 中部署和配置一个生产可用的 Admission Webhook?

如何编写单元测试覆盖 Webhook 行为?

Kubernetes Go Operator AdmissionWebhook MutatingWebhook ValidatingWebhook k8s v1.36.1

学习重点提示建议先通读全文,再重点回顾标注内容

重点掌握(必须)

  • API Server 请求处理链:Authentication → Authorization → MutatingAdmissionWebhook → ValidatingAdmissionWebhook → Storage
  • AdmissionReview 协议:Request/Response 结构、UID 机制、PatchType=JSONPatch
  • MutatingWebhook 实现:dispatcher.go 中 callAttrMutatingHook 的完整调用链路和 Patch 应用流程
  • ValidatingWebhook 实现:并行调用、FailOpen/FailClosed、短路逻辑

次重点(了解即可)

  • ReinvocationPolicy 的二次调用机制
  • MatchConditions(CEL 表达式匹配条件)
  • AuditAnnotations 和 Warnings 机制

文章目录

  1. 一、为什么需要 Admission Webhook?—— API Server 的门卫机制
  2. 二、Kubernetes API 访问控制完整链路
  3. 三、Admission Webhook 在请求处理流程中的位置
  4. 四、Admission 接口体系:MutationInterface 与 ValidationInterface
  5. 五、AdmissionReview 协议详解
  6. 六、MutatingWebhook 实现:MutatingDispatcher 全链路
  7. 七、MutatingWebhook 实现:JSON Patch 应用流程
  8. 八、ValidatingWebhook 实现:并行调用与短路逻辑
  9. 九、Webhook 配置:MutatingWebhookConfiguration 与 ValidatingWebhookConfiguration
  10. 十、常见策略配置:FailurePolicy、MatchPolicy、SideEffects
  11. 十一、实战:一个完整的 MutatingWebhook 实现
  12. 十二、部署与运行:证书、TLS、Service 暴露
  13. 十三、测试:单元测试与集成测试
  14. 十四、Roadmap:后续预告

一、为什么需要 Admission Webhook?—— API Server 的门卫机制

思考记忆提示本节是全篇的"入口"——理解 Admission Webhook 解决什么问题,才能理解后面的每一个设计决策

  • API Server 的默认行为是"来者不拒",只验证语法合法性,不验证业务语义
  • Admission Webhook 让管理员在不修改 API Server 源码的情况下,注入自定义的准入逻辑
  • 两种类型:MutatingWebhook(可修改对象)和 ValidatingWebhook(只验证,拒绝/允许)
  • 面试高频提问:MutatingWebhook 和 ValidatingWebhook 的执行顺序是什么?为什么这个顺序不能颠倒?

Kubernetes API Server 是整个集群的"中央大脑"——所有操作(创建 Pod、修改 Deployment、删除 Service)都必须经过 API Server。但 API Server 默认只做两件事:验证请求的语法合法性(字段类型对不对)和检查基本约束(字段值是否在范围内)。它不关心业务语义——比如:"这个 Pod 使用的镜像是否在白名单中?""这个 Deployment 的副本数是否超过了资源配额?"

Admission Webhook 就是来解决这个问题的:它允许管理员在不修改 API Server 源码的情况下,注入自定义的准入逻辑。API Server 在处理请求时,会向外部的 Webhook 服务发送 HTTP 请求,Webhook 返回允许或拒绝(或带修改的响应)。

我的理解的意思是说

可以把 Admission Webhook 想象成一个机场安检系统

  • Authentication(身份验证) = 检查你的护照/身份证,确认你是谁
  • Authorization(授权检查) = 检查你是否有登机牌,确认你能上哪架飞机
  • MutatingWebhook(变形准入) = 安检时要求你脱掉外套、取出笔记本(修改你的"状态")
  • ValidatingWebhook(验证准入) = 检查你是否携带违禁品(拒绝或允许,不能修改你)
  • Storage(持久化) = 登机后,行李被放入货舱(etcd)

Mutating 必须在 Validating 之前执行——因为只有先"脱掉外套",才能检查你有没有藏在口袋里。

二、Kubernetes API 访问控制完整链路

思考记忆提示这是全篇最重要的"地图"——理解完整链路中每个环节的职责和顺序

  • 6 个环节依次执行,MutatingWebhook 在 ValidatingWebhook 之前
  • MutatingWebhook 可以多次调用(Reinvocation),ValidatingWebhook 只调用一次
  • 任何一个环节拒绝,请求都不会到达 Storage
  • 面试高频提问:Authentication 和 Authorization 的区别是什么?它们和 Admission 的关系是什么?

Kubernetes API Server 处理请求的完整链路分为 6 个阶段,每个阶段都有明确的职责:

┌──────────────────────────────────────────────────────────────────────────────────┐
│                    Kubernetes API Server 请求处理完整链路                            │
│                                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  1. Authentication      │  用户身份验证                                        │
│  │  (认证)                  │  ├─ X509 证书                                        │
│  │  pkg/authentication/     │  ├─ Bearer Token                                    │
│  │                         │  └─ OIDC Token                                      │
│  │                         │  输出:UserInfo(username, uid, groups)              │
│  └────────────┬────────────┘                                                     │
│               │ 验证通过                                                          │
│               ▼                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  2. Authorization       │  权限检查(RBAC)                                    │
│  │  (授权)                  │  ├─ Can I create this resource in this namespace?    │
│  │  pkg/authorization/     │  └─ RBAC / ABAC / Webhook                          │
│  │                         │  输出:Decision (Allow / Deny)                       │
│  └────────────┬────────────┘                                                     │
│               │ 允许                                                              │
│               ▼                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  3. MutatingAdmission   │  变形准入(可修改对象)                               │
│  │  Webhook                │  ├─ 内置:MutatingAdmissionWebhook                    │
│  │  (在请求体写入 etcd 前  │  ├─ 内置:DefaultStorageVersion                        │
│  │   修改对象)              │  ├─ 内置:NamespaceLifecycle                         │
│  │                         │  └─ 自定义:MutatingWebhookConfiguration             │
│  │                         │  特点:可以修改对象、多次调用(Reinvocation)          │
│  └────────────┬────────────┘                                                     │
│               │ 对象被修改(如果有)                                              │
│               ▼                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  4. ValidatingAdmission  │  验证准入(只检查,不修改)                          │
│  │  Webhook                │  ├─ 内置:ValidatingAdmissionWebhook                  │
│  │  (检查对象是否符合要求,  │  ├─ 内置:ResourceQuota                             │
│  │   拒绝或允许)            │  ├─ 内置:PodSecurity                               │
│  │                         │  └─ 自定义:ValidatingWebhookConfiguration           │
│  │                         │  特点:只验证不修改,并行调用,无 Reinvocation        │
│  └────────────┬────────────┘                                                     │
│               │ 允许                                                              │
│               ▼                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  5. Persistence          │  持久化到 etcd                                       │
│  │  (etcd 写入)            │  ├─ 一致性保证(RAFT 协议)                          │
│  │  pkg/storage/           │  └─ Watch 通知所有 Watcher                          │
│  └────────────┬────────────┘                                                     │
│               ▼                                                                  │
│  ┌─────────────────────────┐                                                     │
│  │  6. Watch Notification  │  通知所有 Watcher(Informer 等)                    │
│  │  (etcd → API Server →   │                                                     │
│  │   Informer Reflector)    │                                                     │
│  └─────────────────────────┘                                                     │
└──────────────────────────────────────────────────────────────────────────────────┘

设计精髓

MutatingWebhook 必须在 ValidatingWebhook 之前执行,这是一个关键的有序性保证。因为 MutatingWebhook 可能修改对象(比如添加默认标签),修改后的对象才是 ValidatingWebhook 要验证的最终形态。如果 ValidatingWebhook 先执行,它验证的是原始对象,而不是最终会被写入 etcd 的对象——这会导致验证结果不准确。

三、Admission Webhook 在请求处理流程中的位置

思考记忆提示本节展示 Webhook 在 kube-apiserver 内部是如何被调用的

  • Webhook 是 API Server 的外部扩展点,通过 HTTP 调用外部服务
  • MutatingWebhook 和 ValidatingWebhook 分别在 handler chain 的不同位置
  • Configuration Manager 监听 API Server 中的 WebhookConfiguration 资源
  • 面试高频提问:Webhook 和内置 Admission 插件(如 ResourceQuota)有什么本质区别?

Webhook 插件在 API Server 的 handler chain 中处于这样的位置:

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                          kube-apiserver Handler Chain                                 │
│                                                                                      │
│  ┌───────────┐    ┌──────────────┐    ┌────────────────┐    ┌────────────────────┐    │
│  │  Filters  │───▶│   Audit     │───▶│  Authentication │───▶│  Authorization     │    │
│  │  (基础过滤) │    │  (审计日志)   │    │  (认证)         │    │  (授权)            │    │
│  └───────────┘    └──────────────┘    └───────┬────────┘    └─────────┬────────┘    │
│                                                 │                         │              │
│                                          ┌──────┴─────────────────────────┴──────┐      │
│                                          │           Admission Chain               │      │
│                                          │                                       │      │
│                                          │  ┌─────────────────────────────────┐  │      │
│                                          │  │    Built-in Mutating Plugins    │  │      │
│                                          │  │  (NamespaceLifecycle, etc.)    │  │      │
│                                          │  └─────────────┬───────────────────┘  │      │
│                                          │                │                       │      │
│                                          │                ▼                       │      │
│                                          │  ┌─────────────────────────────────┐  │      │
│                                          │  │  MutatingAdmissionWebhook      │  │      │
│                                          │  │  (调用外部 Webhook 服务)        │  │      │
│                                          │  │  可能在内部多次调用             │  │      │
│                                          │  └─────────────┬───────────────────┘  │      │
│                                          │                │                       │      │
│                                          │                ▼                       │      │
│                                          │  ┌─────────────────────────────────┐  │      │
│                                          │  │   Built-in Validating Plugins  │  │      │
│                                          │  │  (ResourceQuota, PodSecurity)  │  │      │
│                                          │  └─────────────┬───────────────────┘  │      │
│                                          │                │                       │      │
│                                          │                ▼                       │      │
│                                          │  ┌─────────────────────────────────┐  │      │
│                                          │  │  ValidatingAdmissionWebhook     │  │      │
│                                          │  │  (调用外部 Webhook 服务)        │  │      │
│                                          │  │  并行调用,短路逻辑             │  │      │
│                                          │  └─────────────────────────────────┘  │      │
│                                          └───────────────────────────────────────┘      │
│                                                             │                            │
│                                                             ▼                            │
│                                                    ┌─────────────────┐                  │
│                                                    │  Storage (etcd) │                  │
│                                                    └─────────────────┘                  │
└─────────────────────────────────────────────────────────────────────────────────────┘

关键点:Webhook 配置是通过 MutatingWebhookConfigurationValidatingWebhookConfiguration 这两个 Kubernetes API 对象来管理的——API Server 通过 Informer 监听这两个资源的变化,动态更新内存中的 Webhook 调用列表。

四、Admission 接口体系:MutationInterface 与 ValidationInterface

思考记忆提示这两个接口是整个 Admission 体系的抽象核心,理解它们就知道如何扩展 Admission

  • MutationInterface.Admit() 可以在上下文中修改对象
  • ValidationInterface.Validate() 只能验证,不能修改
  • Attributes 接口封装了请求的所有上下文信息
  • 面试高频提问:为什么 Validate() 不允许修改对象?这会带来什么好处?

Kubernetes 的 Admission 体系基于一组简洁的接口构建(staging/src/k8s.io/apiserver/pkg/admission/interfaces.go):

// staging/src/k8s.io/apiserver/pkg/admission/interfaces.go(行 122~144)
// Interface 是所有 Admission 插件的基接口
type Interface interface {
    // Handles 返回该插件能处理的操作类型(CREATE/UPDATE/DELETE/CONNECT)
    Handles(operation Operation) bool
}

// MutationInterface:变形(可变)准入
type MutationInterface interface {
    Interface
    // Admit 可以修改请求中的对象(通过 attr.GetObject() 获取并修改)
    // Context 用于超时、取消和追踪
    Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}

// ValidationInterface:验证(只读)准入
type ValidationInterface interface {
    Interface
    // Validate 只能检查对象是否符合规则,不能修改对象
    // 如果拒绝,返回 error(包含 Status 或自定义信息)
    Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}

Attributes 接口封装了请求的完整上下文:

// staging/src/k8s.io/apiserver/pkg/admission/interfaces.go(行 29~77)
type Attributes interface {
    GetName() string              // 对象名称
    GetNamespace() string         // 命名空间
    GetResource() schema.GroupVersionResource   // 资源类型
    GetSubresource() string       // 子资源(如 status、scale)
    GetOperation() Operation      // 操作类型:CREATE/UPDATE/DELETE/CONNECT
    GetOperationOptions() runtime.Object  // 操作选项(如 DeleteOptions)
    IsDryRun() bool              // 是否是 DryRun 请求
    GetObject() runtime.Object   // 请求中的对象(可修改,用于 Mutation)
    GetOldObject() runtime.Object // 旧对象(仅用于 UPDATE/DELETE)
    GetKind() schema.GroupVersionKind  // 对象类型
    GetUserInfo() user.Info      // 请求用户信息

    // 添加审计注解(供 Audit 系统记录)
    AddAnnotation(key, value string) error
    AddAnnotationWithLevel(key string, value string, level auditinternal.Level) error

    // Reinvocation 上下文(MutatingWebhook 多轮调用相关)
    GetReinvocationContext() ReinvocationContext
}

我的理解的意思是说

MutationInterface 和 ValidationInterface 的根本区别在于"是否允许修改对象"。

MutationInterface 的 Admit() 接收一个 Attributes 对象,其中 GetObject() 返回的是可修改的对象引用。MutatingWebhook 可以直接修改这个对象(比如添加标签、设置默认值),修改后的对象会被序列化回请求体,写入 etcd。

ValidationInterface 的 Validate() 同样接收 Attributes,但不能修改对象——只能读取对象、检查条件,然后返回 error 或 nil。这是一种"只读"约束,它确保 ValidatingWebhook 的行为是确定性的(同一个对象,无论何时验证,结果都相同)。

五、AdmissionReview 协议详解

思考记忆提示AdmissionReview 是 Webhook 和 API Server 之间的"通信协议"——理解它就知道 Webhook 服务应该怎么写

  • Request 包含:UID、Kind/Resource、Operation、UserInfo、Object/OldObject
  • Response 必须包含:UID(必须与 Request 一致)、Allowed、Patch(仅 Mutating)
  • UID 是请求/响应的唯一标识,用于防重放和匹配
  • 面试高频提问:为什么 Response.UID 必须等于 Request.UID?Webhook 服务如何验证这一点?

AdmissionReview 是 Kubernetes 定义的标准 HTTP 请求/响应格式,Webhook 服务通过它与 API Server 通信(staging/src/k8s.io/api/admission/v1/types.go):

// staging/src/k8s.io/api/admission/v1/types.go(行 30~178)
// AdmissionReview 是请求/响应的外层包装
type AdmissionReview struct {
    metav1.TypeMeta `json:",inline"`
    Request  *AdmissionRequest   `json:"request,omitempty"`   // API Server → Webhook
    Response *AdmissionResponse   `json:"response,omitempty"`  // Webhook → API Server
}

// ========== Request(API Server → Webhook)==========
type AdmissionRequest struct {
    UID types.UID   // 唯一标识,Response 必须原样返回
    Kind metav1.GroupVersionKind   // 对象的完整类型(如 v1.Pod)
    Resource metav1.GroupVersionResource   // 资源类型(如 v1.pods)
    SubResource string   // 子资源(如 "status"、"scale")

    // 请求的对象(CREATE 时)或新对象(UPDATE 时)
    Object runtime.RawExtension   // JSON 序列化后的字节流
    // 旧对象(仅 UPDATE/DELETE 时,用于对比变化)
    OldObject runtime.RawExtension

    Operation Operation   // CREATE | UPDATE | DELETE | CONNECT
    UserInfo authenticationv1.UserInfo  // 请求用户信息

    // DryRun 表示这只是试运行,不实际持久化
    DryRun *bool
    // Options 包含操作选项(如 CreateOptions/DeleteOptions)
    Options runtime.RawExtension
}

// ========== Response(Webhook → API Server)==========
type AdmissionResponse struct {
    UID types.UID   // 必须与 Request.UID 完全一致

    // 允许或拒绝
    Allowed bool

    // 拒绝原因(仅在 Allowed=false 时使用)
    Result *metav1.Status

    // JSON Patch 内容(仅 MutatingWebhook 使用)
    // 格式:RFC 6902 JSON Patch([{op, path, value}, ...])
    Patch []byte

    // Patch 类型,目前只支持 JSONPatch
    PatchType *PatchType   // PatchTypeJSONPatch = "JSONPatch"

    // 审计注解(键值对,会被记录到审计日志)
    AuditAnnotations map[string]string

    // 警告信息(返回给客户端,v1.36 新增)
    Warnings []string
}

// 支持的操作类型
const (
    Create  Operation = "CREATE"
    Update  Operation = "UPDATE"
    Delete  Operation = "DELETE"
    Connect Operation = "CONNECT"
)

5.1 一个完整的请求/响应示例

// ========== HTTP Request (API Server → Webhook) ==========
POST /admissionreviews HTTP/1.1
Content-Type: application/json

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {
    "uid": "e9a23bb0-1234-5678-abcd-123456789abc",
    "kind": {"group":"","version":"v1","kind":"Pod"},
    "resource": {"group":"","version":"v1","resource":"pods"},
    "namespace": "default",
    "operation": "CREATE",
    "userInfo": {
      "username": "admin",
      "uid": "admin-uid",
      "groups": ["system:masters"]
    },
    "object": {
      "apiVersion": "v1",
      "kind": "Pod",
      "metadata": {"name":"my-pod","namespace":"default"},
      "spec": {
        "containers": [{
          "name": "nginx",
          "image": "nginx:1.25"
        }]
      }
    },
    "dryRun": false
  }
}

// ========== HTTP Response (Webhook → API Server, Mutating) ==========
HTTP/1.1 200 OK
Content-Type: application/json

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "e9a23bb0-1234-5678-abcd-123456789abc",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sgIm9wIjoiYWRkIiwicGF0aCI6Ii9tZXRhZGF0YS9sYWJlbHMiLCJ2YWx1ZSI6eyJhZGQtbGFiZWwiOiJ5ZXMifV19"
    // Base64([{"op":"add","path":"/metadata/labels","value":{"add-label":"yes"}}])
  }
}

小贴士

Request.Object 和 Request.OldObject 是 runtime.RawExtension 类型(JSON 序列化后的字节流),Webhook 需要用 json.Unmarshal 解析。如果对象是自定义资源(CRD),需要用 unstructured.Unstructured 来解析,因为没有对应的 Go 结构体。

六、MutatingWebhook 实现:MutatingDispatcher 全链路

思考记忆提示这是全篇最重要的源码分析——MutatingDispatcher 是 API Server 调用外部 Webhook 的核心引擎

  • Dispatch 方法遍历所有注册的 hooks,调用 ShouldCallHook 过滤
  • callAttrMutatingHook 负责 HTTP 调用、Patch 解析、对象修改
  • ReinvocationPolicy 支持在树内内置插件执行后再次调用 Webhook
  • 面试高频提问:MutatingWebhook 的 Patch 是如何应用到对象上的?为什么支持多个 Webhook 串联?

MutatingDispatcher 是 Kubernetes API Server 调用外部 MutatingWebhook 的核心引擎(staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go)。

6.1 Dispatch 主循环

// staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go(行 105~240)
// Dispatch 是 MutatingWebhook 的入口方法
func (a *mutatingDispatcher) Dispatch(
    ctx context.Context,
    attr admission.Attributes,         // 请求属性(包含对象)
    o admission.ObjectInterfaces,     // 对象操作接口(创建器、转换器等)
    hooks []webhook.WebhookAccessor, // 所有注册的 Webhook(从配置中获取)
) error {
    // ========== 1. 初始化 Reinvocation 上下文(用于多次调用)==========
    reinvokeCtx := attr.GetReinvocationContext()
    // ...

    // ========== 2. 遍历所有注册的 hooks ==========
    for i, hook := range hooks {
        // ShouldCallHook:根据 Rules、Selectors、MatchConditions 判断是否需要调用
        invocation, statusErr := a.plugin.ShouldCallHook(ctx, hook, attrForCheck, o, v)
        if statusErr != nil { return statusErr }
        if invocation == nil { continue }   // 不匹配,跳过

        // ========== 3. 调用单个 Webhook ==========
        changed, err := a.callAttrMutatingHook(ctx, hook, invocation, ...)
        if err != nil {
            // 错误处理:FailOpen / FailClosed
            return err
        }

        // ========== 4. 如果对象被修改,标记需要重新调用 ==========
        if changed {
            webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
            reinvokeCtx.SetShouldReinvoke()
        }

        // ========== 5. 注册可重新调用的 Webhook ==========
        // ReinvocationPolicy=IfNeeded 时生效
        if hook.ReinvocationPolicy != nil && *hook.ReinvocationPolicy == IfNeededReinvocationPolicy {
            webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID())
        }
    }

    // ========== 6. 将修改后的 VersionedObject 转换回内部版本 ==========
    if v.versionedAttr.VersionedObject != nil && v.versionedAttr.Dirty {
        return o.GetObjectConvertor().Convert(
            v.versionedAttr.VersionedObject,
            v.versionedAttr.Attributes.GetObject(), nil)
    }
    return nil
}

6.2 callAttrMutatingHook:HTTP 调用核心

// staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go(行 244~392)
// callAttrMutatingHook 负责:发送请求 → 接收响应 → 验证 → 应用 Patch
func (a *mutatingDispatcher) callAttrMutatingHook(
    ctx context.Context,
    h *admissionregistrationv1.MutatingWebhook,
    invocation *generic.WebhookInvocation,
    attr *admission.VersionedAttributes,
    // ...
) (changed bool, err error) {
    // ========== 1. DryRun 检查:不能有副作用 ==========
    if attr.Attributes.IsDryRun() {
        if *h.SideEffects == SideEffectClassSome || *h.SideEffects == SideEffectClassNone {
            // 只有 SideEffectClassNone 或 SideEffectClassNoneOnDryRun 才允许 DryRun
            return false, ErrDryRunUnsupported
        }
    }

    // ========== 2. 构建 AdmissionReview 请求体 ==========
    uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
    // request 是序列化后的 v1.AdmissionReview 请求体

    // ========== 3. 获取 Webhook 的 HTTP 客户端 ==========
    client, err := invocation.Webhook.GetRESTClient(a.cm)
    if err != nil { return false, ErrCallingWebhook }

    // ========== 4. 发送 HTTP POST 请求到 Webhook 服务 ==========
    // 注意:使用 Webhook 配置的超时时间
    ctx, cancel := context.WithTimeout(ctx, time.Duration(*h.TimeoutSeconds)*time.Second)
    defer cancel()

    err = client.Post().Body(request).Do(ctx).Into(response)

    // ========== 5. 验证响应 ==========
    result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
    // 验证 UID 是否匹配、响应是否有效

    // ========== 6. 处理审计注解和警告 ==========
    for k, v := range result.AuditAnnotations {
        attr.Attributes.AddAnnotation(h.Name + "/" + k, v)
    }
    for _, w := range result.Warnings {
        warning.AddWarning(ctx, "", w)
    }

    // ========== 7. 如果拒绝,返回错误 ==========
    if !result.Allowed {
        return false, ErrWebhookRejection{Status: ToStatusErr(h.Name, result.Result)}
    }

    // ========== 8. 应用 JSON Patch 到对象 ==========
    if len(result.Patch) > 0 {
        patchObj, _ := jsonpatch.DecodePatch(result.Patch)
        objJS, _ := runtime.Encode(jsonSerializer, attr.VersionedObject)
        patchedJS, _ := patchObj.Apply(objJS)   // 应用 Patch
        // 解码回对象
        newVersionedObject, _, _ := jsonSerializer.Decode(patchedJS, nil, newVersionedObject)
        // 标记对象已被修改
        attr.Dirty = true
        attr.VersionedObject = newVersionedObject
        // 重新应用默认值(Mutating 修改后可能需要重新 Default)
        o.GetObjectDefaulter().Default(attr.VersionedObject)
        return true, nil   // changed=true
    }
    return false, nil
}

设计精髓

MutatingDispatcher 的 Patch 应用流程采用了累积 Patch模式:每个 Webhook 返回的 Patch 都会被应用到对象上,然后下一个 Webhook 看到的是前一个 Webhook 修改后的对象。这要求每个 Webhook 的 Patch 都基于同一个基础对象(原始请求)来编写,而不是基于前一个 Patch 的结果。Kubernetes 使用的是 RFC 6902 JSON Patch,每个 patch 操作是绝对路径(以 / 开头),因此不会出现"累积误差"问题。

七、MutatingWebhook 实现:JSON Patch 应用流程

思考记忆提示JSON Patch 是 MutatingWebhook 修改对象的标准方式——理解它就知道如何写一个 MutatingWebhook

  • Patch 是 RFC 6902 标准,op 有 add/replace/remove 三种常用操作
  • 多个 Webhook 的 Patch 串联应用,每个看到的是前一个修改后的结果
  • agnhost webhook 的 addLabel 示例演示了三种 Patch 场景
  • 面试高频提问:JSON Patch 的 add 和 replace 操作的区别是什么?什么时候用哪个?

Kubernetes MutatingWebhook 使用 RFC 6902 JSON Patch 作为标准修改格式。来看 Kubernetes 官方测试镜像 agnhost 中的实现(test/images/agnhost/webhook/addlabel.go):

// test/images/agnhost/webhook/addlabel.go(行 27~71)
// 演示三种场景下的 JSON Patch 写法
const (
    // 场景1:对象还没有 labels map,需要先创建 labels 再添加键值对
    addFirstLabelPatch = `[
        { "op": "add", "path": "/metadata/labels", "value": {"added-label": "yes"}}
    ]`

    // 场景2:对象已有 labels map,只需添加特定键
    addAdditionalLabelPatch = `[
        { "op": "add", "path": "/metadata/labels/added-label", "value": "yes" }
    ]`

    // 场景3:标签已存在但值不对,需要替换
    updateLabelPatch = `[
        { "op": "replace", "path": "/metadata/labels/added-label", "value": "yes" }
    ]`
)

func addLabel(ar v1.AdmissionReview) *v1.AdmissionResponse {
    reviewResponse := v1.AdmissionResponse{}
    reviewResponse.Allowed = true

    pt := v1.PatchTypeJSONPatch   // 声明 Patch 类型

    // 解析请求体中的对象
    obj := struct {
        metav1.ObjectMeta `json:"metadata,omitempty"`
    }{}
    json.Unmarshal(ar.Request.Object.Raw, &obj)

    labelValue, hasLabel := obj.ObjectMeta.Labels["added-label"]
    switch {
    case len(obj.ObjectMeta.Labels) == 0:
        // 标签 Map 不存在:add 操作创建 /metadata/labels
        reviewResponse.Patch = []byte(addFirstLabelPatch)
        reviewResponse.PatchType = &pt
    case !hasLabel:
        // 标签 Map 存在但没有这个键:add 操作在已有 map 中添加键
        reviewResponse.Patch = []byte(addAdditionalLabelPatch)
        reviewResponse.PatchType = &pt
    case labelValue != "yes":
        // 标签值不正确:replace 操作替换值
        reviewResponse.Patch = []byte(updateLabelPatch)
        reviewResponse.PatchType = &pt
    default:
        // 已经是 "yes":不返回 Patch(对象不需要修改)
    }
    return &reviewResponse
}

小贴士

JSON Patch 的三个核心操作:add 在指定路径添加值(路径不存在则创建,存在则替换);replace 替换已存在的值;remove 删除已存在的值。需要注意的是:JSON Patch 的路径使用 RFC 6901 规范——/metadata/labels/foo 表示 labels 下的 foo 键,/metadata/labels/-(减号)在 JSON Patch 中是特殊操作,表示在数组末尾添加。

八、ValidatingWebhook 实现:并行调用与短路逻辑

思考记忆提示ValidatingWebhook 的核心特点是并行调用和短路逻辑——一旦拒绝,后续 Webhook 不再调用

  • ValidatingWebhook 之间是并行调用(不串联),MutatingWebhook 是串联合并 Patch
  • 短路逻辑:任何一个 Webhook 拒绝,整个请求被拒绝
  • FailOpen/FailClosed 通过 FailurePolicy 配置
  • 面试高频提问:ValidatingWebhook 为什么选择并行而不是串行?这带来了什么权衡?

ValidatingDispatcher 的实现(staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go)与 MutatingDispatcher 有几个关键区别:

// staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go(行 88~175)
func (d *validatingDispatcher) Dispatch(ctx context.Context,
    attr admission.Attributes, o admission.ObjectInterfaces,
    hooks []webhook.WebhookAccessor) error {

    // ========== 1. 筛选出匹配的 hooks ==========
    var relevantHooks []*generic.WebhookInvocation
    versionedAttrAccessor := &versionedAttributeAccessor{...}
    for _, hook := range hooks {
        invocation, err := d.plugin.ShouldCallHook(ctx, hook, attr, o, versionedAttrAccessor)
        if err != nil { return err }
        if invocation == nil { continue }
        relevantHooks = append(relevantHooks, invocation)
    }
    if len(relevantHooks) == 0 { return nil }  // 无匹配,直接通过

    // ========== 2. 并行调用所有匹配的 hooks ==========
    wg := sync.WaitGroup{}
    errCh := make(chan error, 2*len(relevantHooks))
    wg.Add(len(relevantHooks))

    for i, invocation := range relevantHooks {
        go func(invocation *generic.WebhookInvocation, idx int) {
            defer wg.Done()
            // 调用单个 Webhook
            err := d.callHook(ctx, invocation, versionedAttrAccessor.versionedAttrs[invocation.Kind])
            if err != nil {
                errCh 

我的理解的意思是说

ValidatingWebhook 选择并行调用而不是串行,有两个原因:

第一,ValidatingWebhook 不修改对象,所以不需要等前一个的结果。每个 Webhook 验证的是同一个原始对象,互不干扰。串行调用不会带来额外价值,只会增加延迟。

第二,短路逻辑是关键:一旦任何一个 Webhook 返回拒绝,后续的 Webhook 就不再被调用。这类似于"与"运算——任何一个条件不满足,整体失败。这大大减少了不必要的 HTTP 调用(如果前置条件已经失败,后面的检查就是浪费)。

但这也带来一个权衡:多个 ValidatingWebhook 之间无法共享验证结果。如果两个 Webhook 都要做类似的检查(比如都检查镜像白名单),它们会各自调用一次,增加 API Server 的调用负载。

8.1 alwaysDeny ValidatingWebhook 示例

// test/images/agnhost/webhook/alwaysdeny.go(行 25~32)
// 最简单的 ValidatingWebhook:拒绝所有请求
func alwaysDeny(ar v1.AdmissionReview) *v1.AdmissionResponse {
    reviewResponse := v1.AdmissionResponse{}
    reviewResponse.Allowed = false   // 拒绝
    reviewResponse.Result = &metav1.Status{
        Message: "this webhook denies all requests",
    }
    return &reviewResponse
}

九、Webhook 配置:MutatingWebhookConfiguration 与 ValidatingWebhookConfiguration

思考记忆提示Webhook 配置决定了 Webhook 的作用范围和行为策略——理解它就能正确部署一个 Webhook

  • ClientConfig:指向 Webhook 服务的地址(Service 或 URL)
  • Rules:定义 Webhook 作用于哪些 API 请求(按 apiGroups/resources/operations 匹配)
  • NamespaceSelector / ObjectSelector:按命名空间或对象标签过滤
  • 面试高频提问:一个 Webhook 如果同时出现在 MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration 中,会发生什么?

Webhook 通过 Kubernetes API 对象注册到 API Server(staging/src/k8s.io/api/admissionregistration/v1/types.go):

# MutatingWebhookConfiguration 示例
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: my-mutating-webhook
webhooks:
  # ========== 核心配置 ==========
  - name: mutating-webhook.example.com     # Webhook 名称(必须唯一)
    sideEffects: None                      # 或 NoneOnDryRun(不允许有副作用必须声明)

    # ========== 调用目标配置 ==========
    clientConfig:
      service:
        namespace: webhook-system
        name: webhook-service          # Service 名称
        path: /mutating-pods            # 可选路径(默认 /)
        port: 443                       # Service 端口
      # 或使用 URL(用于集群外服务)
      # caBundle: LS0tLS1...            # Base64 编码的 CA 证书(用于验证服务端 TLS)

    # ========== 触发条件:请求匹配规则 ==========
    rules:
      - operations: ["CREATE", "UPDATE"]   # 监听哪些操作
        apiGroups: [""]                      # API Group(空字符串=核心 API)
        apiVersions: ["v1"]                  # API 版本
        resources: ["pods"]                 # 资源类型
        scope: "Namespaced"                 # 作用域:Namespaced/Cluster/*

    # ========== 行为策略 ==========
    failurePolicy: Ignore    # Fail = 拒绝请求,Ignore = 忽略错误继续
    matchPolicy: Equivalent  # Exact = 精确匹配,Equivalent = 等价资源匹配

    # ========== 选择器(额外过滤)==========
    namespaceSelector:           # 只作用于匹配的命名空间
      matchLabels:
        environment: production
    objectSelector:             # 只作用于匹配的对象
      matchLabels:
        webhook-enabled: "true"

    # ========== 超时时间(默认 10s)==========
    timeoutSeconds: 30

    # ========== 仅 MutatingWebhook ==========
    reinvocationPolicy: IfNeeded   # Never / IfNeeded

    # ========== 匹配条件(CEL 表达式,v1.27+)==========
    matchConditions:
      - name: exclude-leader-election
        expression: "request.operation != 'CREATE' || !request.object.metadata.labels.containsKey('control-plane')"


---
# ValidatingWebhookConfiguration 示例
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: my-validating-webhook
webhooks:
  - name: validating-webhook.example.com
    sideEffects: None
    clientConfig:
      service:
        namespace: webhook-system
        name: webhook-service
        path: /pods
      caBundle: LS0tLS1...
    rules:
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
        scope: "Namespaced"
    failurePolicy: Fail
    matchPolicy: Equivalent

注意

sideEffects 是必填字段,必须为以下四个值之一:None(无副作用)、NoneOnDryRun(仅在 DryRun 时无副作用)、Some(有副作用,必须在独立沙箱中运行)。这是 Kubernetes 为了防止生产事故而设计的强制声明——如果 Webhook 有副作用(修改集群状态),它必须在隔离的沙箱环境中运行,不能与 API Server 共享资源。

十、常见策略配置:FailurePolicy、MatchPolicy、SideEffects

思考记忆提示这三个配置决定了 Webhook 的安全边界和性能特性——配置错误可能导致集群故障

  • FailurePolicy:Fail(安全优先)vs Ignore(可用性优先)
  • MatchPolicy:Equivalent(推荐)vs Exact(严格匹配)
  • SideEffects:None/NoneOnDryRun/Some 必须如实声明
  • 面试高频提问:生产环境中的 MutatingWebhook 应该用 Fail 还是 Ignore?为什么?
配置项选项含义推荐场景
FailurePolicy Fail Webhook 调用失败时,拒绝请求 生产环境(安全优先)
Ignore Webhook 调用失败时,忽略错误继续处理 开发测试 / 非关键 Webhook
MatchPolicy Equivalent 请求修改等效资源时也调用(推荐) 生产环境(大多数场景)
Exact 只精确匹配规则中声明的资源 需要严格控制范围时
SideEffects None Webhook 永无副作用 生产环境(必须配合 DryRun)
NoneOnDryRun 仅在 DryRun 时无副作用 MutatingWebhook(允许在 DryRun 时调用)
Some 有副作用 必须在隔离沙箱中运行
Unknown 未知(不推荐)
ReinvocationPolicy
(仅 Mutating)
Never 只调用一次,不重新调用 默认
IfNeeded 树内插件修改对象后,重新调用 需要和内置插件协同工作时

小贴士

MatchPolicy: Equivalent 是推荐配置。例如,Deployment 在 apps/v1beta2apps/v1 两个版本都有定义。如果 Webhook 注册的规则是 apiVersions: ["apps/v1"], resources: ["deployments"],Equivalent 模式下,对 apps/v1beta2 Deployment 的请求也会被发送到 Webhook(因为它们是"等价"的)。Exact 模式下则不会。

十一、实战:一个完整的 MutatingWebhook 实现

思考记忆提示这是全篇的"工程落地"部分——从 HTTP 服务器到完整请求处理链路

  • HTTP Handler 需要处理两种版本的 AdmissionReview(v1 和 v1beta1)
  • serve() 函数负责请求反序列化、版本路由、响应序列化
  • TLS 证书配置是生产部署的关键
  • 面试高频提问:为什么 Webhook 服务需要 TLS?自签名证书如何生成和使用?

11.1 完整的 Webhook HTTP 服务器

// 基于 test/images/agnhost/webhook/main.go 整理
package webhook

import (
    "encoding/json"
    "io"
    "net/http"

    v1 "k8s.io/api/admission/v1"
    "k8s.io/api/admission/v1beta1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/klog/v2"
)

// ========== 1. 定义 Handler 类型 ==========
type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
type admitHandler struct {
    v1beta1 admitv1beta1Func   // v1beta1 版本处理函数
    v1      admitv1Func        // v1 版本处理函数
}

func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler {
    return admitHandler{
        v1beta1: delegateV1beta1AdmitToV1(f),   // v1beta1 → v1 转换
        v1:      f,
    }
}

// ========== 2. 核心 HTTP Handler ==========
func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {
    // 读取请求体
    var body []byte
    if r.Body != nil {
        data, _ := io.ReadAll(r.Body)
        body = data
    }

    // 验证 Content-Type
    contentType := r.Header.Get("Content-Type")
    if contentType != "application/json" {
        http.Error(w, "expect application/json", http.StatusBadRequest)
        return
    }

    // 反序列化:根据 GVK 判断版本
    deserializer := codecs.UniversalDeserializer()
    obj, gvk, err := deserializer.Decode(body, nil, nil)
    if err != nil {
        http.Error(w, "cannot decode: "+err.Error(), http.StatusBadRequest)
        return
    }

    var responseObj runtime.Object
    switch *gvk {
    // ========== v1beta1 版本处理 ==========
    case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"):
        review := obj.(*v1beta1.AdmissionReview)
        resp := &v1beta1.AdmissionReview{}
        resp.SetGroupVersionKind(*gvk)
        resp.Response = admit.v1beta1(*review)
        resp.Response.UID = review.Request.UID   // 必须原样返回 UID
        responseObj = resp

    // ========== v1 版本处理 ==========
    case v1.SchemeGroupVersion.WithKind("AdmissionReview"):
        review := obj.(*v1.AdmissionReview)
        resp := &v1.AdmissionReview{}
        resp.SetGroupVersionKind(*gvk)
        resp.Response = admit.v1(*review)
        resp.Response.UID = review.Request.UID
        responseObj = resp

    default:
        http.Error(w, "unsupported GVK: "+gvk.String(), http.StatusBadRequest)
        return
    }

    // 序列化响应并返回
    respBytes, _ := json.Marshal(responseObj)
    w.Header().Set("Content-Type", "application/json")
    w.Write(respBytes)
}

// ========== 3. 业务逻辑:添加标签的 MutatingWebhook ==========
func addLabel(ar v1.AdmissionReview) *v1.AdmissionResponse {
    obj := struct {
        metav1.ObjectMeta `json:"metadata,omitempty"`
    }{}
    json.Unmarshal(ar.Request.Object.Raw, &obj)

    reviewResponse := v1.AdmissionResponse{Allowed: true}
    pt := v1.PatchTypeJSONPatch

    switch {
    case len(obj.ObjectMeta.Labels) == 0:
        patch := `[{"op":"add","path":"/metadata/labels","value":{"injected":"true"}}]`
        reviewResponse.Patch = []byte(patch)
        reviewResponse.PatchType = &pt
    case obj.ObjectMeta.Labels["injected"] != "true":
        patch := `[{"op":"add","path":"/metadata/labels/injected","value":"true"}]`
        reviewResponse.Patch = []byte(patch)
        reviewResponse.PatchType = &pt
    }
    return &reviewResponse
}

// ========== 4. 主函数:路由注册 ==========
func main() {
    http.HandleFunc("/mutating-pods", func(w http.ResponseWriter, r *http.Request) {
        serve(w, r, newDelegateToV1AdmitHandler(addLabel))
    })
    http.HandleFunc("/always-deny", func(w http.ResponseWriter, r *http.Request) {
        serve(w, r, newDelegateToV1AdmitHandler(alwaysDeny))
    })
    http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) {
        w.Write([]byte("ok"))
    })

    server := &http.Server{Addr: ":443", TLSConfig: configTLS()}
    server.ListenAndServeTLS("", "")
}

我的理解的意思是说

这个 Webhook 实现有四个关键设计点:

  • 版本兼容:同时支持 v1 和 v1beta1 AdmissionReview,通过 GVK 判断走哪个分支。v1beta1 会自动转换到 v1 处理(delegate),保证业务逻辑只写一份。
  • UID 回传:Response.UID 必须等于 Request.UID。API Server 用 UID 匹配请求和响应,防止响应乱序或重放攻击。
  • Allowed 默认 true:ValidatingWebhook 建议在逻辑不拒绝时设置 Allowed=true;MutatingWebhook 必须返回 Patch 或 Allowed=true。
  • 幂等性:addLabel 实现了幂等逻辑——标签已存在且值正确时,不返回 Patch,API Server 不会重复修改。

十二、部署与运行:证书、TLS、Service 暴露

思考记忆提示这一节解决"如何把 Webhook 跑起来"的问题——证书生成是最容易出错的地方

  • Webhook 必须通过 HTTPS 提供服务,API Server 验证服务端证书
  • caBundle 必须包含 Webhook 服务端的根 CA 证书
  • Kubernetes 自动注入 Webhook CA 证书(k8s.io/legacy-unknown CA)
  • 面试高频提问:如何在本地测试时使用自签名证书?mutating webhook 如何与 validating webhook 配合?

12.1 证书生成

# ========== 1. 生成 CA(自签名)==========
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
    -out ca.crt -subj "/CN=webhook-ca"

# ========== 2. 生成 Webhook 服务器证书(由 CA 签名)==========
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
    -subj "/CN=webhook-service.webhook-system.svc" \
    -addext "subjectAltName=DNS:webhook-service.webhook-system.svc"

# ========== 3. 使用 CA 签署服务器证书 ===========
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out server.crt -days 365 -sha256 \
    -extfile <(printf "subjectAltName=DNS:webhook-service.webhook-system.svc")

# ========== 4. 验证证书 ==========
openssl x509 -in server.crt -text -noout | grep -A1 "Subject Alternative Name"

12.2 Kubernetes 部署清单

# ========== Deployment + Service ==========
apiVersion: v1
kind: Namespace
metadata:
  name: webhook-system
---
apiVersion: v1
kind: Service
metadata:
  name: webhook-service
  namespace: webhook-system
spec:
  ports:
    - port: 443
      targetPort: 443
  selector:
    app: webhook
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook
  namespace: webhook-system
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webhook
  template:
    metadata:
      labels:
        app: webhook
    spec:
      containers:
        - name: webhook
          image: my-webhook:v1.0.0
          ports:
            - containerPort: 443
          volumeMounts:
            - name: tls-certs
              mountPath: /etc/webhook/certs
              readOnly: true
          readinessProbe:
            httpGet:
              path: /readyz
              port: 443
            initialDelaySeconds: 5
            periodSeconds: 5
      volumes:
        - name: tls-certs
          secret:
            secretName: webhook-tls
---
# ========== ValidatingWebhookConfiguration ==========
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: my-validating-webhook
  # Webhook 通常需要持久化配置,不能依赖 webhook 自身存储
  # 使用 Opaque secret 或其他外部存储管理
webhooks:
  - name: my-webhook.example.com
    sideEffects: None
    clientConfig:
      service:
        namespace: webhook-system
        name: webhook-service
        path: "/pods"
      # caBundle:API Server 用于验证 Webhook 服务端证书的 CA
      # 如果 Webhook 使用 kubernetes.io/legacy-unknown CA(k8s 自动注入),可以留空
      # caBundle: LS0tLS1...   # Base64(ca.crt)
    rules:
      - operations: ["CREATE", "UPDATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
        scope: "Namespaced"
    failurePolicy: Fail
    timeoutSeconds: 30

注意

生产环境中,Webhook 的 caBundle 不能硬编码(否则证书轮换时需要更新 Config 并重启 API Server)。推荐使用 MutatingWebhookConfigurationValidatingWebhookConfiguration 中的 webhooks.clientConfig.service 引用 Service,让 API Server 通过服务发现自动获取 CA 证书。Kubernetes 1.27+ 还支持通过 service.nameservice.path 引用,API Server 会自动注入正确的 caBundle

十三、测试:单元测试与集成测试

思考记忆提示测试是 Webhook 开发中最容易被忽视但最重要的环节

  • 单元测试:用 Fake Webhook Server + Test Case 管理
  • 集成测试:在真实 API Server 中运行 Webhook
  • 常见的测试场景:正常通过、拒绝、Mutating Patch 验证
  • 面试高频提问:如何测试一个 MutatingWebhook 的 Patch 内容是否正确?

Kubernetes 提供了完整的 Webhook 测试框架(staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/):

// ========== 1. 准备测试场景 ==========
// staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go
// ValidatingWebhook 测试用例
validatingTest := []TestCase{
    {
        Name: "allow pods with allowed label",
        Webhook: &registrationv1.ValidatingWebhook{
            Name: "test-webhook.example.com",
            Rules: []registrationv1.RuleWithOperations{{
                Operations: []registrationv1.OperationType{registrationv1.Create},
                Rule: registrationv1.Rule{
                    APIGroups:   []string{""},
                    APIVersions: []string{"v1"},
                    Resources:   []string{"pods"},
                },
            }},
        },
        // Fake Webhook Server:返回 Allowed=true
        WebhookReactor: func() http.HandlerFunc {
            return func(w http.ResponseWriter, r *http.Request) {
                review := v1.AdmissionReview{}
                json.NewDecoder(r.Body).Decode(&review)
                resp := v1.AdmissionReview{
                    Response: &v1.AdmissionResponse{
                        UID:      review.Request.UID,
                        Allowed:  true,
                    },
                }
                json.NewEncoder(w).Encode(resp)
            }
        },
        Object: &v1.Pod{...},
        ExpectedDecision: TestDecisionAllow,
    },
    {
        Name: "deny pods with disallowed label",
        // Fake Webhook 返回拒绝
        ExpectedDecision: TestDecisionDeny,
        ExpectedReason:   "webhook rejected the request",
    },
}

// ========== 2. 运行测试 ==========
// TestWebhookSuite 运行完整的 Webhook 测试套件
func TestWebhookSuite(t *testing.T) {
    suite := webhook_testing.NewWebhookTestSuite("my-webhook-tests")
    suite.Register(validatingTest, mutatingTest)
    suite.Run(t)
}

// ========== 3. MutatingWebhook Patch 验证 ==========
mutatingTest := []TestCase{
    {
        Name: "adds injected label to pod",
        Webhook: &registrationv1.MutatingWebhook{...},
        WebhookReactor: func() http.HandlerFunc {
            return func(w http.ResponseWriter, r *http.Request) {
                review := v1.AdmissionReview{}
                json.NewDecoder(r.Body).Decode(&review)
                patch := `[{"op":"add","path":"/metadata/labels","value":{"injected":"true"}}]`
                resp := v1.AdmissionReview{
                    Response: &v1.AdmissionResponse{
                        UID:       review.Request.UID,
                        Allowed:   true,
                        Patch:     []byte(patch),
                        PatchType: func() *v1.PatchType { pt := v1.PatchTypeJSONPatch; return &pt }(),
                    },
                }
                json.NewEncoder(w).Encode(resp)
            }
        },
        Object: &v1.Pod{...},
        // ExpectedPatch:验证返回的 Patch 内容
        ExpectedPatch:     []byte(`[{"op":"add","path":"/metadata/labels","value":{"injected":"true"}}]`),
        ExpectedPatchType: v1.PatchTypeJSONPatch,
    },
}

// ========== 4. Fake HTTPS Server(测试用)==========
// webhook_server.go 提供了开箱即用的 Fake HTTPS Server
ts := webhook_testing.NewTestServer("testdata/tls.crt", "testdata/tls.key")
defer ts.Close()

// 注册自定义处理函数
ts.RegisterHandler("/mutating-pods", func(w http.ResponseWriter, r *http.Request) {
    // 处理请求...
})

设计精髓

Kubernetes 的 Webhook 测试框架通过Fake HTTPS Server实现了端到端的测试。测试服务器使用真实的 TLS 证书(可以是自签名),完全模拟 API Server 与 Webhook 之间的 HTTPS 通信。这确保了测试环境与生产环境高度一致——TLS 握手、HTTP 请求格式、响应序列化全部真实模拟。

十四、Roadmap:后续预告

思考记忆提示Webhook 是 Operator 开发的三大支柱之一,另外两个是 Controller 和 CRD

  • ValidatingAdmissionPolicy(CEL 表达式,无代码验证)
  • Kubebuilder / Operator-SDK 中的 Webhook 脚手架生成
  • MatchConditions 的 CEL 高级匹配表达式
  • Operator-SDK / Kubebuilder Webhook 脚手架:如何使用 kubebuilder 创建带 MutatingWebhook 和 ValidatingWebhook 的完整 Operator 项目,CRD 定义、Webhook 注册、单元测试全部自动生成。
  • ValidatingAdmissionPolicy(v1.26+):用 CEL 表达式替代代码编写的 ValidatingWebhook,不需要编写 Go 代码,只需要编写 YAML 配置。
  • MatchConditions 高级匹配:使用 CEL 表达式实现复杂的请求匹配条件(基于用户信息、对象标签、命名空间注解等)。

全篇必记总纲

Admission Webhook 的完整链路是:API Server(Authentication → Authorization)→ MutatingAdmissionWebhook(修改对象)→ ValidatingAdmissionWebhook(验证对象)→ Storage。MutatingWebhook 通过 JSON Patch 修改对象(多个 Webhook 串联 Patch),ValidatingWebhook 并行验证(短路逻辑)。两者通过 AdmissionReview 协议与外部服务通信,配置通过 MutatingWebhookConfiguration 和 ValidatingWebhookConfiguration 资源动态管理。


FAQ:常见疑问(20 组)

思考记忆提示FAQ 是全篇的"临考前速背"模块,20 组覆盖全部关键知识点

  • Q1-Q5 围绕 API 访问控制链路:Authentication vs Authorization vs Admission
  • Q6-Q10 围绕 AdmissionReview 协议:Request/Response 字段、UID 机制
  • Q11-Q15 围绕 MutatingWebhook:Patch 应用、Reinvocation、串联合并
  • Q16-Q20 围绕 ValidatingWebhook、配置策略和测试

Q1. Authentication 和 Authorization 的区别是什么?它们和 Admission 的关系是什么?

Authentication 验证"你是谁",Authorization 验证"你能做什么",Admission 验证"你的请求是否合规"。Authentication(认证)发生在最前面,通过 X509 证书、Bearer Token 或 OIDC Token 验证请求者的身份,输出 UserInfo(username, uid, groups)。Authorization(授权)接在认证后面,通过 RBAC/ABAC/Webhook 检查该用户是否有权限执行这个操作。Admission 位于两者之后,验证请求本身是否符合集群的策略规范(与身份无关)。三者的关系是:先确认身份,再检查权限,最后检查合规性——任何一步失败,请求都不会到达 etcd。

Q2. MutatingWebhook 和 ValidatingWebhook 的执行顺序是什么?为什么这个顺序不能颠倒?

MutatingWebhook 必须在 ValidatingWebhook 之前执行,这个顺序不能颠倒。因为 MutatingWebhook 可以修改对象(比如添加标签、设置默认值),修改后的对象才是最终会被持久化的状态。ValidatingWebhook 需要验证的是最终状态,不是原始状态。如果 ValidatingWebhook 先执行,它验证的是用户提交的对象,而不是实际会被写入 etcd 的对象——这会导致验证结果不准确。

Q3. 为什么 MutatingWebhook 可以多次调用(Reinvocation),而 ValidatingWebhook 只能调用一次?

MutatingWebhook 多次调用是为了与树内内置插件(如 DefaultStorageVersion)配合;ValidatingWebhook 一次就足够,因为每个 Webhook 验证的是同一个原始对象。树内内置的 Mutating 插件在外部 Webhook 之后执行(称为"树内插件")。如果树内插件修改了对象(如设置默认值),MutatingWebhook 需要重新看到修改后的对象,以便做进一步的修改或验证。ReinvocationPolicy=IfNeeded 可以实现这个效果。ValidatingWebhook 不修改对象,每个 Webhook 验证的是同一个原始对象,多次调用没有意义,反而增加延迟。

Q4. AdmissionReview 的 Request.UID 和 Response.UID 必须一致,这是为什么?

UID 是防重放和请求匹配的唯一标识,确保 API Server 能正确匹配请求和响应。API Server 可能同时向多个 Webhook 发送请求,多个 Webhook 的响应可能以任意顺序返回(ValidatingWebhook 并行调用)。通过 UID,API Server 能准确地将每个响应与对应的请求匹配。如果 UID 不匹配或缺失,VerifyAdmissionResponse 会返回错误,请求被拒绝。

Q5. JSON Patch 的 add 和 replace 操作有什么区别?什么时候用哪个?

add 在指定路径添加值(路径不存在则创建,存在则替换),replace 只替换已存在的值。add 用于两种场景:1)路径本身不存在(如对象还没有 labels map),需要先创建父路径;2)在已有数组或对象中添加键。replace 用于路径已存在的情况——如果路径不存在,replace 会报错。编写 MutatingWebhook 的 Patch 时,通常先用 add 尝试,如果 add 报错再用 replace。

Q6. MutatingWebhook 返回空 Patch(len(result.Patch)==0)时,API Server 会怎么处理?

空 Patch 表示该 Webhook 不修改对象,API Server 跳过 Patch 应用,继续处理下一个 Webhook。在 dispatcher.go:333~334 的逻辑中,如果 len(result.Patch)==0,直接返回 changed=false,对象保持不变。但需要注意:如果 Webhook 返回 Allowed=false(拒绝请求),即使有 Patch 也不会被应用(请求直接被拒绝)。

Q7. FailurePolicy 的 Fail 和 Ignore 有什么区别?生产环境应该如何选择?

Fail 在 Webhook 调用失败时拒绝请求(安全优先),Ignore 忽略失败继续处理(可用性优先)。生产环境推荐 Fail——如果 Webhook 是安全策略的一部分(如镜像白名单),调用失败时拒绝请求比放行更安全。对于非关键性 Webhook(如仅用于添加可选标签),可以设为 Ignore,避免 Webhook 服务临时不可用时阻塞所有资源创建。

Q8. sideEffects 字段为什么必须声明?有哪些合法值?

sideEffects 是 Kubernetes 的强制安全声明,防止有副作用的 Webhook 污染 API Server。四个合法值:None(永无副作用)、NoneOnDryRun(仅 DryRun 时无副作用)、Some(有副作用,必须在隔离沙箱中运行)、Unknown(不推荐)。如果 Webhook 每次请求都会修改集群状态(如写数据库、发消息),必须声明为 Some。声明不实可能导致 API Server 拒绝加载该 Webhook 配置。

Q9. NamespaceSelector 和 ObjectSelector 的区别是什么?

NamespaceSelector 根据请求的命名空间标签过滤,ObjectSelector 根据请求对象的标签过滤。NamespaceSelector 是在请求进入 API Server 时就能判断的(已知目标命名空间),不需要解析对象内容,适合做"整个命名空间的开关"。ObjectSelector 需要先解析对象标签,适合做"按资源标签精细控制"。两者可以同时使用,只有同时满足才调用 Webhook。

Q10. MatchPolicy 的 Equivalent 和 Exact 有什么区别?

Equivalent 会匹配所有与规则等价的 API 请求,Exact 只匹配精确声明的资源。例如,Deployment 同时在 apps/v1 和 apps/v1beta1 中定义(等价资源)。如果 Webhook 规则声明 apiVersions: ["apps/v1"],Equivalent 模式下对 apps/v1beta2 Deployment 的请求也会发送到 Webhook(因为是等价资源)。Exact 模式则不会。建议使用 Equivalent(默认行为)。

Q11. ValidatingWebhook 为什么选择并行调用而不是串行?这带来了什么权衡?

并行调用可以减少总延迟(所有 Webhook 同时执行,取最长耗时);串行调用没有意义,因为 ValidatingWebhook 不修改对象,验证的是同一个原始对象。权衡是:多个 Webhook 如果都做类似的验证(如镜像白名单检查),会重复调用,浪费 API Server 的调用资源。但对于不同维度的验证(如镜像白名单 + 资源配额 + 安全上下文),并行调用的延迟优势明显。

Q12. MatchConditions(CEL 表达式)的作用是什么?它和 Rules 的区别是什么?

MatchConditions 用 CEL 表达式实现比 Rules 更灵活的匹配条件,基于请求元数据和对象内容做判断。Rules 按 apiGroups/apiVersions/resources/operations 做粗粒度过滤(只能判断"是否是 Pod")。MatchConditions 可以基于请求的任何信息做判断,例如:"request.userInfo.username != 'system:kube-proxy'""request.object.spec.priority != nil"。MatchConditions 在 Rules 之后执行,作为更细粒度的二次过滤。

Q13. Webhook 的 timeoutSeconds 默认值是多少?超时后会怎样?

timeoutSeconds 默认 10 秒,超时后按 FailurePolicy 处理。如果 FailurePolicy=Fail,超时会拒绝请求;如果 FailurePolicy=Ignore,超时会忽略错误继续。建议将 timeoutSeconds 设置为 Webhook 实际处理时间的 1.5~2 倍,留足 buffer 应对网络抖动和 GC 暂停。

Q14. MutatingWebhook 如何处理多个 Webhook 串联的 Patch 合并?

每个 Webhook 的 Patch 都是基于原始对象的绝对路径修改,API Server 依次应用所有 Patch,形成累积效果。例如:Webhook A 添加 labels.webhook-a,Webhook B 添加 labels.webhook-b,最终对象会同时有两个标签。Webhoo k 的 Patch 是基于请求 JSON 序列化后的字节流来编写的(RFC 6902 JSON Patch),每个操作是绝对路径,不会依赖前一个 Patch 的结果。

Q15. 如何测试一个 MutatingWebhook 返回的 Patch 内容是否正确?

通过 webhook_testing.TestCase 的 ExpectedPatch 和 ExpectedPatchType 字段验证。Kubernetes 提供了完整的 Webhook 测试框架,通过 NewTestServer 创建 Fake HTTPS Server,注册返回特定 Patch 的处理函数,然后通过 webhook_testing.TestCase 定义期望的 Patch 内容(Base64 编码或 JSON 字符串),框架会自动验证返回的 Patch 是否与期望一致。

Q16. 为什么 Webhook 服务必须使用 HTTPS?自签名证书如何配置?

API Server 要求与 Webhook 的通信必须是加密的,以防止中间人攻击和数据篡改。自签名证书的流程:1)生成 CA;2)用 CA 签署 Webhook 服务器证书(CN 必须匹配 Service DNS 名);3)将 CA 证书的 Base64 编码放入 webhook.clientConfig.caBundle;4)服务器加载服务器证书和私钥。如果使用 Kubernetes 1.27+,可以省略 caBundle(API Server 会自动从 Service 发现中获取正确的 CA)。

Q17. Webhook 在 Kubernetes 中的服务发现机制是什么?API Server 如何找到 Webhook 服务?

通过 MutatingWebhookConfiguration/ValidatingWebhookConfiguration 中的 clientConfig.service 引用 Kubernetes Service。API Server 在启动时和配置变更时(通过 Informer)从配置对象中读取 Service 引用(namespace + name),通过 Kubernetes DNS 解析到 Pod IP。API Server 验证 TLS 证书时,会用 caBundle 中的 CA 验证服务端证书的 CN/SAN 是否与服务 DNS 匹配。

Q18. 如何让 Webhook 同时支持 v1 和 v1beta1 AdmissionReview?

在 serve() 函数中根据 GVK(GroupVersionKind)判断走哪个分支,v1beta1 路由到 v1 处理函数(版本转换)。实现模式:case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"): ... admit.v1beta1(*review),在 v1beta1 处理函数中调用 convertAdmissionRequestToV1() 将请求转换为 v1 格式,调用 v1 业务逻辑,再通过 convertAdmissionResponseToV1beta1() 转换回 v1beta1 响应。这样业务逻辑只需实现一份。

Q19. DryRun 模式下,MutatingWebhook 的行为有什么特殊限制?

DryRun 时,只有 sideEffects=None 或 NoneOnDryRun 的 MutatingWebhook 才会被调用。如果 Webhook 声明了 sideEffects=Some(可能有副作用),API Server 不会在 DryRun 请求中调用它,返回 DryRunUnsupported 错误。这是为了防止在 DryRun 场景下产生意外的集群状态变更。

Q20. AuditAnnotations 和 Warnings 有什么区别?它们在什么地方可见?

AuditAnnotations 记录到审计日志(需要开启审计策略),Warnings 返回给 API 客户端。AuditAnnotations 是键值对(如 "imagepolicy.example.com/reason": "image blacklisted"),由 API Server 根据审计策略决定是否记录到审计日志。Warnings 是字符串数组(v1.36 新增),直接返回在 HTTP 响应头 Warning: 299 ... 中,kubectl 客户端会显示给用户。两者的用途不同:审计用于事后追溯,警告用于实时通知。