从Privileged到Restricted:Kubernetes Pod安全渐进式迁移实战复盘
事情起因是年初一次内部安全巡检我们集群里跑着的 workload一半以上容器是privileged: true还有一部分虽然没显式写 privileged但因为一个老旧的 MutatingAdmissionWebhook 默认就往 Pod 里补特权配置安全团队给了两周整改时间。我第一反应是直接全量切Restricted结果第一轮就误伤一片——ingress-nginx 起不来、CSI 插件报错、几个老 StatefulSet 直接 CrashLoopBackOff。那次之后我算是彻底想明白了一件事Pod 安全加固这事不是你不给权限就行你得先知道你手里的工作负载到底依赖什么能力然后一层一层往下剥。这篇文章我就把从 Privileged 到 Restricted 的渐进迁移过程完整复盘一遍。内容包括三个安全级别到底差在哪、怎么盘点存量 Pod 的特权画像、从 Baseline 切到 Restricted 会踩哪些具体坑、以及 PSA、Kyverno、OPA 这些策略引擎该怎么选。文中涉及的命令和 YAML 都是我在真实集群里跑过的适合正在做 K8s 安全整改、或者刚接触 Pod Security Standards 的同学参考。1. 为什么一上来就切 Restricted注定翻车很多人对 Pod 安全加固有个误解反正 Restricted 是最安全级别直接把所有命名空间打上pod-security.kubernetes.io/enforcerestricted标签不就行了我在那次事故里用惨痛经历证明了这个思路行不通。原因不是 Restricted 本身有问题而是存量集群的复杂程度远超你想象。1.1 存量集群里的特权来源是历史债务新集群还好说从第一天就按 Restricted 标准去约束大家写配置时就会注意。但存量集群不是这样。我盘点的时候发现特权来源大致有三类第一类是镜像本身以 root 启动。很多老业务的 Dockerfile 压根没写USER指令基础镜像默认就是 root容器起来后进程是 UID 0。这种 Pod 即使没开 privileged攻击面也很大后面我会细讲。第二类是** Helm Chart 的默认值**。比如某些社区 Chart 为了保证开箱即用默认在 values.yaml 里把securityContext.privileged写成 true或者给容器挂 hostPath 卷。你如果直接helm install而不去改参数安全基线直接拉低。第三类是历史 webhook 自动注入。我们集群里那个老 webhook 会在创建 Pod 时自动补privileged: true造成的结果是——你明明在 YAML 里没写特权实际运行出来的容器却带特权。这种隐式特权是最坑的因为靠人眼 review manifest 根本发现不了。所以渐进式迁移的第一个动作不是改配置而是先搞清楚特权到底是从哪一层进来的。镜像文件层、工作负载 YAML、还是 webhook 注入层每一层都要单独排查。不然你可能改了 manifests 半天发现集群策略一执行webhook 又把特权补回去了。1.2 平台组件和业务组件的诉求完全错位第二个翻车点在于集群里不是只有业务 Pod还有一堆平台组件它们对安全约束的诉求跟业务完全不一样。比如网络插件。我们用的 Cilium 需要创建和管理宿主机上的虚拟网络设备还要操作 iptables / BPF这类组件天然需要比较高的权限你让它的 Pod 跑在 Restricted 级别基本不可能。同样存储 CSI 插件比如 AWS EBS CSI driver要挂载宿主机设备、做文件系统操作也需要特权。ingress-nginx 虽然不需要 privileged但需要绑定宿主机的 80/443 端口而 Restricted 对端口、capability 又有限制直接套上去就报权限错误。我之前犯的错就是一刀切把 kube-system 命名空间也一并 enforce 了结果核心组件起不来整个集群差点不可用。平台组件和业务组件必须区分对待——前者按能正常工作所需的最低权限来单独授予后者才走严格基线。所以第一次做 Pod 安全加固正确姿势是先按命名空间/按 workload 类型分桶而不是整个集群一把梭。这也是我后面要重点讲的渐进式的核心思想先分而治之再逐步收紧。2. Privileged 模式到底特权在哪里从内核隔离说起既然要加固你得先知道 Privileged 容器为什么危险。很多人对privileged: true的理解停留在能访问宿主机的 root 权限这个说法太模糊了。我尽量用直白的话拆一下。2.1 特权容器在 Linux 内核层面的开关正常容器之所以相对安全靠的是 Linux 内核的三大隔离/限制机制Namespace让容器只能看到自己的进程、网络、文件系统挂载点看不到宿主机全局视图。Capabilities把 root 的权限拆成几十个独立小权限比如CAP_NET_ADMIN、CAP_SYS_ADMIN容器内即使以 root 跑默认也只有一小部分能力。Seccomp / AppArmor / SELinux限制容器内进程能发起的系统调用把高危 syscall 挡在门外。而privileged: true做的事情简单说就是把这三大机制全部关掉。容器内的 root 变成真正意义上的宿主机 root——可以看到宿主机全部设备/dev、可以直接挂载文件系统、可以加载内核模块、可以执行任意系统调用Capabilities 等于全量开放。这相当于在宿主机内核里跑了一个拥有全部权限的进程隔离层名存实亡。2.2 一条真实的攻击路径从 RCE 到宿主机沦陷讲道理不如讲案例。假设你有个 Java 应用跑在 privileged 容器里某个接口有反序列化漏洞被攻击者打穿了拿到容器内 shell。如果这是普通容器攻击者的下一步会很受限——不能读宿主机/etc/shadow、不能挂载磁盘、不能加载内核模块能折腾的范围有限。但如果这是 privileged 容器攻击者可以直接读取宿主机的/root/.ssh/authorized_keys给自己种一个 SSH 公钥用nsenter或者直接chroot切到宿主机文件系统改 crontab、装后门挂载宿主机根分区直接改/etc/passwd提权加载恶意内核模块彻底驻留宿主机。这就是为什么安全扫描器一看到 privileged 就红灯。privileged 的本质是把容器内被攻破这个事件的损失上限从一个容器放大到了整个宿主机乃至整个集群。所以不管别的加固做不做privileged 一定是要优先清掉的。2.3 非 privileged 的隐性特权同样危险还有一类 Pod没有开 privileged但配置上存在其他高风险项runAsUser: 0以 root 用户运行配合高版本内核漏洞或者配置不当的 capabilities仍然有逃逸风险。Capabilities 不限制容器继承了默认 capabilities 之外还被添加了SYS_ADMIN、NET_ADMIN这类高危权限。hostPID: true/hostIPC: true共享宿主机 PID 命名空间意味着容器内能看到宿主机全部进程这给信息收集和后续攻击提供了很大便利。hostPath 挂载把宿主机目录挂进容器一旦容器被攻破攻击者就能读写宿主机文件系统对应路径。我见过一个生产事故某 Pod 只加了hostPID: true想方便调试为了看宿主机进程结果被入侵后攻击者通过/proc/1/root直接摸到了宿主机文件系统。所以做安全加固时这类非 privileged 但高危的配置也要一并纳入清理范围。这也是从 Privileged 到 Restricted 这个渐进过程中你要逐步收口的东西——privileged 是最大的那个洞但把它堵上不等于万事大吉。3. 一张表看懂 Privileged、Baseline、Restricted 三个安全级别Kubernetes 官方把 Pod 安全标准Pod Security Standards分成了三个级别Privileged、Baseline、Restricted。理解了这三个级别的差异你就知道渐进式加固的每一站分别要达到什么目标。3.1 核心差异对照表我根据官方文档和实际测试整理了一张表直接对比三个级别在关键安全维度上的约束差异安全维度PrivilegedBaselineRestrictedprivileged 容器允许禁止禁止hostPID / hostIPC允许禁止禁止hostNetwork允许允许但 hostPorts 受限制允许但 hostPorts 受限制hostPath 卷允许白名单只读限制禁止Linux capabilities不限制只允许添加白名单内 cap必须 drop ALL只能加 NET_BIND_SERVICEallowPrivilegeEscalation不限制禁止设置为 true必须为 false以 root 运行允许不强制禁止必须 runAsNonRootseccomp不限制不强制必须 RuntimeDefault 或 Localhost卷类型全部允许限制高危类型白名单内的常规卷类型注意一个容易忽略的点Restricted 是 Baseline 的超集。也就是说通过了 Restricted 的 Pod一定也满足 Baseline 的要求。渐进式迁移时先让 workload 达到 Baseline再往 Restricted 努每一步的改动范围是可控的不会出现前面改的全白费的情况。3.2 三个级别的安全逻辑是什么很多人背不住表里的规则但其实每个规则背后都有明确的攻击面对应关系禁止 privileged 限制 capabilities是为了防止容器获得超出需要的 Linux 内核权限。privileged: true等于全量 caps而像SYS_ADMIN这种 cap 一旦被攻击者利用可以直接导致容器逃逸。禁止 host PID/IPC/Network是为了防止容器进程直接接触宿主机或其他容器的进程、网络通信数据。共享宿主机网络意味着容器可以监听宿主机上所有网络流量这在安全上是不可接受的。非 root 运行是为了防止容器内进程拿到 UID 0 后利用内核漏洞做特权升级。即使容器内 root 不等于宿主机 root但一旦有内核漏洞root 身份利用起来远比普通用户容易。强制 seccomp是为了限制容器进程可用的系统调用集合。像mount、unshare、ptrace这类高危 syscall在生产 workload 里大多数都用不到但攻击者需要它们来做逃逸或信息收集。所以你会发现Restricted 并不是为了刁难开发者而设置一堆条条框框它的每一条要求基本都对应一种真实存在的攻击路径。理解了这一点你在跟业务团队解释为什么要改这么多配置的时候会更有说服力。3.3 什么情况下可以停在 Baseline既然 Restricted 最好是不是所有 Pod 都必须冲到 Restricted我的实践结论是不必须按 workload 类型分层。纯计算型、无状态 API 服务这类 workload 不碰宿主机设备、不需要特殊权限直接上 Restricted改造成本最低。需要绑定低端口80/443、需要NET_RAW之类能力的中间件比如 nginx、部分 API 网关可以用 Restricted 白名单方式显式声明需要添加的 capability多数情况下也能过。涉及 hostPath 存储、需要操作宿主机设备、或者依赖老式特权能力的组件比如部分存储插件、监控采集器短期内可以停留在 Baseline用单独策略管理后续再评估是否能改造。渐进式的目标不是让所有 Pod 一夜之间全部变成 Restricted而是让每个 Pod 都收敛到它能达到的最高安全级别。核心业务、边缘业务、平台组件分别定目标这比一刀切更现实也更可持续。4. 渐进迁移第一步从 Privileged 降到 Baseline 的实操清单明确了三个级别的差异之后开始动手。第一步目标很简单把所有 privileged 容器先降下来让 workload 整体达到 Baseline。这一步虽然也有坑但相对温和适合作为安全整改的零号迭代。4.1 先盘点把集群里的 Privileged Pod 全部找出来没有盘点就没有整改。我不建议靠人肉去翻 manifests可以直接用 kubectl 把运行时状态捞出来。下面这个命令可以列出所有命名空间下的 privileged 容器kubectl get pods -A -o json | jq -r .items[] | . as $pod | .spec.containers[] | select(.securityContext.privileged true) | \($pod.metadata.namespace)/\($pod.metadata.name) - \(.name) 如果你没装 jq也可以直接用 go-templatekubectl get pods -A -o custom-columnsNS:.metadata.namespace,POD:.metadata.name,CONTAINER:.spec.containers[*].name,PRIV:.spec.containers[*].securityContext.privileged不过注意这种方式只能看到 Pod 在 etcd 里的声明状态。如果特权是 webhook 注入的kubectl get 不一定能反映最终运行状态。更可靠的做法是用kubectl get pod -o yaml看实际运行态的 status或者用专门的扫描工具。我用过 Starboard现在叫 Trivy Operator它会自动扫描集群里所有 workload 的安全配置并生成报告包括 privileged、capabilities、non-root 等维度比手敲命令全面得多。盘点的产出应该是一张清单每个命名空间下有哪些 workload 是 privileged、它们为什么需要特权、负责人是谁。这张清单是后续所有整改动作的依据。4.2 利用 PSA 的 warn 和 audit 模式做预演Pod Security AdmissionPSA是 Kubernetes 1.23 引入的原生 Pod 安全策略控制器1.25 之后 GA。它的好处是零侵入、内置、不需要额外部署组件非常适合做渐进式迁移的第一站。PSA 支持三个模式enforce不符合策略的 Pod 直接拒绝创建。audit不符合策略的 Pod 照样运行但在审计日志里记录事件。warn不符合策略的 Pod 照样运行但会给用户返回警告信息。我强烈建议你在真正 enforce 之前先给目标命名空间打上 warn 和 audit 模式标签跑一到两周kubectl label ns default \ pod-security.kubernetes.io/warnbaseline \ pod-security.kubernetes.io/auditbaseline \ --overwrite这时候业务团队创建/更新 Pod 时会在返回信息里看到类似would violate PodSecurity baseline:latest: privileged container is not allowed的提示但不会影响运行。审计日志里也会记录所有违反 Baseline 的 Pod 事件。跑一两周之后你就能摸清楚这个命名空间里的存量 workload 到底违反了哪些规则哪些是可以低风险整改的哪些需要特殊处理。预演结束后再切 enforcekubectl label ns default \ pod-security.kubernetes.io/enforcebaseline \ --overwrite切 enforce 之前一定先跟业务团队打声招呼让他们知道新 Pod 如果违反 Baseline 会被拒绝。但好消息是你已经在 warn/audit 阶段把问题暴露得差不多了enforce 阶段理论上只会有零星问题。4.3 处理 Baseline 阶段最常碰到的三类问题根据我实操的经验从 Privileged 降 Baseline出现最多的整改项是下面三个一是 hostPath 卷怎么换。很多老应用把宿主机目录挂进容器有的是为了写日志有的是为了读配置。Baseline 虽然允许白名单内的 hostPath 但要求只读且路径受限很多场景根本没法用它。这种我一般建议优先改成 PVC 或者 emptyDir。比如日志场景最标准的做法是应用把日志写到 stdout由容器运行时去收集或者写到 emptyDir 再由 sidecar 或 DaemonSet 采集。二是 capabilities 报错。某些应用明明不是 privileged却隐式依赖了一些被 Baseline 限制的 capability。比如想要绑定 80 端口就需要NET_BIND_SERVICE这个在 Baseline 白名单内问题不大。但有些中间件需要SYS_PTRACE来做 JVM 诊断Baseline 不允许这种就得和业务方确认是不是真的需要。三是 webhook 注入特权的问题。这个前面提过如果你命名的 webhook 还在给所有 Pod 注入 privileged那么不管你怎么标 PSA创建的 Pod 仍然会违反策略。解决方式是先调整 webhook 配置停止默认注入特权或者将其改成按 annotation 白名单逐个 Pod 注入。你要记住这一步的目标不是完美而是消灭最危险的 privileged 配置。Baseline 会放过一些非 root 要求、seccomp 要求这些留到下一步处理。5. 真正难啃的骨头从 Baseline 升 Restricted 的关键改动很多团队做到 Baseline 就停下了觉得反正最危险的特权已经除掉了。这话也对他也不对——privileged 确实是最危险的洞但 Baseline 没有强制 non-root 和 seccomp攻击面仍然很大。真正到 Restricted 这一级才算是把容器安全基线拉到一个比较理想的水平。这一节讲的几个改动是你在升级到 Restricted 时几乎一定会遇到的。5.1 让镜像彻底非 root 化Restricted 强制要求容器以非 root 用户运行即runAsNonRoot: true并且会检查容器进程 UID 不是 0。这看起来是一条很简单的规则但实际上很多镜像根本做不到常见卡点有三个镜像默认 root 启动。解决办法是改 Dockerfile在构建阶段创建一个低权限用户FROM eclipse-temurin:17-jre # 创建普通用户 RUN useradd --create-home --uid 10001 appuser WORKDIR /app COPY --frombuild /build/app.jar . USER 10001 ENTRYPOINT [java, -jar, app.jar]注意一点USER 10001只是改了默认启动用户如果你的应用代码里有逻辑会在运行时切换到 root比如一些提权初始化那照样过不了runAsNonRoot检查。所以改完 Dockerfile 之后最好在容器里实际验证一下id命令输出。应用需要在运行时写文件。这是最普遍的坑。以前以 root 跑的时候应用可以随便写/var/log、/tmp、/opt下面的任意目录换成普通用户之后这些目录往往没有写权限应用直接报 Permission denied。对于临时文件最简单的办法是给 Pod 挂一个 emptyDirvolumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {}对于需要持久化写入的目录比如日志目录要确保 PVC 挂载后目录属主是目标 UID。可以配fsGroupsecurityContext: runAsNonRoot: true fsGroup: 10001fsGroup会让 kubelet 在挂载卷时把卷的属主改成指定组应用用户UID 10001在组内有写权限就能正常写入了。这个配置在 Restricted 里是允许的也是官方文档里推荐的做法。Java 应用的特殊情况。如果你在用 Java 17 版本在 Restricted 环境里跑还会碰到一个比较隐蔽的兼容性问题当你调用System.setSecurityManager这类受限方法时JVM 会打印warning: a restricted method in java.lang.system has been called某些老版本框架在启动阶段会动态设置系统属性或者 SecurityManager可能触发这个警告甚至直接抛异常。这不是 K8s 层面能解决的需要升级到兼容新 JDK 的框架版本或者调整 JVM 参数。我在迁移一个 Spring Boot 老应用时就碰到过最后是升级了基础镜像里的 JDK 小版本才消掉。5.2 Seccomp 和 Capabilities显式声明永远比靠默认可靠Restricted 要求容器显式设置seccompProfile: RuntimeDefault并drop: [ALL]只允许按需添加NET_BIND_SERVICE。也就是说你不能再依赖不写就等于安全这种心理必须白纸黑字写在 manifest 里。一个合规的 Restricted deployment 的 securityContext 长这样securityContext: runAsNonRoot: true runAsUser: 10001 seccompProfile: type: RuntimeDefault capabilities: drop: [ALL] add: [NET_BIND_SERVICE] allowPrivilegeEscalation: false这里面有几个细节需要注意第一NET_BIND_SERVICE只在必须绑定 1024 以下端口时才需要加。比如 nginx 容器要监听 80 端口普通非 root 用户没有 CAP_NET_BIND_SERVICE 就绑不上。如果你的服务监听的是 8080 之类的端口完全不需要加这个 cap保持 drop 完的状态最安全。第二allowPrivilegeEscalation: false会禁止进程通过setuid等方式提升权限。这对大多数应用没影响但如果你用的 JVM 尝试通过 setuid 来调整线程优先级或者某些旧版运行时依赖这个能力做优化可能会报错。遇到这种问题优先想办法换运行时版本而不是放开这个开关。第三seccomp RuntimeDefault 默认会禁用一部分系统调用包括unshare、mount等。绝大多数业务不会调用这些 syscall但有些特殊应用比如需要在容器内创建 namespace 的 sandbox 类应用会在启动时直接挂掉。runc 报错信息通常是operation not permitted或者seccomp: filter failed。这种应用基本可以判定为不适合跑 Restricted按第 3 节的标准给它单独分级。5.3 存储和网络侧对 Restricted 的适配Restricted 限制卷类型hostPath 直接禁止。这导致很多以前用 hostPath 的组件必须做改造。我的建议是日志/缓存类用 emptyDir 或者 CSI 驱动支持的卷类型。数据库类直接用 PVC不要试图挂宿主机目录。需要读取宿主机内核信息或设备文件的应用这类基本没法上 Restricted需要特殊策略放行。网络侧比较典型的坑是NET_RAW被 drop 之后容器内 ping 命令不可用。因为 ping 需要发送原始 IP 包在 dropNET_RAW之后ping会报socket: Operation not permitted。很多开发者在容器里调试连通性时习惯先 ping突然 ping 不通就以为是网络有问题折腾半天才发现是策略的限制。遇到这种情况要么改用nc -zv或者curl测端口连通性要么给容器临时加NET_RAW不建议。从安全角度讲业务容器里本来就不应该有需要 ping 的场景所以这属于习惯需要改而不是配置需要改的典型例子。还有一点如果 Pod 里跑的是自定义的 CNI 插件 init 容器或者网络观察 agent它们可能需要NET_ADMIN。这类容器在 Restricted 策略下会直接被拒你需要在部署清单里单独给它们走例外通道。还是那句话允许例外的前提是你明确知道它的用途而不是因为它报错就放开。6. 资源和多容器的隐藏坑Restricted 之外的加固盲区安全策略管得住权限但管不住资源。在实际迁移过程中我发现还有两类问题经常和 Pod 安全整改纠在一起如果你没意识到很容易在线上出一堆莫名其妙的事故。6.1 2c4g 这种小规格 Pod并发上限到底在哪热搜词里经常有人问 2c4g 的 Pod 能支持多少并发。这个问题没有标准答案取决于你的应用类型、RT、线程池配置和下游依赖。但有一个规律是确定的2C4G 这种小规格 Pod瓶颈几乎永远是内存和线程池不是安全策略。我用一个 Java 应用举例4G 内存的容器JVM 堆通常会设置-Xmx2g假设请求 RT 是 200ms每个线程每秒能处理 5 个请求200 个线程大约能支撑 1000 QPS。如果你的连接池、数据库连接池、下游超时配置没调好200 个线程可能被卡在 I/O 上实际吞吐直接砍半。安全策略seccomp、capabilities对这类应用的性能影响通常小于 2%基本可以忽略。但有一个场景要注意限制 CPU 之后Java 应用对可用核数的感知可能不准确。K8s 的 CPU limits 使用 CFS 配额实现JVM 在容器里看到的 CPU 核数如果没配置-XX:ActiveProcessorCount可能按照宿主机核数来设置 GC 线程池导致频繁 GC 反而拖慢吞吐。我在 2C4G 的 Pod 上一般会显式设置-XX:ActiveProcessorCount2 -XX:MaxRAMPercentage50所以当你评估 2C4G Pod 的并发支撑能力时先查线程池和 JVM 参数再查业务 RT最后才轮到安全策略。别把性能问题甩锅给安全加固。6.2 多容器 Pod 的木桶效应一个容器失败会影响整个 Pod热搜词里另一个高频问题一个 Pod 中有多个容器如果其中一个没成功启动会不会影响其他容器答案是会而且影响方式比较隐蔽。Kubernetes 中 Pod 是调度和生命周期管理的最小单位Pod 内所有容器共享同一个网络沙箱sandbox、同一个 IP、同一组 volume。如果一个容器 CrashLoopBackOffPod 整体会处于NotReady状态Service 不会把流量打进来即使其他容器还在运行它的日志也照常输出但对用户来说这个 Pod 就是不可用的。更关键的是如果 Pod 配置了 readinessProbe 且其中一个容器的探针失败Pod 会被从 Service Endpoints 摘除等于所有容器一起下线。这个机制对安全加固有一个重要启示PSA 策略是对 Pod 整体生效的一个 Pod 里只要有一个容器违反策略整个 Pod 都会被拒绝。所以当你迁移多容器 Pod 时要以权限要求最高的那个容器为基准来判断整个 Pod 能不能过 Restricted。我在实际迁移中见过太多次这种场景主业务容器完全符合 Restricted但一个 sidecar 因为要抓包写了NET_ADMIN或者 init 容器要做文件系统操作没关 privileged导致整个 Pod 卡在策略检查上。处理办法是把这个特殊容器的权限要求单独评估——能降权限就降不能降就给这个 Pod 专门走例外。这里补充一句别为了一个 sidecar 的权限问题把整个 Pod 留在特权级别很多团队就是这么摆烂的其实 Split 成两个 Deployment 反而更合理——特权采集任务用 DaemonSet 单独跑业务 Pod 保持 Restricted。6.3 资源碎片化和调度器日志的含义做资源评估时你可能会在 kube-scheduler 日志里看到no preemption victims found for incoming pod这种输出。这行日志的意思是有一个高优先级 Pod 因为资源不足无法调度调度器尝试抢占preempt低优先级 Pod但找不到合适的受害者——要么低优先级 Pod 都不能被杀要么杀了也腾不出足够的资源。这个日志经常被误读为调度器出 bug 了其实它只是告诉你集群资源碎片化严重。我建议的排查方向有三步查各节点实际可分配资源确认当前请求总量和节点容量的关系查高优先级 Pod 的 requests 是否设置得异常大比如一个只需要 500m CPU 的 Pod 却写了 4C是否需要给关键业务配置 PriorityClass保证在资源紧张时能抢占边缘业务。安全加固和资源约束其实是互相配合的你把 Pod 的权限收得越紧意味着它越正统也就越应该在资源紧张时得到保障秩序。合理配置 requests、limits、PriorityClass可以让集群在高负载下依然保持稳定。这虽然不是安全策略本身的事但属于从 Privileged 到 Restricted这一整改过程中顺带要处理的基建问题。7. 渐进式落地的策略引擎选型PSA、Kyverno、OPA 怎么选做到这一步你应该已经积累了大量的策略例外——有些 workload 停在了 Baseline有些容器需要添加额外 capabilities有些命名空间需要豁免。这时候靠手动维护一堆 namespace 标签来管理策略会变得越来越痛苦。下面聊聊三种策略控制方案的选型。7.1 Pod Security Admission最轻量的原生方案如果你的集群版本在 1.25 以上PSA 是成本最低的起步方案。它不需要安装任何组件直接通过 namespace 标签启用kubectl label ns production \ pod-security.kubernetes.io/enforcerestricted \ pod-security.kubernetes.io/enforce-versionv1.30 \ pod-security.kubernetes.io/warnrestricted \ pod-security.kubernetes.io/auditrestrictedPSA 的优点是非常直观缺点也很明显粒度只到 namespace无法针对单个 Pod 做例外只有三个固定级别不能自定义策略规则没有审计报告界面只能翻日志。所以 PSA 适合做第一道防线——把整个集群的默认水位抬起来拦住明显不合规的 Pod。但你要想给个别 Pod 开白名单添加某个 capability、放行某个命名空间PSA 是做不到的。7.2 Kyverno策略即代码的灵活替代我在第二阶段会用 Kyverno 替代 PSA。Kyverno 最大的优势是支持策略异常PolicyException你可以针对特定 workload 放行某些规则而不用整个命名空间降级。举个例子你的业务 Pod 基本满足 Restricted但有一部分 legacy 服务必须监听 80 端口且无法立刻改造。用 Kyverno 你可以写一个策略默认 enforcing Restricted但匹配到特定 label 的 Pod 时允许添加NET_BIND_SERVICE这个 capability。Kyverno 还支持用 YAML 定义策略比如强制要求所有容器配置 seccompapiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: require-seccomp spec: validationFailureAction: Audit rules: - name: check-seccomp match: any: - resources: kinds: - Pod validate: message: Seccomp profile must be RuntimeDefault. pattern: spec: securityContext: seccompProfile: type: RuntimeDefault注意我把validationFailureAction写的是Audit这是渐进式迁移的关键技巧——先用 Audit 模式收集所有不合规资源确认影响面之后再改成Enforce。Kyverno 还有 Report 功能能生成一份不合规资源的清单比日志直观得多。7.3 OPA Gatekeeper应对复杂合规场景的重武器如果在大型团队、多集群场景下做合规治理OPA Gatekeeper 更合适。它基于 Rego 语言定义策略表达力最强可以实现非常复杂的逻辑。比如所有包含用户数据的命名空间必须满足 Restricted且排除列表里的命名空间除外这类细粒度规则用 Rego 写很自然。但代价也很明显Rego 学习曲线陡峭非平台组同学基本写不了性能在大规模集群下需要额外调优维护成本明显高于 Kyverno 和 PSA。我的建议是只有当你明确遇到 PSA/Kyverno 表达不了的场景时再考虑引入 OPA不要为了炫技而上它。7.4 我推荐的渐进式落地路径最后总结一下我目前在集群里跑通的路径按阶段划分阶段 0现状盘点。用 Trivy Operator 或手动命令找出所有 privileged、高危 capabilities、hostPath 等配置建立特权画像台账。阶段 1PSA 预演。给全部业务命名空间打上auditbaseline、warnbaseline标签跑 1-2 周观察违反情况并逐步整改。阶段 2Baseline 强制。将业务命名空间切到enforcebaseline平台组件单独管理。这一步清掉绝大多数高危特权。阶段 3Restricted 逐点突破。对无状态核心业务命名空间 enforce Restricted维护一份例外清单暂时停在 Baseline 的 workload每周复盘一次例外是否可以消除。阶段 4策略引擎升级。当例外数量多到 PS A 标签管理不过来时引入 Kyverno把例外落成 PolicyException有条件再评估 OPA。这套路径最大的特点就是每一步都可以回退、可以审计、可以量化。你不需要在某一天突然宣布全部上 Restricted而是每周都能看到一些 Pod 从例外清单里被划掉最终收敛到该 Restricted 的都 Restricted不该硬上的也有明确理由。我在实际执行中最大的体会是无论用什么策略引擎最后拼的都是你对自己 workload 的理解程度。你越清楚每个容器依赖哪些真实权限就越敢放心地收紧策略反过来如果你连自己跑的东西需要什么权限都不知道那任何安全策略都只是纸面合规。所以别急着追求全集群 Restricted先花时间把存量 workload 的特权画像梳理清楚后面的每一步都会顺很多。