1905 words
10 minutes
Docker 资源隔离机制详解

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 对应参数

Terminal window
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)实现存储隔离:

┌─────────────────────────┐
│ 可写层 (Container Layer) │ ← 容器运行时的修改写这里
├─────────────────────────┤
│ Image Layer 3 (nginx) │
├─────────────────────────┤
│ Image Layer 2 (deps) │ ← 只读层,多个容器共享
├─────────────────────────┤
│ Image Layer 1 (base OS)│
└─────────────────────────┘
  • Copy-on-Write (CoW):修改文件时才从下层复制到可写层,节省磁盘和内存
  • Docker 支持多种 UnionFS 后端:overlay2(默认)、btrfs、zfs 等

安全加固(额外隔离层)#

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

整体架构#

┌───────────────────────────────────────────┐
│ Container Process │
│ ┌─────────┐ ┌───────┐ ┌───────────────┐ │
│ │ PID ns │ │ NET ns│ │ USER ns │ │
│ │ "我看到" │ │"我的IP"│ │ "我是 root" │ │
│ └─────────┘ └───────┘ └───────────────┘ │
├───────────────────────────────────────────┤
│ Cgroup 限制 │
│ CPU: 1.5 核 | 内存: 512MB | PIDs: 100 │
├───────────────────────────────────────────┤
│ UnionFS (overlay2) │
│ 只读镜像层 + 可写容器层 (CoW) │
├───────────────────────────────────────────┤
│ Linux Kernel │
│ Seccomp + AppArmor + Capabilities │
└───────────────────────────────────────────┘

一句话总结#

  • 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 做的事情就是:白名单/黑名单过滤系统调用,非法调用直接返回错误或杀掉进程。

工作流程:

容器进程调用 syscall (如 mount)
┌──────────────┐
│ Seccomp 过滤器 │ ← BPF 规则匹配
└──────┬───────┘
┌────┴────┐
│ │
允许 拒绝
│ │
▼ ┌───┴────┐
正常 │ │
执行 ENOSYS 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:

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

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

Terminal window
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 中的加载位置#

容器进程
syscall (如 mount)
┌────────────────────────────────┐
│ Seccomp BPF 过滤器 │ ← runc 在容器启动时加载
│ (从 JSON profile 编译而来) │
└────────────────────────────────┘
│ │
ALLOW ERRNO
│ │
▼ ▼
执行 mount 返回 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/exewrite 相关操作(通过禁止 ptrace 等相关调用),在部分场景下阻断了此攻击路径。

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

Docker 资源隔离机制详解
https://sgjki547.top/posts/docker-resource-isolation/
Author
SGJki
Published at
2026-06-09
License
CC BY-NC-SA 4.0