Security Context 到底是什么

Security Context 是 Kubernetes 用来定义 Pod 或容器运行时权限与访问控制的一组配置。它本质上是在告诉容器运行时:"这个进程该以什么身份、有什么权限、能调用哪些内核功能。"

它控制的东西主要包括:

  • 运行身份:以哪个 UID/GID 运行
  • Linux 权能(Capabilities):授予部分 root 特权而非全部
  • 特权模式:是否以特权模式运行
  • 文件系统:根文件系统是否只读
  • 特权提升:是否允许获取比父进程更高的权限
  • SELinux/AppArmor/seccomp:更细粒度的安全策略

重要:Security Context 的这些设置最终是由容器运行时(如 containerd、CRI-O)翻译成内核级别的指令来执行的。Kubernetes 本身不直接 enforce 这些设置,只是把它们传递给运行时。

配置格式:放在 Pod 还是 Container

securityContext 可以放在两个层级:

  1. Pod 级(spec.securityContext):应用到 Pod 内所有容器
  2. 容器级(spec.containers[].securityContext):仅应用到单个容器

优先级规则:如果 Pod 级和容器级都设置了相同字段,容器级的配置会覆盖 Pod 级的。

建议:把通用的身份配置(runAsUser、runAsGroup、fsGroup)放在 Pod 级,把容器特有的配置(capabilities、allowPrivilegeEscalation)放在容器级。

管理容器的运行身份(UID/GID)

这是生产环境最基础也最容易出错的部分。

核心字段

字段 层级 作用
runAsUser Pod/Container 指定运行进程的 UID
runAsGroup Pod/Container 指定主组 GID
runAsNonRoot Pod/Container 强制要求以非 root 运行
fsGroup Pod 挂载卷的所属组 GID
fsGroupChangePolicy Pod 控制卷权限变更的时机
supplementalGroups Pod 附加组列表

最小可行配置

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:                      # Pod 级配置
    runAsUser: 1000                     # 所有容器以 uid 1000 运行
    runAsGroup: 3000                    # 主组为 3000
    fsGroup: 2000                       # 卷的所属组为 2000
    supplementalGroups: [4000]          # 附加组
  containers:
  - name: sec-ctx-demo
    image: busybox:1.36.1               # 固定具体版本,避免 latest 漂移
    command: ["sh", "-c", "sleep 1h"]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:                    # 容器级配置(覆盖 Pod 级)
      allowPrivilegeEscalation: false
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}

几个关键细节

1. runAsNonRoot 的验证逻辑

设置 runAsNonRoot: true 后,Kubelet 会在容器启动时验证容器是否以非 root 用户运行(UID != 0)。如果容器最终以 root 运行,Pod 会启动失败。

securityContext:
  runAsNonRoot: true
  runAsUser: 1000    # 必须显式指定非 0 的 UID

一个常见的组合错误:如果显式设置 runAsUser: 0 但同时设置了 runAsNonRoot: true,Pod 会直接启动失败——Kubelet 会检测到 UID=0 并报错,这两个配置是互斥的。

2. runAsGroup 未设置时的行为

如果设置了 runAsUser没有设置 runAsGroup,容器的主组 ID 由容器运行时决定

具体来说,容器运行时会优先查 /etc/passwd 中该 UID 对应的主组;如果镜像中没有该 UID 的记录,则回退到容器运行时的默认值(通常是 0,即 root 组)。这是容器运行时的行为,非 Kubernetes 直接控制。

3. fsGroup 的行为与 fsGroupChangePolicy

当设置 fsGroup 时,Kubelet 会递归地将 Pod 挂载的支持 fsGroup 的卷类型的 ownership 改为 fsGroup 指定的 GID。

fsGroupChangePolicy 控制卷权限变更的策略,有效值为 OnRootMismatchAlways

  • Always(默认):每次 Pod 启动都递归修改卷权限
  • OnRootMismatch:仅在卷根目录的权限与预期不匹配时才修改

注意:fsGroupChangePolicy 字段仅适用于支持 fsGroup 的卷类型,对 secret configMap emptyDir 等临时卷类型没有影响

