← blog DevOps & 云原生 · 2026-06-09

Docker 资源隔离机制详解

深入剖析 Docker 资源隔离的四大核心机制:Namespaces 视图隔离、Cgroups 资源限制、UnionFS 分层存储,以及 Seccomp/Capability/MAC 安全加固,含架构图与实战示例。

8 min read

Docker 资源隔离机制:Namespaces、Cgroups、UnionFS 与安全加固

Docker 的资源隔离并非 Docker 自身发明,而是对 Linux 内核已有特性 的封装与组合使用。核心依赖三大机制:Namespaces(命名空间)、Cgroups(控制组) 和 UnionFS(联合文件系统),辅以 Seccomp、Capability、AppArmor 等安全加固手段。


Namespaces — 视图隔离(“你能看到什么”)

Namespace 让进程以为自己独占整个系统。Docker 使用了 6 种 namespace:

Namespace隔离内容系统调用
PID进程 IDCLONE_NEWPID
NET网络栈(接口、端口、路由表)CLONE_NEWNET
IPC进程间通信(信号量、消息队列、共享内存)CLONE_NEWIPC
MNT文件系统挂载点CLONE_NEWNS
UTS主机名和域名CLONE_NEWUTS
USER用户和用户组 UID/GID 映射CLONE_NEWUSER

例子:容器内 PID 1 的进程,在宿主机上实际可能是 PID 24567。容器内的 ps 只能看到自己 namespace 里的进程。

底层 API:unshare() 系统调用创建新 namespace,setns() 加入已有 namespace。


Cgroups — 资源限制(“你能用多少”)

Cgroups(Control Groups)限制、记录和隔离进程组使用的物理资源:

子系统限制内容
cpuCPU 时间份额(cpu.shares)、核心数(cpu.cfs_quota_us)
memory内存使用上限(memory.limit_in_bytes)、swap 限制
blkio块设备 I/O 带宽
devices可访问的设备列表
pids最大进程数(pids.max)
net_cls网络包标记(配合 tc 做带宽限制)

Docker 对应参数:

docker run --memory=512m --cpus=1.5 --pids-limit=100 nginx

对应在宿主机上的路径(cgroup v2):

/sys/fs/cgroup/docker/<container-id>/memory.max
/sys/fs/cgroup/docker/<container-id>/cpu.max

UnionFS — 文件系统隔离

通过分层镜像(layered image)实现存储隔离:

graph TD
    W["可写层 (Container Layer) ← 容器运行时的修改写这里"]
    L3["Image Layer 3 (nginx)"]
    L2["Image Layer 2 (deps) ← 只读层,多个容器共享"]
    L1["Image Layer 1 (base OS)"]
    L1 --> L2 --> L3 --> W
  • Copy-on-Write (CoW):修改文件时才从下层复制到可写层,节省磁盘和内存
  • Docker 支持多种 UnionFS 后端:overlay2(默认)、btrfs、zfs 等

安全加固(额外隔离层)

机制作用
Seccomp限制容器可用的系统调用(白名单,约 300 个 syscalls 中默认允许约 50 个)
AppArmor / SELinux强制访问控制(MAC),限制文件访问权限
Capability丢弃不必要的 Linux capabilities(如 CAP_NET_ADMIN、CAP_SYS_ADMIN)
User Namespace容器内 root → 宿主机普通用户的 UID 映射
netfilter/iptables容器间网络隔离(bridge 网络 + 规则)

整体架构

graph TD
    subgraph ContainerProcess["Container Process"]
        NS["PID ns '我看到'<br/>NET ns '我的IP'<br/>USER ns '我是 root'"]
    end
    CG["Cgroup 限制<br/>CPU: 1.5 核 | 内存: 512MB | PIDs: 100"]
    UF["UnionFS (overlay2)<br/>只读镜像层 + 可写容器层 (CoW)"]
    Kernel["Linux Kernel<br/>Seccomp + AppArmor + Capabilities"]
    ContainerProcess --> CG --> UF --> Kernel

一句话总结

  • Namespace:让进程”看”不到别人(隔离视图)
  • Cgroup:让进程”用”不了太多(限制资源)
  • UnionFS:让进程”改”不到镜像(分层存储)
  • Seccomp/Capability/MAC:让进程”干”不了坏事(安全加固)

Docker 本质上就是对这些 Linux 内核特性的封装和组合使用,它本身并不包含这些隔离机制——这些是内核提供的,Docker 只是通过 runc(OCI runtime)调用了相应的 API。


深入 Seccomp(Secure Computing Mode)

核心原理

Linux 有大约 300-400 个系统调用(syscalls),比如 open, read, write, execve, mount, reboot 等。大多数容器在正常运行时只需要其中一小部分。

Seccomp 做的事情就是:白名单/黑名单过滤系统调用,非法调用直接返回错误或杀掉进程。

工作流程:

graph TD
    Syscall["容器进程调用 syscall (如 mount)"] --> Filter["Seccomp 过滤器 ← BPF 规则匹配"]
    Filter --> Allow["允许 → 正常执行 (继续)"]
    Filter --> Deny["拒绝"]
    Deny --> E1["ENOSYS (返回错误)"]
    Deny --> E2["SIGSYS (杀掉进程)"]

两种模式

模式说明
strict(经典模式)只允许 read, write, _exit, sigreturn 四个调用,其他一律 SIGSYS 杀进程。太严格,基本没人用。
filter(BPF 模式)用 BPF 规则自定义允许/拒绝哪些调用。Docker 用的就是这个。

