容器逃逸是云原生环境中最致命的攻击向量之一。攻击者一旦突破应用进入容器,下一步就是尝试提权、访问宿主机敏感文件甚至控制整个集群。而安全上下文配置,就是直接决定攻击者能否得逞的那道闸门。很多人部署应用时习惯用默认配置,root 用户跑容器、文件系统可写、特权模式开启,这些看似方便的设置,实际上等于把宿主机的钥匙挂在了容器门口。
Docker 和 Kubernetes 都提供了精细化的安全上下文机制,核心逻辑一致但配置层级和表达能力有差异。理解它们如何协同工作,是构建纵深防御体系的基础。下面直接进入具体配置项,每一项都关乎容器的隔离强度。
用户与用户组:放弃 root 是最低要求容器内默认以 root 运行,这个 root 虽然受到内核命名空间的一定限制,但一旦配合某些高危挂载或内核漏洞,权限就能延伸到宿主机。最直接的加固手段就是指定非 root 用户运行进程。在 Docker 中,可以在 Dockerfile 里用 USER 指令指定,也可以在运行时通过 --user 参数覆盖。Kubernetes 则在 securityContext 中通过 runAsUser 和 runAsGroup 字段控制。
这里有一个容易被忽略的细节:runAsUser 指定的 UID 在容器镜像中可能不存在于 /etc/passwd 文件。这完全没问题,内核只关心数字 UID,不关心用户名。但某些应用启动脚本会检查当前用户是否存在,遇到这种情况需要预先在镜像中创建好对应 UID 的用户,或者调整启动脚本逻辑。另外,runAsGroup 最好也显式指定,避免落入默认的 root 组。文件系统权限的判定依赖于 UID 和 GID 的组合,两者都控制住才能有效限制对挂载卷的访问。
特权模式与能力集:最小化内核权限--privileged 标志是容器安全的大忌。它赋予容器几乎所有的内核能力,并解除设备 cgroup 的限制,等于把容器变成了一个轻量级虚拟机。除非是嵌套容器或需要直接操作宿主机硬件的极端场景,否则绝不应该开启。Kubernetes 中对应的配置是 securityContext.privileged,默认 false,但很多 CI/CD 工具和 Helm Chart 会擅自设为 true,部署前务必审查。
比直接使用特权模式更合理的做法,是通过能力集(capabilities)进行细粒度授权。Linux 内核将超级用户权限拆分为数十个独立的能力单元。Docker 默认会为容器开启一组有限的能力,例如 NET_BIND_SERVICE、CHOWN 等,但像 SYS_ADMIN、NET_RAW、SYS_PTRACE 这类高危能力默认是关闭的。Kubernetes 允许在 securityContext.capabilities.add 和 drop 中精确控制。一条铁律是:先通过 drop: ["ALL"] 丢弃全部能力,再按需 add 必要的能力。这种做法比依赖默认白名单更安全,因为默认白名单可能随容器运行时版本变化而引入新的风险能力。
# Kubernetes Pod 安全上下文示例
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 1000
runAsGroup: 3000
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readOnlyRootFilesystem: true
禁止权限提升:no_new_privs 的守护
allowPrivilegeEscalation 字段控制容器进程是否能通过 setuid/setgid 二进制文件或内核能力集获得比父进程更多的权限。这个字段对应内核的 no_new_privs 标志。一旦设为 false,即使容器内存在一个 setuid root 的 /bin/su 程序,普通用户执行它也无法提升为 root。这是防止容器内权限放大的关键开关。
很多运维人员忽略这个配置,因为默认值是 true。但考虑一个场景:攻击者通过 RCE 漏洞获得了一个低权限 shell,如果 allowPrivilegeEscalation 为 true,他就可以尝试利用系统中带 SUID 位的程序进行提权。设为 false 后,这条路就堵死了。除非业务程序本身依赖 setuid 机制(极少数情况),否则一律建议设为 false。
只读根文件系统:让攻击者无处落脚readOnlyRootFilesystem 设为 true 后,容器的根文件系统被挂载为只读。这意味着攻击者无法在 /bin、/tmp、/etc 等目录写入恶意程序、修改配置或植入后门。大多数无状态应用完全可以在只读根文件系统上运行,需要写入的目录(如日志、临时缓存)可以通过挂载 emptyDir 或临时卷来解决。
实现这个配置的关键在于梳理应用的写入路径。常见需要写入的目录包括 /tmp、/var/log、应用特定的 data 目录等。在 Dockerfile 中就应该规划好这些路径的挂载点,在 Kubernetes 中通过 volumeMounts 挂载 emptyDir 或 PVC。一个额外的安全收益是,只读根文件系统能有效防止攻击者利用容器漏洞下载执行恶意二进制文件,因为根本没有可写的执行路径。
Seccomp 与 AppArmor/SELinux:多层内核防护Seccomp(安全计算模式)通过 BPF 规则限制进程可调用的系统调用。Docker 和 Kubernetes 都支持加载自定义 Seccomp 配置文件。默认的 Docker Seccomp 策略已经禁用了约 44 个高危系统调用,包括 mount、kexec_load、ptrace 等。但默认策略是面向通用场景的,针对特定应用可以进一步收紧。例如,一个纯 HTTP API 服务根本不需要 clone、unshare 这类命名空间相关的系统调用,完全可以在自定义 Seccomp 策略中禁止它们。
Kubernetes 中通过 securityContext.seccompProfile 字段指定策略,type 可选 RuntimeDefault 或 Localhost。RuntimeDefault 使用容器运行时的默认 Seccomp 策略,Localhost 则指向节点上预先放置的自定义策略文件。建议生产环境至少使用 RuntimeDefault,关键服务进一步使用自定义策略。AppArmor 和 SELinux 则提供了基于标签的强制访问控制,能在文件、网络、能力等多个维度施加限制。它们的配置复杂度较高,但对抗容器逃逸的效果非常显著。Kubernetes 通过注解或 securityContext.appArmorProfile 字段(1.30 版本后稳定)来指定。
安全上下文在 Pod 与容器级别的继承关系Kubernetes 中安全上下文可以设置在 Pod 级别和容器级别。Pod 级别的 securityContext 对所有容器生效,容器级别的则只对单个容器生效,且会覆盖 Pod 级别的同名字段。这种分层设计允许设置组织级的基线策略,同时为特殊容器保留灵活性。一个常见实践是:在 Pod 级别设置 runAsUser、runAsGroup、fsGroup 等通用字段,在容器级别设置 capabilities、readOnlyRootFilesystem 等应用特定字段。
fsGroup 是一个值得单独强调的字段。它指定挂载卷的文件系统组所有权,确保即使容器以非 root 用户运行,也能读写挂载卷中的数据。Kubernetes 会在挂载卷时递归修改卷中所有文件和目录的组所有权为 fsGroup 指定的值。这个行为在大数据量卷上可能造成明显的启动延迟,需要通过 fsGroupChangePolicy 字段控制,可选 OnRootMismatch 来仅在顶级目录权限不匹配时才执行递归修改。
Pod 安全标准与准入控制手动为每个 Pod 配置安全上下文容易遗漏,Kubernetes 提供了 Pod 安全标准(PSS)来定义三个策略级别:Privileged、Baseline 和 Restricted。Baseline 禁止了已知的特权提升手段,Restricted 则遵循 Pod 加固的最佳实践。通过 Pod 安全准入控制器,可以在命名空间级别强制实施这些标准。
实际落地时,建议将生产命名空间设为 Restricted 级别,这会强制要求 runAsNonRoot=true、drop ALL capabilities、禁止特权模式等。对于确实需要部分特权的系统组件,可以单独划分命名空间并使用 Baseline 级别,同时通过标签豁免特定 ServiceAccount。这种分层策略在安全性和可用性之间取得了平衡,也符合零信任的逐步收紧原则。
Docker 与 Kubernetes 配置的映射与协同在混合环境中,应用可能既需要 Docker 直接部署,又需要迁移到 Kubernetes。理解两者安全配置的对应关系有助于保持策略一致性。Docker 的 --user 对应 Kubernetes 的 runAsUser/runAsGroup;--cap-add/--cap-drop 对应 capabilities.add/drop;--read-only 对应 readOnlyRootFilesystem;--security-opt no-new-privileges 对应 allowPrivilegeEscalation;--security-opt seccomp/apparmor 对应 seccompProfile 和 appArmorProfile。
一个容易被忽视的差异是:Docker 默认会为容器分配一个完整的 IPC 命名空间,而 Kubernetes Pod 内的容器共享 IPC 命名空间。这意味着 Kubernetes 中同一 Pod 内的容器可以通过共享内存或信号量进行通信,但也增加了横向移动的风险。如果不需要 IPC 通信,可以通过 hostIPC: false(默认值)保持隔离,并避免在 Pod 内放置互不信任的容器。
运行时类与沙箱容器对于需要执行不可信代码或处理高风险数据的场景,常规的安全上下文配置可能还不够。Kubernetes 的 RuntimeClass 机制允许为 Pod 指定不同的容器运行时。gVisor 和 Kata Containers 等沙箱运行时提供了额外的隔离层。gVisor 在用户态实现了一个内核接口,拦截应用进程的系统调用并进行过滤;Kata Containers 则使用轻量级虚拟机为每个 Pod 提供独立的内核。这些运行时配合安全上下文配置,可以构成纵深防御的最外层。
配置 RuntimeClass 时,安全上下文的部分字段行为会发生变化。例如,在 Kata Containers 中,privileged 模式仍然存在但隔离性远强于 runc;Seccomp 策略可能由虚拟机监控器而非宿主机内核执行。因此,即使使用了沙箱运行时,安全上下文的最佳实践仍然适用,不能因为有了额外隔离层就放松配置。
审计与持续验证安全上下文配置不是一次性工作。集群中不断有新的工作负载被部署,配置漂移是常态。需要借助工具进行持续审计。kubectl 的 --dry-run=server 和 validate 功能可以在部署前检查 Pod 安全标准合规性。Kube-bench、Kube-hunter 等开源工具能扫描集群中的不安全配置。商业化的云原生安全平台如 Aqua、Sysdig、Prisma Cloud 等提供了更全面的运行时监控和策略执行能力。
一个有效的审计流程是:在 CI 管道中集成安全上下文检查,拒绝不符合 Restricted 标准的部署清单;在集群内部署准入 webhook,对已运行的 Pod 进行周期性合规扫描;将安全上下文配置纳入 IaC 模板,确保所有环境使用统一的加固基线。只有将安全上下文从“建议”提升为“强制”,才能真正缩小攻击面。
容器化环境的安全加固没有银弹,安全上下文配置是距离应用最近的一道防线。它不需要额外的基础设施,不增加明显的性能开销,却能有效阻断大多数已知的容器逃逸路径。从非 root 用户、只读根文件系统、最小化能力集这三个基础配置开始,逐步引入 Seccomp、AppArmor 和 Pod 安全标准,配合持续审计,就能构建起一套实用且有效的容器运行时防护体系。
