Pod 安全:PSS 与 PSA(替代 PSP)
本教程共 65 篇 · 第 46 篇 · 更新于 2026-08-14 · 约 20 分钟阅读
本节目标:学会用命名空间标签给 Pod 上安全约束,搞清 privileged / baseline / restricted 三个级别的差别和 enforce / audit / warn 三种模式的用法。
先把一件事说清楚,免得你在网上翻到老文章走弯路:
WarningPodSecurityPolicy(PSP)已在 Kubernetes v1.25 彻底移除。 你在 v1.36 集群上写 PSP 的 YAML,API 服务器根本不认识这个类型。网上大量 2021 年前的教程还在教 PSP,直接跳过。替代方案是本章讲的 Pod 安全标准(PSS)+ Pod 安全准入(PSA)。
PSP 为什么被弃了?简单说:它太难用。授权模型走 RBAC 但语义扭曲,多个 PSP 同时匹配时选哪个规则玄学,还会静默修改 Pod 定义。用对了很难,用错了不自知。社区试过修,最后决定重来。
46-1 PSS 和 PSA 是两个东西
新手最容易混淆的就是这两个缩写,先分清:
- PSS(Pod Security Standards,Pod 安全标准)——一套标准定义。它规定了三个隔离级别各自允许什么、禁止什么。它只是文档和规范,本身不做任何事。
- PSA(Pod Security Admission,Pod 安全准入)——Kubernetes 内置的准入控制器,负责把 PSS 真正执行起来。v1.25 起 stable。
打个比方:PSS 是国标里写的”三级防火等级各要满足什么条件”,PSA 是门口那个真会拦人的安检员。
PSA 是内置的,不用安装、不用装 Webhook,默认就在。你只要给命名空间打标签告诉它怎么管。
46-2 三个级别:privileged / baseline / restricted
PSS 定义三个策略级别,叠加式的——从宽松到严格:
| 级别 | 说明 |
|---|---|
privileged | 完全不受限。允许已知的特权提升。 |
baseline | 限制最弱但禁止已知特权提升。默认的、最简 Pod 配置能直接通过。 |
restricted | 强限制,遵循当前 Pod 加固最佳实践。 |
privileged:等于没管
有意开放、完全无限制。这类策略针对由受信任用户管理的系统级、基础设施级负载。
用了它,Pod 能绕过典型的容器隔离机制——比如直接访问节点的主机网络。
kube-system 命名空间通常就是这个级别,因为 CNI 插件、kube-proxy 这些东西确实需要特权。
baseline:挡住已知提权
面向应用运维人员和非关键应用开发者。禁止的主要项:
- 主机命名空间——
spec.hostNetwork、spec.hostPID、spec.hostIPC必须为 false 或不设。 - 特权容器——
securityContext.privileged不能为 true。 - 额外权能(Capabilities)——只能添加一个很小的白名单内的权能,其他都不行。
- hostPath 卷——
spec.volumes[*].hostPath被禁。 - 主机端口——通常限制或禁止。
- AppArmor / SELinux——不能覆盖或禁用默认配置,SELinux 只允许受限的 type,禁止自定义 user 和 role。
/proc挂载类型——必须是默认值。- Seccomp——不能显式设为
Unconfined。 - Sysctls——只允许一个”安全”子集,比如
net.ipv4.ip_unprivileged_port_start。
关键点:一个啥都没配的普通 Pod 可以直接通过 baseline。它只挡明显危险的操作。
restricted:真正的加固
在 baseline 之上继续收紧:
- 卷类型白名单——只允许
configMap、csi、downwardAPI、emptyDir、ephemeral、persistentVolumeClaim、projected、secret。别的一律不行。 - 禁止权限提升——
allowPrivilegeEscalation必须显式设为false。 - 必须非 root 运行——
runAsNonRoot必须为true,且runAsUser不能是 0。 - Seccomp 必须配——
seccompProfile.type要设为RuntimeDefault或Localhost。 - 权能必须丢弃 ALL——
capabilities.drop必须包含ALL,之后只允许加回NET_BIND_SERVICE这一个(PSS 策略本身仅适用于 Linux Pod)。
Note注意 restricted 有几项是必须显式声明的,不是”默认值刚好符合就行”。比如
allowPrivilegeEscalation不写会被拒,得明确写false。这个设计是有意的——强迫你确认过,而不是碰巧合规。
一个能通过 restricted 的最小 Pod 长这样:
apiVersion: v1
kind: Pod
metadata:
name: restricted-ok
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginxinc/nginx-unprivileged:1.27
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
注意镜像换成了 nginx-unprivileged。官方 nginx 镜像默认以 root 启动,配 runAsNonRoot: true 会直接起不来。这是从 baseline 升 restricted 时最常见的拦路虎——不是配置写错了,是镜像本身没准备好。
Tip官方文档提到过一个常见疑问:为什么 privileged 和 baseline 之间没有中间级别?因为那个区间的需求太零散,做成通用标准反而没人用得上。真有特殊需求,走豁免(Exemption)或者自己写准入 Webhook。
46-3 三种模式:enforce / audit / warn
级别定义了”什么算违规”,模式定义了”违规了怎么办”:
| 模式 | 行为 |
|---|---|
enforce | 违规直接拒绝创建 Pod |
audit | 违规照样创建,但在审计日志里加一条审计注解 |
warn | 违规照样创建,但给用户返回一条警告信息 |
三种模式可以同时配,而且各自可以用不同级别。这是 PSA 设计里最实用的一点。
46-4 用命名空间标签配起来
标签格式就两个:
# 模式的级别标签
# MODE 是 enforce / audit / warn 之一
# LEVEL 是 privileged / baseline / restricted 之一
pod-security.kubernetes.io/<MODE>: <LEVEL>
# 可选:把策略锁定到某个 Kubernetes 小版本
# VERSION 是合法小版本号或 latest
pod-security.kubernetes.io/<MODE>-version: <VERSION>
实际配置:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# 强制执行 baseline,违规就拒
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.36
# 同时按 restricted 标准发警告和审计,但不拦
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
用命令打标签更快:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted
# 改已有标签加 --overwrite
kubectl label --overwrite namespace production \
pod-security.kubernetes.io/enforce=restricted
看当前配置:
kubectl get namespace production -o yaml | grep pod-security
推荐的推进路径
上面那个”enforce=baseline + warn=restricted”的组合就是标准渐进式做法:
- 先只开
warn和audit,级别设成你的目标(比如 restricted)。什么都不拦,先收集数据。 - 看警告和审计日志,统计有多少工作负载违规、违规在哪。
- 逐个修复应用,改
securityContext、换非 root 镜像。 - 违规清零后,把
enforce提到目标级别。
Tip直接上
enforce: restricted是最常见的翻车方式。整个命名空间的 Deployment 全都建不出 Pod,而且报错信息藏在 ReplicaSet 事件里,第一次遇到能查半天。先 warn 再 enforce,多花两天,少熬一个通宵。
version 标签是干什么的
enforce-version 把策略锁到某个 Kubernetes 小版本。
为什么需要?因为 PSS 标准本身会随版本演进——新版本可能给某个级别加新的限制项。如果你不锁版本(等价于 latest),集群升级后可能突然有 Pod 建不出来了。
生产环境建议显式写版本号。想跟进新标准时再手动改。
46-5 一个坑:Deployment 建得出来,Pod 建不出来
这一点必须单独讲,因为它是 PSA 最反直觉的行为。
Pod 通常不是你直接建的,是通过 Deployment、Job 这类工作负载对象间接创建的——控制器读 Pod 模板,然后造 Pod。
PSA 的处理方式是:
audit和warn会应用到工作负载资源本身。你kubectl apply一个违规的 Deployment,会立刻看到警告。enforce不应用到工作负载资源,只应用到最终生成的 Pod 对象。
所以在 enforce 命名空间里 apply 一个违规的 Deployment:
kubectl apply -f bad-deployment.yaml
# deployment.apps/bad-deployment created ← 成功了!
kubectl get pods
# 没有任何 Pod
Deployment 创建成功,Pod 一个都没有。真正的错误在 ReplicaSet 里:
kubectl describe rs -l app=bad-deployment
# Events:
# Warning FailedCreate ... Error creating: pods "bad-deployment-xxx" is forbidden:
# violates PodSecurity "restricted:v1.36": allowPrivilegeEscalation != false ...
排查口诀:Deployment 有了但 Pod 没有,先去 describe rs 看事件。
好消息是 warn 模式会在 apply 工作负载时就提示你,所以同时开 warn 很有价值——它把问题提前到了你敲命令的那一刻。
46-6 豁免:确实需要特权怎么办
有些负载真的需要特权——日志采集要读 hostPath,网络插件要 hostNetwork。
最简单的办法是把它们放到独立命名空间,那个命名空间设 privileged。 这是首选方案,清晰、可审计。
apiVersion: v1
kind: Namespace
metadata:
name: infra-agents
labels:
pod-security.kubernetes.io/enforce: privileged
另一条路是在准入控制器配置里静态声明豁免。豁免必须显式枚举,满足条件的请求会被 PSA 完全忽略(enforce、audit、warn 全部跳过)。三个维度:
- Usernames——来自被豁免用户名(含伪装身份)的请求。
- RuntimeClassNames——指定了被豁免运行时类的 Pod 和工作负载资源。
- Namespaces——位于被豁免命名空间中的 Pod 和工作负载资源。
Warning豁免用户名基本没用,而且危险。因为大多数 Pod 是控制器建的,豁免某个最终用户只在他直接建 Pod 时生效,建 Deployment 时不生效。更要命的是:绝对不要豁免控制器的服务账号,比如
system:serviceaccount:kube-system:replicaset-controller。豁免了它,等于豁免了所有能创建对应工作负载资源的用户——整个 PSA 相当于形同虚设。
另外,有些字段的更新操作天然豁免策略检查。如果一个 Pod 更新请求只改这些字段,即使 Pod 违反当前策略也不会被拒绝。大部分元数据更新属于这一类(seccomp 和 AppArmor 相关的那几个已废弃注解除外)。这是为了避免已存在的 Pod 因为策略变严而没法做无关的日常运维操作。
46-7 从 PSP 迁移过来的对照思路
如果你手上有老集群的 PSP 要迁移,思路是这样的:
官方提供了 PSP 参数到 PSS 的映射表。对每个 PSP 参数,看它的取值落在 baseline 还是 restricted 的允许范围里;超出这两个级别允许范围的,就归到 privileged。表里”无意见(No opinion)“表示所有 PSS 级别都允许该值。
实践上,大多数团队的 PSP 最终能归到这几类:
- 明显宽松的 PSP → 对应命名空间设
privileged。 - 常规约束的 PSP(禁特权、禁 hostPath)→ 设
baseline。 - 严格加固的 PSP(强制非 root、drop ALL)→ 设
restricted。
有些 PSP 能力 PSA 确实覆盖不了。比如 PSP 会修改 Pod(补默认值),PSA 只做校验不做修改。真需要修改行为,得上变更型准入 Webhook(第 57 章)。
46-8 排查违规
看具体哪一项不合规,最直接的办法是用 --dry-run=server 试一下:
# 服务端演练,会走完整的准入控制流程
kubectl apply -f my-pod.yaml --dry-run=server
或者在命名空间上临时开一个更严的 warn,看它警告什么:
kubectl label --overwrite namespace dev \
pod-security.kubernetes.io/warn=restricted
kubectl apply -f my-pod.yaml
# Warning: would violate PodSecurity "restricted:latest": ...
违规信息读起来是这个格式:
violates PodSecurity "restricted:v1.36":
allowPrivilegeEscalation != false (container "app" must set
securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "app" must set
securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true, seccompProfile
它会把所有违规项一次列全,不是报一个改一个。照着改就行。
46-9 一份实用的起手配置
给全集群命名空间的建议基线:
# 业务命名空间:强制 baseline,警告 restricted
apiVersion: v1
kind: Namespace
metadata:
name: app-prod
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.36
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
---
# 基础设施命名空间:放开
apiVersion: v1
kind: Namespace
metadata:
name: infra-agents
labels:
pod-security.kubernetes.io/enforce: privileged
Note
default命名空间默认没有任何 PSA 标签,等于privileged。新集群第一件事就该给它打上标签,或者干脆约定不在default里跑业务。
小结
- PSP 已在 v1.25 移除,别学别用。现在是 PSS(标准)+ PSA(内置准入控制器)。
- 三个级别:
privileged(不管)、baseline(挡已知提权,默认 Pod 能过)、restricted(加固最佳实践,需显式声明多个字段)。 - 三种模式:
enforce(拒绝)、audit(记审计)、warn(发警告),可同时配且各用不同级别。 - 配置方式就是给命名空间打
pod-security.kubernetes.io/<MODE>: <LEVEL>标签。 enforce不作用于 Deployment,只作用于生成的 Pod——所以要describe rs看错误。- 实施时按”先 warn/audit,再 enforce”推进;生产环境锁
-version标签。 - 需要特权的负载放独立的
privileged命名空间,别去豁免控制器服务账号。
下一章把安全上下文和网络策略串起来,做一次 Pod 加固要点的汇总。