BPF(Berkeley Packet Filter)原本是做网络包过滤的,内核把它复用为通用的规则引擎——seccomp-bpf 就是用 BPF 程序来决定每个 syscall 的命运。

Seccomp 的动作(Actions)

当 syscall 命中规则时,可以执行以下动作:

Action效果
SECCOMP_RET_ALLOW放行,正常执行
SECCOMP_RET_ERRNO返回错误码(通常是 ENOSYS:系统调用未实现),进程不死
SECCOMP_RET_KILL_THREAD杀掉当前线程
SECCOMP_RET_KILL_PROCESS杀掉整个进程
SECCOMP_RET_TRACE交给 ptrace 处理(调试场景)
SECCOMP_RET_LOG放行但记录日志
SECCOMP_RET_TRAP发送 SIGSYS 信号(可被捕获处理)

Docker 默认对不允许的 syscall 使用 SECCOMP_RET_ERRNO(返回错误,不杀进程),这样容器内程序会收到”此调用不可用”的反馈,而不是直接崩溃。

Docker 的默认 Seccomp Profile

Docker 有一个内置的默认 profile(约 300 行 JSON),定义了默认的过滤规则:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    { "names": ["read", "write", "open", "close", "..."], "action": "SCMP_ACT_ALLOW" },
    { "names": ["mount", "umount2"], "action": "SCMP_ACT_ERRNO" }
  ]
}

逻辑:

  • defaultAction: ERRNO → 默认拒绝所有 syscall
  • 然后白名单列出允许的约 50-60 个关键 syscall
  • 一些调用有条件允许(带参数过滤)

默认允许的典型 syscall

read, write, open, close, stat, fstat, poll, mmap, mprotect,
munmap, brk, ioctl, access, pipe, select, mremap, mmap,
clone, fork, vfork, execve, exit, wait4, getpid, getuid,
getgid, getppid, dup, dup2, socket, connect, accept,
bind, listen, recvmsg, sendmsg, ...

默认禁止的典型 syscall

mount, umount2, reboot, kexec_load, open_by_handle_at,
init_module, delete_module, iopl, ioperm, swapon, swapoff,
syslog, perf_event_open, fanotify_init, kcmp,
add_key, request_key, keyctl, ...

这些被禁止的调用基本都是内核管理类操作——容器不应该挂载磁盘、加载内核模块、重启系统。

自定义 Seccomp Profile

你可以写自己的 JSON profile:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": ["chmod", "chown"],
      "action": "SCMP_ACT_ERRNO"
    },
    {
      "names": ["read", "write", "open", "close"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

使用自定义 profile:

docker run --security-opt seccomp=/path/to/profile.json nginx

或者完全禁用 seccomp(不推荐,仅调试用):

docker run --security-opt seccomp=unconfined nginx

参数级过滤(高级)

Seccomp-bpf 不仅可以根据 syscall 名字过滤,还能根据参数值过滤:

{
  "names": ["socket"],
  "action": "SCMP_ACT_ERRNO",
  "args": [
    {
      "index": 0,
      "op": "SCMP_CMP_NE",
      "value": 2
    }
  ],
  "comment": "只允许 AF_INET (2),其他 socket domain 拒绝"
}

args 字段:

  • index:参数位置(从 0 开始)
  • op:比较运算符(EQ, NE, LT, LE, GT, GE 等)
  • value:比较值

这样可以实现非常精细的控制,比如”允许 clone 但不允许 CLONE_NEWUSER flag”。

Seccomp 在 Docker 中的加载位置

graph TD
    Proc["容器进程"] --> Syscall["syscall (如 mount)"]
    Syscall --> Filter["Seccomp BPF 过滤器 (从 JSON profile 编译而来)<br/>← runc 在容器启动时加载"]
    Filter -->|ALLOW| Ok["执行 mount"]
    Filter -->|ERRNO| Err["返回 ENOSYS 'Function not implemented'"]

加载时机:runc(Docker 的容器运行时)在 clone 创建容器进程后、执行用户程序前,通过 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) 加载 BPF 程序。一旦加载,不可撤销(单向门)。

与其他安全机制的关系

机制过滤粒度特点
Seccomp系统调用级别限制”能调什么”
Capability权限级别限制”有什么特权”(如 CAP_SYS_ADMIN)
AppArmor/SELinux文件/资源级别限制”能访问什么文件/做什么操作”

三者互补:

  • Seccomp 说:你不能调 mount
  • Capability 说:就算你能调 mount,你没有 CAP_SYS_ADMIN 也白搭
  • AppArmor 说:就算你有 CAP_SYS_ADMIN,这个路径你也不许写

实际案例:CVE-2019-5736

CVE-2019-5736(runc 容器逃逸漏洞):攻击者可以通过 /proc/self/exe 覆盖宿主机上的 runc 二进制文件。Seccomp 的默认 profile 禁止了对 /proc/self/exe 的 write 相关操作(通过禁止 ptrace 等相关调用),在部分场景下阻断了此攻击路径。

没有 seccomp 时,容器内的进程理论上可以调用全部 300+ 个 syscall,攻击面巨大。有了 seccomp,攻击面缩减到约 50 个,大幅降低了内核漏洞被利用的风险。

Sources

No external sources for this entry.

Related