容器从Created变为Running的本质是Docker Engine调用containerd执行runc start,完成命名空间进入、cgroups设置、伪文件系统挂载及execve用户命令。

容器从 Created 状态变为 Running 状态,本质是 Docker Engine 调用容器运行时(如 containerd)启动一个已创建但尚未执行的容器实例。这个过程不涉及镜像拉取或根文件系统解压(这些在 create 阶段已完成),核心是初始化命名空间、挂载资源、设置 cgroups,并真正执行用户指定的入口命令。
关键步骤:从 create 到 start 的底层链路
Docker CLI 发出 docker start <container-id></container-id> 后,调用路径大致如下:
- Docker CLI → Docker Daemon(通过 Unix socket 或 HTTP API)
- Docker Daemon → containerd(gRPC 调用
StartContainer) - containerd → containerd-shim(启动 shim 进程并委托其管理容器生命周期)
- containerd-shim → runc(调用
runc start执行实际容器启动)
runc start 是状态跃迁的核心动作
runc start 并非“新建进程”,而是基于之前 runc create 已准备好的 bundle(包括 config.json 和 rootfs),完成以下关键操作:
- 进入为该容器预分配的 Linux 命名空间(pid、mnt、net、uts、ipc、user 等),若启用则设置新 user namespace
- 根据 config.json 中的
process.args和process.cwd构造 execve 参数 - 设置 cgroups v1/v2 控制组路径与资源限制(CPU、memory、pids 等)
- 挂载 /proc、/sys、/dev、/dev/pts 等伪文件系统(部分在 create 阶段已 mount,start 阶段确保就绪)
- 调用
clone()(带 CLONE_NEW* 标志)创建 init 进程(即容器主进程),随后execve()运行用户命令
此时内核中产生一个真实 PID,该进程成为容器命名空间中的 PID 1,Docker daemon 收到 runc 成功返回后,将容器状态更新为 Running。
为什么不是所有“created”容器都能成功 running?
start 失败通常卡在 runc 层,常见原因包括:
- cgroups 路径不可写(如 systemd 占用 cgroup v2 delegation)
- 挂载点冲突或权限不足(例如 bind mount 的宿主机路径不存在或 noexec/nosuid 限制)
- 用户 namespace 映射失败(/etc/subuid 缺失或范围不足)
- OOMKilled 前置触发(cgroup memory limit 过小,init 进程刚 fork 就被 kill)
- SELinux/AppArmor 策略拒绝 exec 或 mount 操作
可通过 runc state <id></id> 查看当前状态细节,或 runc events <id></id> 监听启动事件流辅助定位。
daemon 侧的状态同步机制
Docker daemon 不轮询容器进程,而是依赖 containerd 的事件通知:
- containerd-shim 在 runc start 成功后向 containerd 发送 “Started” 事件
- containerd 通过本地事件总线通知 Docker daemon
- daemon 更新内存中容器对象的
Status字段,并持久化到/var/lib/docker/containers/<id>/hostconfig.json</id>和config.v2.json -
docker ps读取的是 daemon 内存状态,非实时查 runc 或 procfs
因此,docker ps 显示 Running,只代表 daemon 认为它已启动——实际进程可能已在后台崩溃(需结合 docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' 判断)。

