生产环境建议用 OnRootMismatch,可以减少不必要的递归操作,提升启动速度。

4. supplementalGroups 的隐式合并风险

默认情况下,Kubernetes 会把 Pod 中指定的 supplementalGroups 与容器镜像中 /etc/group 文件里定义的、属于该容器主用户的组合并到一起。这意味着即使你在 Pod 清单里只配了 supplementalGroups: [4000],容器里实际生效的附加组可能还包括镜像中定义的 50000、60000 等。

这是一个安全隐患——这些隐式 GID 在 Pod 清单中看不到,无法被策略引擎检测或验证。

Kubernetes v1.35 开始,supplementalGroupsPolicy 字段正式 GA(从 v1.31 Alpha → v1.33 Beta → v1.35 GA)。该字段有两个取值:

  • Merge(默认):合并 Pod 中指定的组与镜像 /etc/group 中定义的组
  • Strict:仅使用 Pod 中显式指定的 fsGroupsupplementalGroupsrunAsGroup,忽略镜像中的组定义

如果你在用 v1.35+,建议显式设置 supplementalGroupsPolicy: Strict 来消除这个安全隐患。

升级提醒:v1.33 beta 阶段引入了一些行为变更(breaking change)——kubelet 会拒绝在无法确保指定策略的节点上运行 Pod。如果你计划从 v1.32 或更早版本升级到 v1.35,请提前查阅官方升级注意事项。

5. runAsUser 与镜像 USER 指令的交互

在 Pod 清单中设置的 runAsUserrunAsGroup 优先于容器镜像中的 USER 指令。这意味着即使 Dockerfile 里写了 USER 1000:1000,Pod 的 securityContext 仍然可以覆盖它。

但有一个坑:如果镜像中没有 runAsUser 指定的 UID 对应的用户(/etc/passwd 中没有记录),容器仍然可以运行——只是 id 命令可能显示 uid=1000 gid=0(如果没设 runAsGroup)。这对于某些依赖用户名解析的应用可能会造成问题。

管理容器的内核功能(Capabilities)

Linux Capabilities 是 Security Context 里最精细、最灵活的控制手段。它把 root 的超级权限拆分成了 30 多个独立单元,你可以按需授予。

配置格式

securityContext:
  capabilities:
    drop: ["ALL"]                        # 先丢弃所有
    add: ["NET_BIND_SERVICE"]            # 再按需添加

生产环境推荐配置

securityContext:
  capabilities:
    drop:
    - ALL                                # 丢弃所有 capabilities
    add:
    - NET_BIND_SERVICE                   # 只保留绑定 1024 以下端口的能力
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true

这个组合拳的效果是:容器除了能绑定特权端口外,几乎没有任何额外的系统权限。如果应用不需要绑定 80/443 端口,连 NET_BIND_SERVICE 都不用加。

有些安全基准(如 CIS Benchmarks)建议"drop all capabilities then add back needed ones"。但实际生产中你可能需要保留 SETPCAPCHOWN,取决于应用的具体行为。建议先在测试环境验证,大部分业务应用只需要 NET_BIND_SERVICE 就够用了。

特权模式容器:能不用就别用

privileged: true 是 Security Context 里最危险的配置,没有之一。

它到底做了什么

特权模式容器几乎拥有宿主机 root 的所有权限,可以:

  • 访问宿主机所有设备
  • 加载/卸载内核模块
  • 修改宿主机网络配置
  • 绕过容器的大部分隔离机制

一个容易被忽略的限制

如果你给容器设置了 privileged: trueseccomp 配置文件将无法生效。特权容器始终以 Unconfined 运行,这意味着你失去了系统调用层面的防护。

allowPrivilegeEscalation 细节

allowPrivilegeEscalation 控制进程能否获得比父进程更高的权限,它直接控制 no_new_privs 标志是否被设置。设置 allowPrivilegeEscalation: false 会阻止进程通过 execve() 获得新的权限(如 setuid 位),这是它安全价值的核心所在。

