docker组件拆分解读

Docker 在近几年做的一次关键架构拆分:把原来全堆在一个进程里的容器管理逻辑,拆成了分层、可独立升级的结构,从而避免“一升级 Docker,所有容器一起抖三抖”的历史问题。

gaga.png

早期 Docker 的问题是:
Docker Daemon 自己既管镜像、又管容器、还负责启动进程。

结果就是:

  • Docker 守护进程挂了,容器跟着出问题
  • 升级 Docker,可能影响正在运行的容器
  • 架构耦合严重,难以扩展

于是 Docker 做了关键一步:把容器运行时剥离出去


containerd:中间层容器管理者

Docker 把真正负责容器生命周期的部分拆出来,变成独立项目 containerd

containerd 的职责:

  • 管理容器生命周期(创建、运行、暂停、删除)
  • 管理镜像
  • 管理 snapshot
  • 与底层 runtime 通信

Docker Daemon 不再直接控制容器,而是通过 gRPC 调用 containerd。

containerd 后来被捐赠给了 Cloud Native Computing Foundation,成为云原生核心基础设施之一。

现在 Kubernetes、Docker 等都可以直接用 containerd。


containerd-shim:每个容器一个“保姆进程”

图中下面这一层:
每个容器都会有一个:

containerd-shim

它的存在很巧妙。

作用是:

  • 让容器进程 脱离 containerd 父子关系
  • containerd 重启时容器不会死
  • 接管容器 IO 和退出状态

也就是说:

containerd 死了
  ↓
shim 还活着
  ↓
容器继续运行

所以现在:

  • 升级 Docker
  • 重启 containerd

容器仍然继续跑。


runc:真正创建容器的执行者

再往下是:

runc

这是符合 OCI 标准的 runtime。

它干的事情非常“底层”:

  • 设置 namespace(隔离)
  • 设置 cgroups(资源限制)
  • 挂载 rootfs
  • 启动容器进程

换句话说:

runc = 用 Linux 内核功能真正启动容器

runc 项目来自 Open Container Initiative,保证不同 runtime 兼容。


总结

当你执行:

1
docker run nginx

真实链路变成:

docker cli
   ↓
Docker Engine
   ↓ gRPC
containerd
   ↓
containerd-shim
   ↓
runc
   ↓
Linux namespace + cgroup
   ↓
容器进程启动

简化架构图

flowchart TB
    A[Docker CLI] --> B[Docker Engine]
    B --> C[containerd]
    C --> D1[containerd-shim]
    C --> D2[containerd-shim]
    D1 --> E1[runc]
    D2 --> E2[runc]
    E1 --> F1[Container Process]
    E2 --> F2[Container Process]

架构升级真正解决了什么?

核心收益有三点:

  1. Docker 重启不再影响容器
  2. runtime 可以独立升级
  3. Kubernetes 等系统可以绕过 Docker 直接用 containerd

这也就是为什么 Kubernetes 后来宣布 弃用 dockershim,直接对接 containerd。

现在服务器上经常能看到:

containerd
containerd-shim-runc-v2
runc

一台机器几十上百个 shim 进程其实很正常——一个容器一个 shim

当你看到 containerd 崩了但容器还在跑,不是 bug,而正是这个架构设计的成功。