MutatingAdmissionPolicy 实战指南:用声明式策略替代准入 Webhook
【免费下载链接】websiteKubernetes website and documentation repo:项目地址: https://gitcode.com/GitHub_Trending/webs/website
MutatingAdmissionPolicy 是 Kubernetes 提供的声明式变更准入特性:它让 API Server 在资源持久化之前按策略改写对象,自 1.36 版本起正式 GA(默认开启)。它解决的是"自动给资源打标签、注 sidecar、补默认值"这类需求——以往只能写一个独立 Webhook 服务实现的事情,现在用两份 YAML 就能完成,不需要证书、独立部署和额外端口。
一个 Webhook 团队的日常,先算笔账
假设平台组维护着一个 MutatingWebhook,用途很朴素:给新建的 Deployment 打上team标签和 owner 注解。围绕它必须做的事:
- 签发并轮换 Webhook 的 client CA 证书;
- 配置 admissionregistration 里的
caBundle与 443 端口 TLS 要求; - 至少 2 副本防单点,配好健康检查;
- 每次调整注入逻辑(比如换镜像 tag)都要走一次 CI/CD 发布。
如果策略只是"加标签"这一类字段级修改,这套成本明显偏高。MutatingAdmissionPolicy 的执行逻辑直接编译进 kube-apiserver,用 CEL(Common Expression Language)表达式描述改动,产物是一个 patch,全程不出集群边界。
请求是如何被策略改写并落地的
一次匹配的 API 请求,在准入链中的流转如下:
策略不是单对象,而是由三类资源拼成,职责分离:
| 对象 | 职责 | 关键点 |
|---|---|---|
MutatingAdmissionPolicy | 描述"匹配什么、改什么"(抽象逻辑) | paramKind声明参数类型;matchConditions写 CEL 前置条件 |
| 参数资源(ConfigMap、CRD 等) | 承载具体取值 | 通过 binding 的spec.paramRef关联,CEL 中以params访问 |
MutatingAdmissionPolicyBinding | 把策略接到集群并限定作用范围 | 没有 binding,策略不生效;matchResources/namespaceSelector在这里收窄 |
CEL 表达式可访问的常用变量:object(本次请求对象)、oldObject(变更前对象,CREATE 时为 null)、request(请求属性)、namespaceObject(所属命名空间)、params(参数资源)。注意matchConditions中访问不到namespaceObject和variables,详见官方概念文档。
如何部署第一条 MutatingAdmissionPolicy
目标:默认命名空间新建的 Deployment 自动带上team标签和 owner 注解,无需参数,先看最小可用形态。
apiVersion: admissionregistration.k8s.io/v1 kind: MutatingAdmissionPolicy metadata: name: default-deploy-policy spec: matchConstraints: resourceRules: - apiGroups: ["apps"] apiVersions: ["v1"] operations: ["CREATE"] resources: ["deployments"] matchConditions: - name: owner-missing expression: "!has(object.metadata.labels['owner'])" mutations: - patchType: JSONPatch jsonPatch: expression: > [ JSONPatch{op: "add", path: "/metadata/labels/team", value: "platform"} ]matchConditions保证"已有 owner 标签的请求不再处理",避免重复执行;mutations里的 CEL 表达式求值后产出一条 JSON Patch。
apiVersion: admissionregistration.k8s.io/v1 kind: MutatingAdmissionPolicyBinding metadata: name: default-deploy-policy-binding spec: policyName: default-deploy-policy matchResources: namespaceSelector: matchLabels: purpose: default-apps failurePolicy: IgnorepolicyName必须与 Policy 的metadata.name一致,这是两者唯一的连接点;matchResources限定只作用于purpose: default-apps的命名空间。failurePolicy设在 binding 上,Ignore表示 CEL 求值失败时跳过改动并记日志。
验证:
kubectl apply -f deploy-policy.yaml -f deploy-binding.yaml kubectl get mutatingadmissionpolicies kubectl get mutatingadmissionpolicybindings kubectl get deploy my-deploy -o jsonpath='{.metadata.labels}' # 期望输出包含 "team":"platform"常见坑:Policy 建了、Binding 忘了建,策略会静默不生效,kubectl get两个资源对照一下 name 是否一致。
用 ConfigMap 作为参数资源给策略供数
目标:owner 和环境值不应该硬编码在策略里,改为从 ConfigMap 读取,换值不重新发布策略。
apiVersion: v1 kind: ConfigMap metadata: name: deploy-meta namespace: default data: owner: "team.example.com/billing" environment: "prod"apiVersion: admissionregistration.k8s.io/v1 kind: MutatingAdmissionPolicy metadata: name: deploy-owner-policy spec: paramKind: apiVersion: v1 kind: ConfigMap matchConstraints: resourceRules: - apiGroups: ["apps"] apiVersions: ["v1"] operations: ["CREATE"] resources: ["deployments"] mutations: - patchType: JSONPatch jsonPatch: expression: > [ JSONPatch{op: "add", path: "/metadata/annotations/" + jsonpatch.escapeKey("team.example.com/owner"), value: params.data.owner}, JSONPatch{op: "add", path: "/metadata/annotations/team.example.com/environment", value: params.data.environment} ] --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingAdmissionPolicyBinding metadata: name: deploy-owner-policy-binding spec: policyName: deploy-owner-policy paramRef: name: deploy-meta namespace: default matchResources: namespaceSelector: matchLabels: purpose: default-apps failurePolicy: Ignoreparams.data.owner即绑定 ConfigMap 的data.owner字段;jsonpatch.escapeKey()负责转义路径键中的/(转成~1),含斜杠的注解 key 必须走它。参数资源还可以是自定义 CRD,改镜像版本、换 owner 只改数据,不动策略本身。
JSON Patch 条件分支与 ApplyConfiguration 何时选用
JSON Patch 支持完整的 RFC 6902 操作符(add、remove、replace、move、copy、test),test可以构造"先查后改"的分支:
[ JSONPatch{op: "test", path: "/spec/template/spec/containers/0/image", value: "app/web:debug"}, JSONPatch{op: "replace", path: "/spec/template/spec/containers/0/image", value: "app/web:stable-2026.10"} ]前一条test校验镜像是否为 debug 版,不满足时整组 patch 执行失败,由failurePolicy决定后续行为。
ApplyConfiguration 模式则是用 CEL 直接构造对象结构,按 Server-Side Apply 的合并规则改写:
Object{ metadata: Object.metadata{ annotations: Object.metadata.annotations{ "policy.k8s.local/applied-by": "deploy-owner-policy" } } }它只覆盖写出的字段,其余内容保持不变;限制是不能直接修改原子字段(整个 map 或 array 会被视为整体)。
两种 patch 形态的取舍:
| 维度 | JSONPatch | ApplyConfiguration |
|---|---|---|
| 典型场景 | 点状增删改、条件分支、数组插入 | 结构化字段声明式覆盖 |
| 路径写法 | JSON Pointer 字符串,需 escapeKey | 字段路径按Object.xxx写法 |
| 数组处理 | add到path/-追加 | 受限,避免直接改原子 array |
| 条件能力 | 可用test操作符 | 表达式本身可用完整 CEL 判断 |
两者都能做条件判断,选择看改动形态而非性能。仓库里可直接参考 applyconfiguration-example.yaml 和 json-patch-example.yaml 两个示例。
策略没生效时按这 5 步排查
👇 排障时按表从上往下走,前 4 项覆盖了绝大多数"看起来没工作"的情况:
| 症状 | 常见原因 | 处理 |
|---|---|---|
| 改动完全没发生 | 只有 Policy 没有 Binding;或policyName与metadata.name对不上 | kubectl get mutatingadmissionpolicybindings -o yaml核对 |
| 改动完全没发生 | matchResources/namespaceSelector没覆盖目标命名空间 | 确认目标 ns 带上了被选择器命中的标签 |
| 想按命名空间标签过滤,条件不生效 | matchConditions中namespaceObject恒为 null | 把命名空间条件挪到 binding 的namespaceSelector |
| 请求被拒绝且日志有 CEL 报错 | CEL 求值失败且failurePolicy: Fail(默认) | 非关键改动改为Ignore,并用 CEL Playground 离线验证表达式 |
| 想给 Policy/Binding 自身打标签,不生效 | 准入配置类资源豁免于策略(防自举死锁) | 用kubectl label手工维护,或走 manifest-based admission control |
补充两条硬约束:TokenReview、SubjectAccessReview 等鉴权类虚拟资源对所有策略豁免;kubectl describe mutatingadmissionpolicybinding <name>可以看到 binding 的 status 信息,配合 apiserver 日志(--v=4以上可见 CEL 求值细节)定位表达式错误。
MutatingAdmissionPolicy 与 MutatingWebhook 怎么选
| 维度 | MutatingAdmissionPolicy | MutatingAdmissionWebhook |
|---|---|---|
| 运行位置 | API Server 进程内 | 独立服务 + 网络调用 |
| 证书 / 端口 / 副本 | 不需要 | 全部需要维护 |
| 表达能力 | CEL 可表达的逻辑(含条件、参数) | 任意代码,可查外部 API、数据库 |
| 性能开销 | 进程内求值 | 每请求一次 HTTPS 往返 |
| 变更发布 | 改 YAML 即生效 | 发版 + 重启 |
| 故障形态 | 按failurePolicy拒绝或跳过 | Webhook 不可达时同样按 failurePolicy,另叠加服务运维风险 |
适用边界:字段默认值、标签/注解注入、简单条件改写、按参数供数 → Policy;需要跨对象查询、调外部系统、复杂状态机 → Webhook。两者可以并存,同一请求会串行经过所有命中的变更插件。
选型速查表与版本注意
| 问题 | 结论 |
|---|---|
| 我的集群版本够吗 | 1.30–1.33 alpha、1.34–1.35 beta,1.36 起 GA 且默认开启,详见 Feature Gate 页面 |
| API group 用哪个 | GA 后为admissionregistration.k8s.io/v1,旧教程里的v1alpha1需核对版本 |
| 策略需要参数吗 | 不需要则省略spec.paramKind,binding 里也不写paramRef |
| 想改镜像版本、owner 这类常变值 | 放参数资源(ConfigMap/CRD),别写死在表达式里 |
| CEL 表达式哪里试 | 用 CEL Playground 离线验证,再kubectl apply |
| 上线节奏 | 开发集群先启用 →matchResources限定最小范围观察 → 再逐步扩大 |
决策原则一句话:能写成 CEL 表达式的字段级改动,优先 Policy;必须出集群查数据,才保留 Webhook。落地前以官方文档中你所用版本的字段说明为准。
【免费下载链接】websiteKubernetes website and documentation repo:项目地址: https://gitcode.com/GitHub_Trending/webs/website
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考