当容器以特权模式运行或具有 CAP_SYS_ADMIN 权能时,该字段始终为 true。也就是说,你即使显式设置了 allowPrivilegeEscalation: false,只要容器是特权模式或拥有 CAP_SYS_ADMIN,它仍然会被覆盖为 true。

需要留意的是,某些场景下容器可能会隐式获得 CAP_SYS_ADMIN——比如挂载了 hostPath 或某些 CSI 卷时,容器运行时可能会赋予这个权能。这会导致你明明设置了 allowPrivilegeEscalation: false,但它实际上不生效。遇到这种情况,需要检查卷挂载配置是否引入了额外的权能。

建议

  • 默认禁止:在生产集群中,通过 Pod 安全标准(Pod Security Standards)的 BaselineRestricted 策略禁止特权容器
  • 如果非用不可:评估是否可以用 capabilities 替代。比如需要操作网络时,可能只需要 CAP_NET_ADMIN 而不是整个特权模式
  • 绝对不用的场景:任何面向公网的服务

在 Pod 上使用 sysctl 的最佳实践

sysctl 是 Linux 内核参数的配置接口。在 Kubernetes 中,你可以通过 securityContext.sysctls 来设置。

配置格式

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-demo
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "1"
    - name: net.core.somaxconn
      value: "1024"
    - name: kernel.msgmax
      value: "65536"

安全 sysctl vs 不安全 sysctl

Kubernetes 把 sysctl 分为两类:

类型 说明 示例
安全 sysctl 默认允许,无需额外配置 kernel.shm_rmid_forcednet.ipv4.ip_local_port_range
不安全 sysctl 默认禁止,需集群管理员在节点上单独启用 net.core.somaxconnkernel.msgmaxkernel.msgmnb

安全 sysctl 的完整列表(Kubernetes v1.32,含内核版本要求):

sysctl 引入版本 内核版本要求
kernel.shm_rmid_forced - -
net.ipv4.ip_local_port_range - -
net.ipv4.tcp_syncookies - 自内核 4.6+ 支持命名空间作用域
net.ipv4.ping_group_range v1.18+ -
net.ipv4.ip_unprivileged_port_start v1.22+ -
net.ipv4.ip_local_reserved_ports v1.27+ 内核 3.16+
net.ipv4.tcp_keepalive_time 自 v1.29 起加入安全列表 内核 4.5+
net.ipv4.tcp_fin_timeout 自 v1.29 起加入安全列表 内核 4.6+
net.ipv4.tcp_keepalive_intvl 自 v1.29 起加入安全列表 内核 4.5+
net.ipv4.tcp_keepalive_probes 自 v1.29 起加入安全列表 内核 4.5+
net.ipv4.tcp_rmem v1.32+ 内核 4.15+
net.ipv4.tcp_wmem v1.32+ 内核 4.15+
net.ipv4.vs.conn_reuse_mode - 内核 4.1+(用于 IPVS 代理模式)

net.ipv4.tcp_rmemnet.ipv4.tcp_wmem 是在 v1.32 才加入安全列表的。如果你的集群版本低于 v1.32,这两个参数可能仍被视为不安全 sysctl。

启用不安全 sysctl 的步骤

  1. 节点配置:集群管理员需要在 kubelet 的 --allowed-unsafe-sysctls 参数中指定允许的列表
  2. Pod 配置:在 Pod 的 securityContext.sysctls 中设置
  3. (OpenShift 环境):通过 Security Context Constraints 的 allowedUnsafeSysctls 字段控制

最佳实践

  1. 优先使用安全 sysctl:能用安全的就别碰不安全的
  2. 不安全 sysctl 需要审批流程:建议建立变更审批机制
  3. 测试环境先行:sysctl 改错了可能导致节点不稳定,先在测试环境验证
  4. 明确标注:在 Pod 配置中加注释说明为什么要改这个参数

配置 seccomp 配置文件

seccomp(Secure Computing Mode)是 Linux 内核的特性,用于限制进程能够发起的系统调用。Kubernetes v1.19 开始,seccomp 配置正式 GA。

配置方式

