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

如果你曾试图在一批 Kubernetes 集群中强制执行安全策略,很可能遇到过一个令人头疼的先后依赖问题。准入策略是 API 对象,因此必须有人先创建,它们才会存在;具备相应权限的人也能删除它们。在集群引导过程中,总有一段策略尚未生效的时间窗口,而且无法阻止高权限用户将策略移除。

Kubernetes v1.36 引入了一项处于 Alpha 阶段的功能来解决这个问题:基于清单的准入控制(manifest-based admission control)。你可以把准入 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 准入策略被删除。对于管理共享集群的平台团队,这是显著的改进:可以保证集群管理员不会通过 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,甚至挂载的 ConfigMap,在整批集群中滚动更新策略,而不造成 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 频道联系团队;加入 Slack 的邀请入口为 slack.k8s.io。

如何参与

如果你希望为这项功能或 SIG API Machinery 的其他项目贡献代码,可以加入上述 Slack 频道,也欢迎参加每隔一个星期三举行的 SIG API Machinery 会议。

来源与许可

译自 Kubernetes v1.36: Admission Policies That Can’t Be Deleted,2026年5月4日。作者:Anish Ramasekar(Microsoft)、Benjamin Elder(Google)。文章讨论的是 Kubernetes v1.36 的 Alpha 功能;“无法删除”指无法通过 Kubernetes API 删除,磁盘上的清单仍可修改。

原文采用 CC BY 4.0,参见 Kubernetes 网站许可证。中文译文亦采用 CC BY 4.0。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容