元鉴
返回中文阅读流

Kubernetes Blog

Kubernetes v1.36:无法被删除的准入策略

Kubernetes v1.36 引入基于清单的准入控制,支持启动时加载不可通过 API 删除的策略。

中文内容

已翻译official company source英文原文2026-05-04

Kubernetes v1.36:无法被删除的准入策略

By Anish Ramasekar (Microsoft), Benjamin Elder (Google) | Monday, May 04, 2026

如果你曾尝试在一组 Kubernetes 集群中强制执行安全策略,可能遇到过令人沮丧的“先有鸡还是先有蛋”问题。你的准入策略是 API 对象,这意味着它们只有在有人创建后才存在,并且任何拥有相应权限的人都可以删除它们。在集群引导期间,总会有一段时间策略尚未生效,而且无法阻止特权用户将其移除。

Kubernetes v1.36 引入了一个用于解决该问题的 alpha 特性:基于清单的准入控制。它允许你将准入 webhook 和基于 CEL 的策略定义为磁盘上的文件,由 API 服务器在启动时、开始处理任何请求之前加载。

我们正在弥合的缺口

如今大多数 Kubernetes 策略执行都通过 API 工作。你将 ValidatingAdmissionPolicy 或 webhook 配置作为 API 对象创建,准入控制器会接收它。这在稳定状态下运行良好,但存在一些根本限制。

在集群引导期间,从 API 服务器开始处理请求到你的策略被创建并生效之间存在一个空档。如果你正在从备份恢复,或从 etcd 故障中恢复,这个空档可能会很明显。

还存在一个自我保护问题。准入 webhook 和策略无法拦截对其自身配置资源的操作。Kubernetes 会跳过对 ValidatingWebhookConfiguration 等类型调用 webhook,以避免循环依赖。这意味着拥有足够权限的用户可以删除你的关键准入策略,而准入链中没有任何机制可以阻止他们。

我们——Kubernetes SIG API Machinery——希望有一种方式可以明确表示:“这些策略始终启用,没有例外。”

工作原理

你可以在已经通过 --admission-control-config-file 传递给 API 服务器的 AdmissionConfiguration 文件中添加 staticManifestsDir 字段。将它指向一个目录,把策略 YAML 文件放在那里,API 服务器就会在开始提供服务前加载它们。

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionPolicy
  configuration:
    apiVersion: apiserver.config.k8s.io/v1
    kind: ValidatingAdmissionPolicyConfiguration
    staticManifestsDir: "/etc/kubernetes/admission/validating-policies/"

这些清单文件是标准 Kubernetes 资源定义。唯一要求是,这些清单定义的所有对象名称都必须以 .static.k8s.io 结尾。这个保留后缀可防止与基于 API 的配置发生冲突,并且在查看指标或审计日志时,便于判断某个准入决策来自哪里。