apiVersion: v1
kind: Pod
metadata:
  name: seccomp-demo
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault          # Pod 级默认配置
  containers:
  - name: nginx
    image: nginx:latest
    securityContext:
      seccompProfile:
        type: Localhost             # 容器级覆盖,使用本地配置文件
        localhostProfile: my-profile.json

三种 Profile 类型

类型 说明
Unconfined 不应用任何 seccomp 限制
RuntimeDefault 使用容器运行时定义的默认 seccomp 配置文件
Localhost 使用节点上 kubelet seccomp 根目录下的自定义配置文件

优先级与继承规则

  • 如果容器没有显式设置 securityContext.seccompProfile,会继承 Pod 级的配置
  • 如果 Pod 级和容器级都设置了,容器级覆盖 Pod 级——这与 securityContext 其他字段的优先级规则一致

Localhost 配置文件的存放路径

localhostProfile 指定的文件必须预先存放在节点上。在 Linux 系统中,默认路径是 /var/lib/kubelet/seccomp/。例如配置 localhostProfile: my-profile.json,则文件需要放在 /var/lib/kubelet/seccomp/my-profile.json

如果配置文件不存在,容器创建会失败,报错类似:

Error: setup seccomp: unable to load local profile "/var/lib/kubelet/seccomp/nginx-1.25.3.json": open /var/lib/kubelet/seccomp/nginx-1.25.3.json: no such file or directory

与特权模式的关系

只要容器级设置了 privileged: true,seccomp 配置就不会生效,容器会以 Unconfined 运行。这个限制无法绕过。

生产建议

  • 大部分业务容器直接用 RuntimeDefault 就够了
  • 只有对安全要求极高的场景才考虑自定义 Localhost 配置文件
  • 不要同时使用 privileged: true 和 seccomp——它们互斥

验证你的配置是否生效

1. 检查 Pod 中的进程身份

# 进入容器
kubectl exec -it security-context-demo -- sh

# 查看当前用户
id
# 预期输出应显示 uid=1000,gid=3000

groups 部分的实际输出可能因 supplementalGroups 和镜像中 /etc/group 的合并而有所不同。如果设置了 supplementalGroupsPolicy: Strict,则只会显示 Pod 中显式指定的组。

2. 检查 capabilities

# 查看进程的 capabilities(位图形式)
kubectl exec -it security-context-demo -- cat /proc/1/status | grep Cap

CapEff 那行会显示当前生效的 capabilities 位图。如果 drop 了 ALL,你应该看到很小的数值。如果想看更直观的输出,可以用:

kubectl exec -it security-context-demo -- capsh --print

3. 检查 sysctl 是否生效

kubectl exec -it sysctl-demo -- cat /proc/sys/net/core/somaxconn
# 预期输出:1024

4. 检查 seccomp 是否生效

kubectl exec -it seccomp-demo -- grep Seccomp /proc/self/status
# 预期输出:Seccomp: 2(2 表示启用)

常见问题与真实报错

Q1: 设置了 runAsNonRoot: true 但容器仍以 root 运行

现象:Pod 启动失败,事件中有类似报错:

Error: container has runAsNonRoot and image will run as root

原因:容器镜像默认以 root 用户运行(大多数基础镜像都是这样),而 runAsNonRoot: true 要求容器必须以非 root 运行。

解决
- 方案一:设置 runAsUser: 1000 显式指定非 root UID
- 方案二:修改 Dockerfile,用 USER 1000:1000 指定非 root 用户

Q2: 设置了 runAsUser 但容器写不了文件

现象:应用报 Permission denied 错误。

原因runAsUser 改了进程的 UID,但挂载的卷可能还是 root 所有。

解决:在 Pod 级设置 fsGroup,Kubelet 会自动将卷的 ownership 改为该 GID。

spec:
  securityContext:
    runAsUser: 1000
    fsGroup: 2000   # 关键:让卷可写

Q3: 使用不安全 sysctl 时 Pod 无法启动

现象:Pod 一直处于 Pending 状态,事件显示:

forbidden sysctl: "net.core.somaxconn" not allowed in the pod's security context

