K8s Pod 安全上下文实战:最小权限容器配置

在 Kubernetes 里,容器进程默认以 root 用户(UID 0)启动,一旦应用存在远程代码执行之类的漏洞,攻击者就能在容器内直接拿到最高权限,进而挂载宿主目录、篡改系统文件。securityContext 是 K8s 提供的原生安全原语,它让我们无需改动镜像,就可以通过声明式配置把 Pod 收敛为「最小权限」运行:降权到非 root、只读根文件系统、丢弃危险的 Linux capabilities、过滤系统调用。本文用一套生产级 YAML 实战讲解每个字段该怎么填、为什么要填。

一、为什么需要安全上下文

很多团队把注意力放在网络隔离和镜像扫描上,却忽略了「容器里进程以谁的身份跑」这件最基础的事。默认情况下,Dockerfile 里没写 USER 指令,或者基础镜像本身就以 root 打包,最终在集群里运行的进程就是 root。这会带来三类典型风险:

  • 容器内 root 与宿主 root 共享同一个内核,只是被 namespace 和 cgroup 隔离;一旦逃逸成功,危害被放大。
  • 攻击者拿到 root 后,可以安装提权工具、修改 /etc,进一步横向移动。
  • 挂载了 hostPath 或特权容器(privileged)时,root 几乎等于拿到宿主控制权。

安全上下文(securityContext)正是为了堵住这条路径:它在 Pod 和容器两个层级提供一组字段,用来约束进程的身份、文件系统和内核能力,是纵深防御里性价比最高的一环。它与 Cilium 网络策略 形成互补——一个管「网络谁能动」,一个管「进程能做什么」。

二、安全上下文作用在哪些层级

securityContext 可以写在两个地方,作用范围不同:

层级作用范围典型字段
Pod 级(spec.securityContext)对该 Pod 下所有容器生效runAsUser、runAsGroup、runAsNonRoot、fsGroup、seccompProfile
容器级(container.securityContext)仅对单个容器生效,优先级更高以上全部,外加 readOnlyRootFilesystem、capabilities、allowPrivilegeEscalation、privileged

当 Pod 级和容器级都设置了同一字段时,容器级覆盖 Pod 级。推荐的做法是:把通用的身份约束放到 Pod 级,把容器特有的能力裁剪放到容器级,这样既统一又灵活。

三、身份降权:runAsNonRoot 与 runAsUser

最基础的一步是让进程不再以 root 运行。K8s 提供了两种写法:一种是显式指定 UID/GID,另一种是让 K8s 帮你校验「不能是 root」。

apiVersion: v1
kind: Pod
metadata:
  name: app-nonroot
spec:
  securityContext:
    runAsNonRoot: true      # 拒绝以 UID 0 启动,镜像若为 root 则 Pod 起不来
    runAsUser: 1000         # 显式指定非特权 UID
    runAsGroup: 3000        # 主 GID
    fsGroup: 2000           # 挂载卷的属组,便于容器读写
  containers:
  - name: app
    image: nginx:1.27
    securityContext:
      runAsUser: 1001       # 容器级覆盖 Pod 级

这里有个常见的坑:镜像里的工作目录或文件属主是 root,你却用 runAsUser: 1000 去跑,应用会因为权限不足而启动失败。解决方法是重新打镜像(用非 root USER 构建),或者在 Dockerfile 里 chown 目录。如果图省事用 Docker 多阶段构建 时,记得在最终阶段显式声明 USER。

四、文件系统加固:只读根文件系统

很多攻击依赖向磁盘写入 webshell 或下载恶意脚本。把根文件系统设为只读,可以大幅压缩攻击者的操作空间。配合一个可写的 emptyDir 给真正需要落盘的路径即可。

spec:
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /app/cache
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}

只读根文件系统还有一个额外好处:配合 Kyverno 策略即代码 做准入校验,可以强制所有生产负载都开启这一项,从集群层面兜底。

五、能力最小化:capabilities 裁剪

Linux capabilities 把传统 root 的权限拆成了几十个细粒度单元。容器默认会带一组 capabilities(如 CHOWN、NET_RAW、SYS_TIME),其中很多应用根本用不到。最佳实践是「先全部丢弃,再按需添加」。

securityContext:
  capabilities:
    drop:
      - ALL                  # 丢弃全部默认能力
    add:
      - NET_BIND_SERVICE     # 仅允许绑定 1024 以下端口(如 80/443)
  allowPrivilegeEscalation: false