下面是一个完整示例,用于拒绝 kube-system 之外的特权容器:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: "deny-privileged.static.k8s.io"
  annotations:
    kubernetes.io/description: "Deny launching privileged pods, anywhere this policy is applied"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: ["v1"]
      operations: ["CREATE", "UPDATE"]
      resources: ["pods"]
  variables:
  - name: allContainers
    expression: >-
      object.spec.containers +
      (has(object.spec.initContainers) ? object.spec.initContainers : []) +
      (has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
  validations:
  - expression: >-
      !variables.allContainers.exists(c,
      has(c.securityContext) && has(c.securityContext.privileged) &&
      c.securityContext.privileged == true)
    message: "Privileged containers are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "deny-privileged-binding.static.k8s.io"
  annotations:
    kubernetes.io/description: "Bind deny-privileged policy to all namespaces except kube-system"
spec:
  policyName: "deny-privileged.static.k8s.io"
  validationActions:
  - Deny
  matchResources:
    namespaceSelector:
      matchExpressions:
      - key: "kubernetes.io/metadata.name"
        operator: NotIn
        values: ["kube-system"]

保护过去无法保护的内容

我们最感到兴奋的部分,是能够拦截针对准入配置资源本身的操作。

使用基于 API 的准入时,webhook 和策略永远不会在 ValidatingAdmissionPolicy 或 ValidatingWebhookConfiguration 等类型上被调用。这个限制有充分理由:如果某个 webhook 可以拒绝对其自身配置的更改,你可能会被锁在外面,无法通过 API 修复问题。

基于清单的策略没有这个问题。如果某个有问题的策略阻止了不该阻止的内容,你可以修复磁盘上的文件,API 服务器会接收该更改。不存在循环依赖,因为恢复路径不经过 API。

这意味着你可以编写一个基于清单的策略,阻止关键的基于 API 的准入策略被删除。对于管理共享集群的平台团队来说,这是一个重要改进。现在你可以保证基线安全策略不会被集群管理员移除,无论是意外还是其他原因。

下面是实际效果。该策略会阻止对带有 platform.example.com/protected: "true" 标签的准入资源进行任何修改或删除:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: "protect-policies.static.k8s.io"
  annotations:
    kubernetes.io/description: "Prevent modification or deletion of protected admission resources"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: ["admissionregistration.k8s.io"]
      apiVersions: ["*"]
      operations: ["DELETE", "UPDATE"]
      resources:
      - "validatingadmissionpolicies"
      - "validatingadmissionpolicybindings"
      - "validatingwebhookconfigurations"
      - "mutatingwebhookconfigurations"
  validations:
  - expression: >-
      !has(oldObject.metadata.labels) ||
      !('platform.example.com/protected' in oldObject.metadata.labels) ||
      oldObject.metadata.labels['platform.example.com/protected'] != 'true'
    message: "Protected admission resources cannot be modified or deleted"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "protect-policies-binding.static.k8s.io"
  annotations:
    kubernetes.io/description: "Bind protect-policies policy to all admission resources"
spec:
  policyName: "protect-policies.static.k8s.io"
  validationActions:
  - Deny

部署后,任何标记为 platform.example.com/protected: "true" 的基于 API 的准入策略或 webhook 配置都会受到防篡改保护。保护机制本身位于磁盘上,无法通过 API 移除。

需要了解的几点

基于清单的配置有意设计为自包含。它们不能引用 API 资源,这意味着策略不能使用 paramKind,准入 webhook 不能使用 Service 引用(而是仅支持 URL),并且绑定只能引用同一清单集中的策略。这些限制存在的原因是,这些配置需要在没有任何集群状态的情况下工作,包括启动时 etcd 尚不可用的阶段。

如果运行多个 API 服务器实例,每个实例都会独立加载自己的清单文件。系统没有内置跨服务器同步。这与其他基于文件的 API 服务器配置(如静态加密)采用相同模型。启用该特性后,Kubernetes 会在相关指标上以标签形式暴露配置哈希,因此你可以检测配置漂移。

运行时会监视文件变更,因此更新策略时不需要重启 API 服务器。如果更新清单文件,API 服务器会验证新配置并以原子方式切换。如果验证失败,它会保留上一个有效配置并记录错误。这意味着你可以使用标准配置管理工具(Ansible、Puppet,甚至挂载的 ConfigMaps)在整个集群群组中推出策略变更,而无需 API 服务器停机。

启动时的初始加载更加严格:如果任何清单无效,API 服务器将不会启动。这是有意设计的。启动时快速失败比在未加载预期策略的情况下运行更安全。

试用

要在 Kubernetes v1.36 中试用该特性:

  1. 为每个 kube-apiserver 启用 ManifestBasedAdmissionControlConfig 特性门控。
  2. 创建一个包含静态清单文件的目录。如果需要将其挂载到运行 API 服务器的 Pod 中,也请一并完成。只读挂载即可。
  3. 在 AdmissionConfiguration 中使用目录路径配置 staticManifestsDir。
  4. 启动 API 服务器,并让 --admission-control-config-file 指向你的 AdmissionConfiguration 文件。

完整文档位于 Manifest-Based Admission Control,你也可以关注 KEP-5793 以了解后续进展。

我们希望听到你的反馈。请在 Kubernetes Slack 的 #sig-api-machinery 频道联系我们(如需邀请,请访问 https://slack.k8s.io/ )。

如何参与

如果你有兴趣为该特性或其他 SIG API Machinery 项目做贡献,请在 Kubernetes Slack 上加入 #sig-api-machinery。也欢迎你参加每隔一个星期三举行的 SIG API Machinery 会议。

  • ← 上一篇
  • 下一篇 →

原文标题

Kubernetes v1.36: Admission Policies That Can't Be Deleted