Docker容器运行本质是OCI规范驱动的分层执行链:CLI→dockerd→containerd→runc→内核命名空间/cgroups。runc依config.json调用clone/setns/execve创建隔离进程,cgroups限制资源,命名空间实现环境隔离。

运行一个 Docker 容器,表面看是一条 docker run 命令,背后却涉及多层抽象与协作:从用户态的 CLI,到守护进程 dockerd,再到容器运行时(Runtime),最终落到内核的命名空间、cgroups 和 OCI 规范约束之上。理解其 Runtime 架构,是掌握容器本质的关键。
OCI 标准:统一容器运行时的契约
OCI(Open Container Initiative)定义了两个核心规范:runtime-spec 和 image-spec。其中 runtime-spec 明确规定了容器的生命周期(create/start/kill)、配置格式(config.json)、以及 rootfs、mounts、namespaces、cgroups 等必须满足的行为。所有合规的运行时(如 runc、crun、kata-runtime)都必须按此规范解析 config.json 并执行对应系统调用。Docker 本身不直接操作内核,而是生成符合 OCI 的 bundle(含 config.json + rootfs),再交由底层 runtime 执行。
runc:Docker 默认的轻量级 OCI 运行时
runc 是 OCI 官方参考实现,也是 Docker(18.09+)默认使用的运行时。它是一个静态编译的二进制,直接调用 Linux 系统 API:
- 通过
clone()创建进程,并传入CLONE_NEW*标志启用 PID、UTS、IPC、NET、USER、MNT 等命名空间 - 调用
setns()或在 clone 阶段挂载指定 namespace 文件,实现网络/IPC 等资源隔离 - 使用
libcontainer(runc 内置库)设置 cgroups v1/v2 路径并写入进程 PID,实现资源限制 - 执行
execve()启动容器内 init 进程(如 /bin/sh),完成容器启动
Containerd:Docker 的运行时管理层
Docker Engine 不直接调用 runc,而是通过 containerd 作为中间守护进程。它的角色是解耦和稳态管理:
- 接收来自 dockerd 的 gRPC 请求(如 CreateTask、Start),转换为 OCI Bundle 并调用 runc
- 持久化容器状态(task、process、snapshot),即使 dockerd 重启也不丢失运行中容器
- 管理镜像拉取、存储(基于 snapshotter 插件)、网络(通过 CNI 插件)、生命周期事件订阅
- 支持多运行时共存:可通过
containerd config.toml配置多个 runtime(如 runc、crun、kata-clh),按容器标签动态分发
运行时栈全景:从命令到内核的逐层穿透
以 docker run -it --memory=512m ubuntu:22.04 bash 为例,执行链路如下:
- Docker CLI 将参数序列化为 API 请求,发给 dockerd 的 REST 接口
- dockerd 解析镜像、生成 OCI bundle(含 config.json 中 memory.limit_in_bytes=536870912)
- dockerd 调用 containerd 的
TaskService.Create,传入 bundle 路径 - containerd 启动 runc,执行
runc create --bundle /path/to/bundle mycontainer - runc 读取 config.json,设置 cgroups path(如 /sys/fs/cgroup/memory/docker/xxx),写入当前进程 PID
- runc 调用 clone + setns + execve,启动 bash 进程,并将其纳入前述 cgroup 和 namespace 中
至此,一个受内存限制、网络隔离、文件系统独立的 Linux 进程组正式运行——它不是虚拟机,而是一组被严格约束的原生进程。

