NET_RAW 是尤其该丢弃的能力——它允许构造原始 socket,常被用于内网探测与攻击,而绝大多数 Web 服务并不需要它。drop ALL 之后再只加 NET_BIND_SERVICE,是生产环境的标准姿势。

六、防止提权:allowPrivilegeEscalation

allowPrivilegeEscalation 控制进程是否能通过 setuid 之类的方式获得比父进程更高的权限。把它设为 false,等价于内核的 no_new_privs 标志,能挡住大量本地提权利用。它与必须丢弃 SETPCAP 等能力配合使用,效果最佳。

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
      - SETPCAP

七、系统调用过滤:seccompProfile

即使做了能力裁剪,进程仍然能调用全部系统调用。seccomp(secure computing mode)可以限制进程能用的 syscall 集合。K8s 内置了 RuntimeDefault 这个开箱即用的 profiles,由容器运行时维护一份经过裁剪的安全 syscall 白名单,性价比极高。

securityContext:
  seccompProfile:
    type: RuntimeDefault     # 使用容器运行时提供的默认安全 profile

对于安全要求更高的场景,可以自定义 seccomp profile 文件并挂载进节点,但在 90% 的常规业务里,RuntimeDefault 已经能拦掉相当大一部分内核攻击面。

八、与 Pod Security Admission 的关系

上面这些字段是「应用自己声明」,而 Pod Security Admission(PSA)是「集群层面强制」。PSA 提供三个标准:Privileged(完全放开)、Baseline(适度限制)、Restricted(最严格,强制要求 runAsNonRoot、readOnlyRootFilesystem、drop ALL capabilities 等)。

建议把命名空间标记为 restricted,这样即使开发者忘记写 securityContext,集群也会直接拒绝不符合要求的 Pod 创建。PSA 与 Kubernetes ValidatingAdmissionPolicy 可以组合使用,前者管通用基线,后者管业务定制规则。

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest

九、一份生产级完整示例

把前面所有要点合在一起,下面是一个可以直接落地的 Web 服务 Pod 模板:身份非 root、只读根文件系统、能力全丢只加绑端口、禁止提权、启用 RuntimeDefault seccomp。

apiVersion: v1
kind: Pod
metadata:
  name: web-secure
  namespace: prod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: web
    image: nginx:1.27
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL
        add:
          - NET_BIND_SERVICE
    ports:
    - containerPort: 8080
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: var-cache
      mountPath: /var/cache/nginx
  volumes:
  - name: tmp
    emptyDir: {}
  - name: var-cache
    emptyDir: {}

十、常见坑与排错清单

现象根因解决办法
Pod 一直 CreateContainerConfigError / 起不来runAsNonRoot 为 true,但镜像以 root 打包重打镜像显式 USER 非 root,或确认镜像 entrypoint 不要求 root
应用报 read-only file system 写失败开了 readOnlyRootFilesystem 但程序要写日志/临时文件把 /tmp、缓存目录挂 emptyDir,或改日志输出到 stdout
绑定 80 端口报 permission denieddrop ALL 后缺少 NET_BIND_SERVICE在 add 列表补 NET_BIND_SERVICE
PSA restricted 下被拒绝创建缺 runAsNonRoot 或仍带特权能力补齐第九节模板中的字段,或调整命名空间标签

十一、落地 Checklist

检查项是否必选说明
runAsNonRoot: true必选杜绝以 root 运行
readOnlyRootFilesystem: true推荐压缩写入型攻击面
capabilities drop ALL必选能力最小化
allowPrivilegeEscalation: false必选防止本地提权
seccompProfile RuntimeDefault推荐限制系统调用
命名空间 PSA=restricted推荐集群级强制兜底

十二、小结

securityContext 不要求你改写一行业务代码,却能把容器的攻击面砍掉一大半:身份非 root、根文件系统只读、能力只留必要项、禁止提权、过滤系统调用。把这套配置沉淀进 Helm 模板或 Kustomize 基线,再叠加命名空间的 PSA restricted 策略,就能让「最小权限」从一句口号变成集群的默认行为。在 CI 里用 GitHub Actions 跑 kube-score 或 conftest 校验,可以在合并前就拦下不安全的安全上下文配置。

上一篇 数据脱敏实战:敏感数据识别、加密与动态脱敏方案
下一篇 SonarQube 实战:用静态代码分析把 Bug 挡在合并之前
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]