Kubernetes Pod Security 落地:AI 容器不要跑在 privileged 模式
一、特权容器:AI 推理最常见的错误运维姿势
生产集群上一台 GPU 节点跑着三个模型服务。查 Pod 的 securityContext 时发现是空的。运维同事解释:"模型加载需要 /dev/nvidia0 设备,不跑 privileged 模式容器起不来。"
带着这个信念部署的 GPU 节点,攻击面是裸金属级别的。特权容器可以:
- 访问宿主机的所有设备(不只是 GPU)
- 修改内核参数和加载内核模块
- 挂载宿主机文件系统
- 逃逸到节点层,横向移动到其他 Pod
这些都发生在一行 privileged: true 之后。
Pod Security Standards 在 K8s 1.25 GA 之后已经提供了三层策略:Privileged、Baseline、Restricted。大多数生产场景应该锁定在 Baseline 级别。AI 推理负载的特殊需求——GPU 设备访问、大页内存、共享内存——完全可以通过精细权限配置满足,不需要特权模式。
二、SecurityContext 精细权限:用 capabilities 替代 privileged
privileged: true 是一个安全上极大的开关。它赋予了容器几乎所有的 Linux capabilities,外加禁用 seccomp 和 AppArmor。实际需求可以通过以下分解项逐一满足:
flowchart LR
subgraph 需求分解
A[需要访问 GPU] –> A1[device plugin 挂载]
B[需要大共享内存] –> B1[tmpfs 挂载 /dev/shm]
C[需要修改内核参数] –> C1[initContainer + 特定 cap]
D[需要低端口] –> D1[NET_BIND_SERVICE cap]
end
subgraph 安全策略
A1 –> P[Pod Security: Baseline]
B1 –> P
C1 –> P
D1 –> P
end
P –> Q[统一审计与准入控制]
每个需求的解决方案:
- GPU 设备访问:使用 NVIDIA Device Plugin + nvidia.com/gpu 资源请求,不需要特权
- 大共享内存:mount /dev/shm 为 tmpfs 而非宿主机路径
- 内核参数:通过 initContainers 使用 CAP_SYS_ADMIN 一次性设置,主容器不需要
- 低端口:如果推理服务需要监听 443,添加 CAP_NET_BIND_SERVICE
Baseline 策略本质上允许以下配置:
- hostNetwork、hostPID、hostIPC 都设为 false
- capabilities 只添加必要的,不包含 SYS_ADMIN 和 NET_RAW
- runAsNonRoot 设为 true,但允许 root 用户(部分模型加载需要)
常见的'我也许以后会用到这些权限'思路是高风险的。权限缩小到最小可行集,这是安全左移最直接的实践。基础设施不需要漂亮话,需要的是在每一个 Pod manifest 里把这些字段填对。
三、生产级准入控制:OPA Gatekeeper 拦截特权 Pod
YAML 里写规范没人强制遵守,等于没写。准入控制器(Admission Controller)是确保策略落地的最后一道防线。
package admission
import (
"context"
corev1 "k8s.io/api/core/v1"
)
// AIWorkloadValidator 校验 AI 推理 Pod 的安全上下文
type AIWorkloadValidator struct {
// allowedCapabilities 允许的额外 capabilities 白名单
allowedCapabilities map[string]bool
}
// Validate 检查 Pod 的 securityContext 在部署前拦截不合规配置
func (v *AIWorkloadValidator) Validate(
ctx context.Context, pod *corev1.Pod,
) error {
for i, container := range pod.Spec.Containers {
sc := container.SecurityContext
if sc == nil {
return fmt.Errorf(
"容器 %s: securityContext 不允许为空",
container.Name,
)
}
// 规则1: 禁止 privileged 模式
if sc.Privileged != nil && *sc.Privileged {
return fmt.Errorf(
"容器 %s: privileged 模式被禁止,请使用细粒度权限",
container.Name,
)
}
// 规则2: 不允许以 root 用户运行
if sc.RunAsUser == nil || *sc.RunAsUser == 0 {
return fmt.Errorf(
"容器 %s: 禁止以 root 用户运行",
container.Name,
)
}
// 规则3: root 文件系统只读
if sc.ReadOnlyRootFilesystem == nil ||
!*sc.ReadOnlyRootFilesystem {
return fmt.Errorf(
"容器 %s: root 文件系统必须为只读",
container.Name,
)
}
// 规则4: capabilities 白名单校验
caps := sc.Capabilities
if caps != nil {
for _, add := range caps.Add {
if !v.allowedCapabilities[string(add)] {
return fmt.Errorf(
"容器 %s: capability %s 不在白名单中",
container.Name, add,
)
}
}
}
}
return nil
}
部署形态使用 OPA Gatekeeper 或 Kyverno 的 ValidatingWebhookConfiguration:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-security-ai-workloads
webhooks:
– name: pod-security.ai.dev
clientConfig:
service:
name: pod-security-webhook
namespace: security
path: /validate
rules:
– operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchLabels:
workload-type: ai-inference
failurePolicy: Fail
timeoutSeconds: 5
注意两处设计细节:
- failurePolicy: Fail:Webhook 故障时拒绝创建 Pod,防止合规性被绕过。这要求 webhook 服务本身高可用部署
- namespaceSelector:只拦截打了 workload-type: ai-inference 标签的命名空间,避免影响系统组件
四、三个你会遇到的现实问题
GPU Operator 需要特权。NVIDIA GPU Operator 的 nvidia-device-plugin-daemonset 确实需要 privileged: true。但这是系统组件,它应该跑在 kube-system 命名空间。用户的推理 Pod 不需要特权——这是两个完全不同的安全边界。
共享内存大小。推理服务的 /dev/shm 默认是 64MB,但 NCCL 通信和多进程数据加载可能需要数 GB。解决方案不是开 privileged,而是在 volumes 里显式声明:
volumes:
– name: dshm
emptyDir:
medium: Memory
sizeLimit: 16Gi
模型权重目录写权限。模型文件通常挂载自 PVC,推理服务只需读取。但有些框架在加载时会在模型目录写入缓存文件。解决方式是把缓存路径重定向到 ephemeral volume,而不是给整个模型目录写权限。
不适合 Baseline 策略的少数场景:
- GPU Direct RDMA 需要 hostNetwork 和 CAP_SYS_ADMIN,这需要评估是否真的需要 RDMA 还是 InfiniBand 就足够
- 调试容器需要 CAP_SYS_PTRACE,但生产环境不应出现调试容器
五、总结
privileged: true 不是 AI 推理的必需品。GPU 设备访问通过 Device Plugin 解决,大共享内存通过 emptyDir 解决,特定能力通过精细 capabilities 添加。三个必须落地的动作:
基础设施的安全不是口号。它是每个 Pod manifest 里被填对的每一行 securityContext 配置。



