Kubernetes 基础体系 · 第 46/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes Pod Security:Standards、Admission、SecurityContext 和基线

Kubernetes 中的 Pod 安全不是单个字段或单个插件完成的。一个 Pod 能否创建,通常由以下几层共同决定:

  1. 认证与授权决定“谁可以请求什么”;
  2. Admission 决定“请求是否满足集群策略”,并可能修改对象;
  3. Pod Security Standards(PSS) 定义一组可移植的 Pod 安全级别;
  4. Pod Security Admission(PSA) 将这些级别应用到命名空间;
  5. SecurityContext 将安全要求表达为 Pod 或容器的运行时配置;
  6. 容器运行时、Linux 内核和节点安全机制最终执行这些限制。

因此,“Pod 使用了 runAsNonRoot: true”并不等于“Pod 已经符合安全基线”。它只表达了一个运行时约束,不能替代 Admission 策略,也不能覆盖网络、身份、节点和供应链风险。


一、先区分四个容易混淆的概念

1. SecurityContext 是运行配置

securityContext 是 Pod API 的一部分,用来描述容器进程应具备或不具备的安全属性,例如:

  • 是否以非 root 用户运行;
  • 使用哪个 UID、GID;
  • 是否允许获得特权;
  • Linux capabilities;
  • seccomp、AppArmor、SELinux 配置;
  • 根文件系统是否只读;
  • 是否使用特定的补充组;
  • Pod 是否设置 fsGroup

它回答的是:

这个容器或 Pod 启动后,应以什么安全身份和内核约束运行?

2. Pod Security Standards 是安全规则集合

PSS 不是一个字段,也不是一个运行时沙箱。它定义了 Kubernetes 官方认可的三种策略级别:

  • privileged:不施加限制;
  • baseline:禁止已知的高风险配置;
  • restricted:在 baseline 之上,要求更严格的安全默认值。

它回答的是:

一个 Pod 的规格,是否落在某个安全策略允许的范围内?

3. Pod Security Admission 是准入控制器

PSA 是 Kubernetes 内置的 Admission Controller。它读取命名空间上的标签,对 Pod 进行检查:

  • enforce:不符合策略时拒绝请求;
  • audit:允许请求,但写入审计注解;
  • warn:允许请求,但向客户端返回警告。

它回答的是:

这个 Pod 是否可以进入 API Server 的持久化流程?

4. 基线不是“所有 Pod 都必须非 root”

baseline 是一个具体的 PSS 级别,不是泛指“最低安全要求”。它主要禁止特权、主机命名空间、危险 capabilities、任意 hostPath 等高风险行为,但通常不会要求所有应用都满足 restricted 的完整约束。

例如,一个 Pod 以 UID 0 运行,可能不符合 restricted,但不一定仅因为这一点就违反 baseline。反过来,一个非 root Pod 如果使用 hostNetwork: true,仍可能违反 baseline。


二、请求进入集群后,安全检查发生在哪里

一个创建 Pod 的请求大致经过以下路径:

flowchart LR
    C[客户端] --> A[认证 Authentication]
    A --> Z[授权 Authorization]
    Z --> M[Mutating Admission]
    M --> P[Pod Security Admission]
    P --> V[其他 Validating Admission / Webhook]
    V --> S[API Server 持久化]
    S --> K[调度器与 Kubelet]
    K --> R[容器运行时与 Linux 内核]

这张图表示的是逻辑阶段,而不是所有插件的精确固定顺序。具体 Admission 顺序由 API Server 配置和插件类型决定,但安全上必须区分以下事实:

  • 认证只确定请求者身份,不判断 Pod 是否安全;
  • 授权判断请求者是否有权限在某命名空间创建 Pod;
  • Admission处理对象内容,可以拒绝或修改;
  • PSA主要是验证 Pod 是否符合 PSS,不是 Linux 沙箱;
  • 容器运行时和内核才真正执行 seccomp、capabilities、UID 等约束。

例如:

用户 alice 有权限在 team-a 创建 Pod

只说明 Alice 可以发起请求。它不说明:

Pod 可以使用 hostNetwork
Pod 可以挂载 hostPath
Pod 可以以 root 运行
Pod 可以添加 SYS_ADMIN capability

认证、授权与 Admission 是三个不同问题。

Admission 的拒绝点

设:

  • P P 是提交的 Pod;
  • L L 是命名空间上的策略标签;
  • E(P,L) E(P, L) 是策略评估结果;
  • A(P) A(P) 是 Admission 是否接受该请求。

对于 enforce 模式,可以简化为:

A(P)={allow,E(P,L)=passdeny,E(P,L)=failA(P) = \begin{cases} \text{allow}, & E(P,L)=\text{pass}\\ \text{deny}, & E(P,L)=\text{fail} \end{cases}

auditwarn 不改变是否接受:

A(P)=allowA(P)=\text{allow}

但它们分别把结果写入审计事件或返回客户端警告。

因此,命名空间同时使用三个模式时,可能出现:

enforce=baseline
audit=restricted
warn=restricted

此时一个“不符合 restricted、但符合 baseline”的 Pod:

  • 可以创建,因为 enforce 只要求 baseline;
  • 会产生 audit 信息;
  • 客户端会收到 warning。

这是一种常见的迁移方式:先观察 restricted 违规,再逐步切换 enforce。


三、Pod Security Standards 的三个级别

1. Privileged

privileged 不限制 Pod 的安全上下文。它适合确实需要节点级能力的系统组件,例如某些节点代理、调试工具或基础设施 DaemonSet。

privileged 不是“更高权限的安全模式”,而是:

不通过 PSS 对 Pod 的这些安全属性施加限制。

它不等于 Kubernetes RBAC 中的 cluster-admin,也不自动授予 API Server 权限。一个 privileged 容器可能拥有大量 Linux 权限,却仍没有访问 Kubernetes API 的 ServiceAccount 权限。

2. Baseline

baseline 禁止常见的高风险配置,典型范围包括:

  • 使用特权容器;
  • 使用主机网络、主机 PID 或主机 IPC;
  • 添加危险 Linux capabilities;
  • 使用某些不安全的 volume 类型;
  • 使用 hostPath;
  • 使用不受允许范围约束的主机端口;
  • 使用不安全的 procMount;
  • 在 Linux Pod 中使用不允许的 seccomp 配置;
  • 某些主机用户命名空间或 SELinux 相关高风险配置。

Baseline 的核心思想是:

阻止容易导致容器直接扩大到节点权限的配置,但不强迫所有应用立即采用最严格的运行时约束。

3. Restricted

restricted 在 baseline 之上,进一步要求 Pod 使用更安全的默认值。典型要求包括:

  • 明确使用非 root;
  • 禁止 runAsUser: 0
  • 必须设置 allowPrivilegeEscalation: false
  • capabilities 必须删除 ALL,并且只能添加极少数允许项;
  • seccomp 必须使用 RuntimeDefault 或允许的本地配置;
  • 某些版本的 restricted 还会检查 Linux Pod 的安全上下文完整性;
  • 对 Windows Pod 和 Linux Pod 的规则存在差异。

这里必须注意版本问题:PSS 的具体字段检查会随 Kubernetes 版本演进。restricted 在不同 Kubernetes minor 版本中可能新增要求,因此生产环境不应只写“restricted”,还应考虑固定版本。


四、策略版本与命名空间标签

PSA 通过命名空间标签选择策略。例如:

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.34
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.34
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.34

执行:

kubectl apply -f namespace.yaml
kubectl get ns team-a --show-labels

预期可以看到这些标签。

标签含义是:

  • pod-security.kubernetes.io/<mode>:策略级别;
  • pod-security.kubernetes.io/<mode>-version:策略版本;
  • mode 可以是 enforceauditwarn
  • 版本通常写成 v1.xx,而不是 Kubernetes 发行版完整版本号。

为什么要固定版本

假设今天 restricted 允许某个配置,升级 Kubernetes 后该配置被纳入违规检查。如果命名空间使用:

pod-security.kubernetes.io/enforce: restricted

其行为可能随集群支持的默认策略版本变化。

固定为:

pod-security.kubernetes.io/enforce-version: v1.34

可以使策略语义更稳定。但它不是永久兼容保证:当集群不再支持该策略版本,仍需要按升级计划迁移。

使用 kubectl 预检查

可以在不真正创建对象的情况下预检查:

kubectl label --dry-run=server --overwrite ns team-a \
  pod-security.kubernetes.io/enforce=restricted

也可以直接检查资源:

kubectl apply --dry-run=server -f pod.yaml

如果命名空间启用了 warn,客户端可能看到类似以下警告:

Warning: would violate PodSecurity "restricted:v1.34": ...

警告文本是诊断入口,但具体字段应以当前集群返回内容为准,不能依赖某个固定错误字符串做自动化解析。


五、一个完整的 Pod 安全上下文

下面的 Pod 以 restricted 常见要求为目标:

apiVersion: v1
kind: Pod
metadata:
  name: secure-demo
  namespace: team-a
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10000
    runAsGroup: 10000
    fsGroup: 10000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: nginx:1.27
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
        readOnlyRootFilesystem: true

创建:

kubectl apply -f secure-demo.yaml
kubectl get pod secure-demo -n team-a
kubectl describe pod secure-demo -n team-a

这个例子中有几层含义:

  • runAsNonRoot: true 要求进程不能以 UID 0 运行;
  • runAsUser: 10000 指定目标 UID;
  • runAsGroup 指定主组;
  • fsGroup 主要影响挂载卷上的组所有权和访问组;
  • seccompProfile: RuntimeDefault 使用运行时默认 seccomp 配置;
  • allowPrivilegeEscalation: false 禁止进程通过 setuid 等机制获得更高权限;
  • drop: [ALL] 删除所有 Linux capabilities;
  • readOnlyRootFilesystem: true 将根文件系统挂载为只读。

但是这个示例未必能直接运行。nginx 可能需要写入某些路径,例如 PID、缓存或临时目录。若根文件系统只读,应显式提供可写的临时卷:

apiVersion: v1
kind: Pod
metadata:
  name: secure-nginx
  namespace: team-a
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 101
    runAsGroup: 101
    fsGroup: 101
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: nginx
      image: nginxinc/nginx-unprivileged:1.27
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
        readOnlyRootFilesystem: true
      volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /var/cache/nginx
        - name: run
          mountPath: /var/run
  volumes:
    - name: tmp
      emptyDir: {}
    - name: cache
      emptyDir: {}
    - name: run
      emptyDir: {}

这里使用非 root 镜像很重要。仅仅写:

runAsNonRoot: true

并不能把一个只支持 root 的镜像“改造成”非 root 镜像。Kubelet 会根据镜像元数据和运行时配置判断,如果无法证明容器不会以 root 启动,Pod 可能出现:

container has runAsNonRoot and image will run as root

六、Pod 级与容器级 SecurityContext 的作用域

securityContext 可以写在 spec.securityContext,也可以写在每个容器的 securityContext

Pod 级字段

常见 Pod 级字段包括:

spec:
  securityContext:
    runAsUser: 10000
    runAsGroup: 10000
    runAsNonRoot: true
    fsGroup: 10000
    seccompProfile:
      type: RuntimeDefault

它们通常作为 Pod 内容器的默认值,适合表达整个 Pod 的共同身份或策略。

容器级字段

容器级配置例如:

containers:
  - name: app
    securityContext:
      allowPrivilegeEscalation: false
      privileged: false
      capabilities:
        drop: ["ALL"]
      readOnlyRootFilesystem: true

这些字段直接作用于某个容器。

当 Pod 级和容器级都设置同一属性时,不能简单理解为“两个值合并”。通常容器级配置会覆盖对应的 Pod 默认值;但不同字段的继承和校验规则不同,最终应查看生成后的 Pod:

kubectl get pod secure-demo -n team-a -o yaml

Admission 也会检查 Pod 中的所有相关容器,包括:

  • 普通 containers
  • initContainers
  • 在相应版本规则中受检查的 ephemeral containers。

因此只保护主容器而遗漏 initContainer 是常见错误:

initContainers:
  - name: migration
    image: migration:latest
    securityContext:
      privileged: true

即使主容器完全符合 restricted,整个 Pod 仍可能因为 initContainer 违规而被拒绝。


七、关键安全字段的真实语义

1. runAsNonRootrunAsUser 与 root

这三个概念不同:

  • runAsUser: 10000:请求使用 UID 10000;
  • runAsNonRoot: true:拒绝以 UID 0 运行;
  • 镜像中的 USER:镜像默认用户,只有在没有更高层配置时才可能成为最终依据。

一个常见误解是:

runAsUser: 10000

就能保证进程安全。它只改变身份,不限制:

  • 是否能访问挂载的宿主机路径;
  • 是否持有危险 capabilities;
  • 是否可以利用内核漏洞;
  • 是否能访问 Kubernetes API;
  • 是否能写入共享卷中的敏感文件。

非 root 是重要约束,但不是完整隔离。

2. allowPrivilegeEscalation

该字段控制进程是否可以获得比启动时更高的权限,通常对应 Linux 的 no_new_privs 语义。

securityContext:
  allowPrivilegeEscalation: false

它不是“禁止所有特权”的同义词:

  • 容器可能仍以 root 启动;
  • 容器可能仍拥有已有 capabilities;
  • privileged: true 的行为不能靠它补救;
  • 它通常不能与需要 setuid 提权的程序兼容。

restricted 要求该字段为 false,这是因为“初始权限不高”还不够,进程也不应通过执行特殊文件获得更高权限。

3. Linux capabilities

Linux capability 将传统 root 权限拆分为多个能力,例如:

  • NET_BIND_SERVICE:绑定低于 1024 的端口;
  • NET_ADMIN:部分网络管理操作;
  • SYS_ADMIN:权限范围很大,风险高;
  • SYS_PTRACE:跟踪其他进程。

更安全的默认写法是:

capabilities:
  drop:
    - ALL

若应用确实需要绑定低端口,可以只添加必要能力:

capabilities:
  drop:
    - ALL
  add:
    - NET_BIND_SERVICE

不要因为“容器不是 root”就忽略 capabilities。一个非 root 进程如果被授予高风险 capability,仍可能执行高影响操作。反过来,删除所有 capabilities 也不等于没有所有权限,因为权限还来自 UID、文件权限、seccomp、设备、挂载和内核漏洞等来源。

4. privileged

securityContext:
  privileged: true

通常会显著扩大容器与宿主机的交互能力,可能获得设备访问、更多 capabilities 以及弱化的隔离语义。它不是修复权限问题的普通开关。

需要节点级能力的系统组件可以使用它,但应通过:

  • 独立命名空间;
  • 严格 RBAC;
  • 固定镜像;
  • 节点隔离;
  • 最小化挂载;
  • 审计和变更审批;

限制影响范围。

5. readOnlyRootFilesystem

readOnlyRootFilesystem: true

它只读的是容器根文件系统,不代表所有文件都只读。以下位置仍可能可写:

  • emptyDir
  • PVC;
  • Secret 或 ConfigMap 的特定挂载语义;
  • 某些运行时提供的文件系统。

生产中开启该选项后,应根据应用启动失败日志补充明确的 emptyDir 或持久卷,而不是直接关闭该限制。

6. fsGroup

spec:
  securityContext:
    fsGroup: 10000

fsGroup 主要用于卷挂载时的组访问控制。它不是进程 UID,也不是 Kubernetes 用户身份。某些卷类型和 CSI 驱动对 fsGroup 的处理不同,可能出现:

  • 挂载时递归修改大量文件导致启动变慢;
  • 存储驱动不支持预期的组变更;
  • 共享卷中的旧文件权限仍然不正确。

所以 fsGroup 的实际效果取决于卷类型、CSI 驱动和节点实现,不能仅凭 YAML 推断全部行为。


八、seccomp、AppArmor 与 SELinux

1. seccomp

seccomp 通过限制进程可调用的系统调用来缩小内核攻击面。常见配置为:

securityContext:
  seccompProfile:
    type: RuntimeDefault

类型主要包括:

  • RuntimeDefault:使用容器运行时默认 profile;
  • Localhost:使用节点上配置的本地 profile;
  • Unconfined:不使用 seccomp 限制。

RuntimeDefault 的具体系统调用集合由容器运行时和节点配置决定,不是一个跨所有发行版完全相同的固定列表。若应用启动后报 Operation not permitted,可能是 seccomp 拦截了系统调用,诊断时需要结合:

kubectl describe pod POD -n NAMESPACE
kubectl logs POD -n NAMESPACE

并检查节点运行时、内核审计日志。不要首先将 profile 改成 Unconfined 作为永久修复;这会扩大系统调用面,正确做法通常是确认应用确实需要的调用,再提供经过测试的本地 profile。

2. AppArmor

AppArmor 是 Linux LSM 之一,通过配置文件约束文件访问、能力和部分系统行为。它要求:

  • 节点内核支持 AppArmor;
  • 节点加载对应 profile;
  • Kubernetes 与容器运行时支持对应配置方式。

较新的 Kubernetes API 提供 appArmorProfile 字段时,可以在 securityContext 中声明 profile;较旧集群常见的是使用 container.apparmor.security.beta.kubernetes.io/<container-name> 注解。注解方式与字段方式不能不加判断地混用,必须以目标 Kubernetes 版本 API 文档和节点支持情况为准。

AppArmor 不是 Pod Security Admission 的替代品。PSA 可以阻止不允许的安全配置,但不会为业务自动生成一个正确的 AppArmor profile。

3. SELinux

SELinux 通过标签和策略控制进程与文件的访问。相关配置可能包括:

securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"

但 SELinux 标签具有强烈的节点发行版和集群配置依赖。错误配置可能导致容器无法访问本应可访问的文件。生产环境应确认:

  • 节点是否启用 SELinux;
  • CRI 是否正确处理标签;
  • 存储驱动是否支持;
  • 标签是否与平台安全策略一致。

PSS 对 SELinux 相关字段有基线约束,但“通过 PSS”不等于“SELinux 策略已经正确保护业务”。


九、一个会失败的基线示例

以下 Pod 同时使用了多个高风险设置:

apiVersion: v1
kind: Pod
metadata:
  name: unsafe-demo
  namespace: team-a
spec:
  hostNetwork: true
  hostPID: true
  containers:
    - name: shell
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      securityContext:
        privileged: true
      volumeMounts:
        - name: host
          mountPath: /host
  volumes:
    - name: host
      hostPath:
        path: /

如果 team-a 使用:

pod-security.kubernetes.io/enforce=baseline

执行:

kubectl apply -f unsafe-demo.yaml

预期请求被拒绝,错误通常会列出一个或多个违规项,例如:

violates PodSecurity "baseline": privileged, host namespaces, hostPath ...

这里的因果链是:

  1. privileged 扩大容器权限;
  2. hostPID 让容器看到主机进程命名空间;
  3. hostNetwork 让容器使用主机网络栈;
  4. hostPath: / 暴露宿主机文件系统;
  5. 任意一项都可能形成严重风险,组合使用时风险更高;
  6. baseline 在 Admission 阶段拒绝对象,因此 Pod 不会进入调度和启动阶段。

这与“Pod 创建成功但容器启动失败”不同。若是 PSA 拒绝,通常不会产生一个可查询的运行中 Pod:

kubectl get pod unsafe-demo -n team-a

可能得到:

No resources found

而不是 CrashLoopBackOff


十、Baseline 与 Restricted 的迁移方法

可以先创建一个“只观察、不阻断”的命名空间策略:

apiVersion: v1
kind: Namespace
metadata:
  name: migration
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.34
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.34

应用业务 Pod:

kubectl apply -f workload.yaml -n migration

然后观察三类信息:

kubectl get events -n migration --sort-by=.lastTimestamp
kubectl get pod -n migration
kubectl describe pod POD_NAME -n migration
  • warn:直接显示给执行命令的客户端;
  • audit:进入审计事件,具体保存方式取决于 API Server 审计配置;
  • enforce:不符合时请求失败。

修复的顺序应以违规字段为依据,而不是盲目添加全部字段:

  1. 移除 privileged、host namespace 和不必要的 hostPath;
  2. 删除所有 capabilities,再按错误逐个恢复必要能力;
  3. 设置 runAsNonRoot 和非 root UID;
  4. 设置 allowPrivilegeEscalation: false
  5. 使用 RuntimeDefault seccomp;
  6. 配置只读根文件系统,并显式提供必要可写目录;
  7. 修复 initContainer 和临时调试容器;
  8. 在验证所有工作负载后,将 enforce 从 baseline 提升为 restricted。

不能把 warnaudit 当成保护措施。它们只提供可见性,不会阻止不安全 Pod 进入集群。


十一、PSA 的豁免与自动化风险

集群管理员可以通过 PSA 配置文件设置豁免范围,例如豁免:

  • 特定用户名;
  • 特定运行时类;
  • 特定命名空间;
  • 特定资源请求。

豁免通常用于系统组件或受控基础设施,而不是为普通业务开发提供“绕过按钮”。

真实风险在于,某些工作负载并不是用户直接创建的:

用户提交 Deployment
→ Deployment Controller 创建 ReplicaSet
→ ReplicaSet Controller 创建 Pod

PSA 最终检查的是 Pod 对象,但 Admission 请求的用户可能是控制器 ServiceAccount。于是需要确认:

  • 豁免匹配的是原始用户还是控制器身份;
  • 生成的 Pod 是否会落入受保护命名空间;
  • 自定义 Operator 是否需要特殊权限;
  • 扩容、滚动更新和恢复流程是否都能通过策略。

测试只执行一次 kubectl apply 不足以证明控制器工作正常。至少应验证:

kubectl rollout status deployment/app -n team-a
kubectl get rs,pod -n team-a
kubectl describe deployment app -n team-a

十二、Webhook 与 PSA 的故障语义不同

PSA 是内置 Admission 插件,而自定义策略通常通过 Validating Webhook 或 Mutating Webhook 实现。

Webhook 常有以下配置:

failurePolicy: Fail
  • Fail:Webhook 不可达或返回错误时,拒绝请求;
  • Ignore:Webhook 故障时放行请求。

Fail 提高策略完整性,但 Webhook 的可用性会直接影响集群写入;Ignore 提高可用性,却可能在故障期间形成策略旁路。

PSA 的核心策略不应依赖一个可能因网络、证书、Service、端点或升级而不可达的外部 Webhook。常见分工是:

  • PSA 负责官方 Pod 安全基线;
  • Webhook 负责组织特有规则,例如镜像仓库、标签、资源限制、业务隔离;
  • Webhook 必须有超时、证书轮换、拓扑和升级方案;
  • 对安全强制规则慎用 Ignore

还要注意 Admission 的失败路径。请求可能在:

  • 授权阶段被拒绝;
  • Mutating Webhook 阶段超时;
  • PSA 阶段违反 baseline;
  • Validating Webhook 阶段因策略失败;
  • API Server 持久化或 etcd 阶段失败。

诊断时应根据 HTTP 错误、API Server 日志、事件和 Webhook 配置区分原因,而不是把所有“创建失败”都归咎于 PSA。


十三、排查 Pod 安全问题的顺序

1. 确认目标命名空间和标签

kubectl get ns team-a -o yaml
kubectl label ns team-a --list

重点检查:

  • 是否真的在预期命名空间;
  • enforce 是否存在;
  • enforce-version 是否固定;
  • 是否有命名空间拼写或上下文错误。

2. 确认服务端实际收到的对象

客户端 YAML 可能由 Helm、Kustomize 或 Operator 生成。应检查最终清单:

helm template release ./chart -n team-a
kubectl apply --dry-run=server -f rendered.yaml

不要只检查模板源文件。

3. 区分拒绝与运行失败

  • kubectl apply 立即返回 violates PodSecurity:通常是 Admission 拒绝;
  • Pod 存在但 Pending:可能是调度、卷或节点问题;
  • Pod 存在但 CrashLoopBackOff:可能是用户、文件权限、只读根文件系统或 seccomp;
  • Pod 创建后立即消失:可能是控制器回滚或对象被删除。

4. 检查所有容器和卷

kubectl get pod app-xxx -n team-a -o jsonpath='{.spec.initContainers[*].securityContext}'
kubectl get pod app-xxx -n team-a -o yaml

检查主容器、initContainer、ephemeral container、卷、主机命名空间和端口,而不是只看第一个容器。

5. 在节点侧确认运行时限制

当 YAML 看起来正确但行为不符时,还要检查:

  • 节点内核是否支持 seccomp/AppArmor/SELinux;
  • 容器运行时实际加载的 profile;
  • CSI 驱动是否处理 fsGroup;
  • 镜像真实默认用户;
  • 节点是否属于不同发行版或不同运行时。

云厂商托管 Kubernetes 还可能对节点镜像、Admission 配置、默认 seccomp、AppArmor 和系统组件做额外处理。这些行为不属于 Kubernetes API 的统一保证,必须查目标平台文档和节点实际状态。


十四、边界与生产取舍

1. PSS 不是完整容器安全方案

PSS 主要检查 Pod 规格,不能解决:

  • 应用漏洞;
  • 恶意镜像;
  • Kubernetes API 权限过大;
  • Secret 泄露;
  • 网络横向移动;
  • 节点内核漏洞;
  • 供应链投毒;
  • 容器运行后的异常行为。

因此还需要分别配置 RBAC、NetworkPolicy、镜像签名或扫描、Secret 管理、节点加固和运行时检测。

2. 非 root 不是绝对隔离

非 root 能减少传统 root 风险,但容器仍可能:

  • 访问错误挂载的 Secret;
  • 访问共享卷中的其他数据;
  • 使用被授予的 capabilities;
  • 利用内核或运行时漏洞;
  • 通过应用身份访问 Kubernetes API。

3. Restricted 可能与旧应用冲突

旧应用常见不兼容包括:

  • 启动脚本要求 root;
  • 需要写入镜像内目录;
  • 需要低端口但不能添加 NET_BIND_SERVICE
  • 需要额外系统调用;
  • 依赖 setuid;
  • 使用 hostNetwork 或 hostPath;
  • initContainer 需要修改共享卷权限。

这些问题不应通过把整个命名空间改为 privileged 解决。更合理的方式是:

  • 修改镜像和启动脚本;
  • 使用固定非 root UID;
  • 提供明确的 writable volume;
  • 缩小 capability;
  • 编写受控 seccomp/AppArmor profile;
  • 将确实需要节点权限的组件放入独立命名空间并限制其权限。

4. “通过 baseline”不是安全证明

形式上,如果:

PbaselineP \models baseline

只能说明 Pod 满足 baseline 定义的约束。它不能推出:

PrestrictedP \models restricted

更不能推出:

P 不存在运行时安全风险P \text{ 不存在运行时安全风险}

安全级别是策略集合之间的关系,而不是风险评分。一个 Pod 可以通过 baseline,却仍以 root 运行、根文件系统可写、拥有过多应用权限。


十五、一个可操作的最低闭环

对普通业务命名空间,可以采用如下闭环:

metadata:
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.34
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.34
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.34

然后对工作负载逐步落实:

spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
    - securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
        readOnlyRootFilesystem: true

验证不仅包括:

kubectl apply --dry-run=server -f workload.yaml
kubectl apply -f workload.yaml
kubectl rollout status deployment/app -n team-a
kubectl logs deployment/app -n team-a

还要验证升级、回滚、扩容、initContainer、探针、卷权限和节点差异。最终把 restrictedwarn/audit 提升为 enforce,应以生产工作负载已经完成兼容性验证为前提。

Pod Security 的正确理解不是“给 YAML 加几个字段”,而是建立一条完整因果链:

安全目标
→ SecurityContext 表达约束
→ PSS 定义可验证规则
→ PSA 在 API Server 阶段拒绝违规对象
→ 容器运行时和内核执行约束
→ 日志、审计和测试验证实际结果

只有这条链条同时成立,Pod 安全基线才不是文档中的声明,而是集群中的可执行控制。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。