原因:不安全 sysctl 需要在节点上显式启用。

解决
1. 确认该 sysctl 是否确实需要(很多场景其实不需要调)
2. 联系集群管理员在 kubelet 的 --allowed-unsafe-sysctls 中添加
3. 或在 Pod 安全策略/SCC 中配置 allowedUnsafeSysctls

Q4: 容器需要绑定 80 端口但报错

现象:应用启动失败,报 bind: permission denied

原因:默认情况下容器没有 NET_BIND_SERVICE capability,无法绑定 1024 以下的特权端口。

解决:添加 NET_BIND_SERVICE capability:

securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]

Q5: readOnlyRootFilesystem: true 导致应用崩溃

现象:设置了 readOnlyRootFilesystem: true 后,应用启动失败,报类似:

mkdir: cannot create directory '/tmp/cache': Read-only file system

原因:应用的根文件系统被挂载为只读,但应用尝试写入 /tmp 或其他目录。

解决:将需要写入的目录通过 emptyDir 卷单独挂载出来:

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

Q6: 容器有特权模式但 seccomp 不生效

原因:这是 Kubernetes 的设计限制——特权模式会绕过 seccomp 的限制。

解决:不要同时使用特权模式和 seccomp。如果确实需要特权,考虑是否可以用 capabilities 替代。

Pod 安全标准与 Security Context 的对应关系

Kubernetes Pod 安全标准定义了三个策略级别:

策略 说明 对 Security Context 的要求
Privileged 完全不受限 无任何限制
Baseline 防止已知的特权提升,面向非关键应用 禁止特权容器、禁止 host 命名空间、限制 capabilities
Restricted 遵循 Pod 加固最佳实践,面向安全关键应用 在 Baseline 基础上额外要求:runAsNonRoot: trueseccompProfile 必须为 RuntimeDefaultLocalhost

Baseline 策略对 Capabilities 的限制

Baseline 策略下,允许添加的 capabilities 仅限于以下列表:

AUDIT_WRITECHOWNDAC_OVERRIDEFOWNERFSETIDKILLMKNODNET_BIND_SERVICESETFCAPSETGIDSETPCAPSETUIDSYS_CHROOT

Restricted 策略下只允许 NET_BIND_SERVICE(或空列表)。

如果你在 Baseline 策略下试图添加 CAP_SYS_ADMIN 等不在列表中的 capability,Pod 会被拒绝。

这个列表可能会随 Kubernetes 版本变化,建议以官方文档的最新版本为准。

生产环境建议

  • 开发/测试环境:可以用 Baseline,允许一定灵活性
  • 生产环境:强烈建议用 Restricted,配合合理的 securityContext 配置
  • 系统级组件(如网络插件、监控 agent):可能需要 Privileged,但应尽量缩小范围

核心要点总结

  1. Security Context 是生产环境必选项,不是可选项。 至少要把 runAsNonRoot: trueallowPrivilegeEscalation: false 配上。
  2. 容器级配置覆盖 Pod 级,利用这个特性可以做精细控制——通用身份配置放 Pod 级,特殊权限放容器级。
  3. 特权模式是万恶之源,能不用就不用。 99% 的场景可以用 capabilities 替代。而且特权模式会让 seccomp 失效。
  4. sysctl 用之前先确认是安全的还是不安全的,不安全的需要集群管理员在节点上单独启用。安全 sysctl 列表中的部分参数有内核版本要求。
  5. readOnlyRootFilesystem 配了记得挂载可写目录,不然应用会因无法写临时文件而崩溃。
  6. seccomp 用 RuntimeDefault 就够了,除非有特殊需求。别和特权模式一起用。
  7. Pod 安全标准帮你兜底,生产环境用 Restricted 策略,能自动拦截大部分不安全配置。
  8. v1.35+ 可以用 supplementalGroupsPolicy: Strict 来消除镜像 /etc/group 带来的隐式组安全隐患。升级前请注意 v1.33 引入的行为变更。
  9. 本文档仅适用于 Linux 容器——fsGrouprunAsUserrunAsGroup 等字段在 Windows 节点上不支持。