以下是基于当前技术实现的 Docker 架构原理图(适配 Linux 系统,2024年最新组件关系),配合核心组件详解:

Docker架构设计剖析

Docker 架构图(文字描述 + 伪代码结构)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
graph TD
subgraph User Space
A[Docker CLI<br>“docker” 命令] -->|REST API 调用| B[Dockerd<br>守护进程]
B --> C[Container Runtime Interface]
B --> D[Network Drivers<br>• libnetwork<br>• Bridge/Overlay]
B --> E[Storage Drivers<br>• Overlay2<br>• Volume]
end

subgraph Core Runtime
C --> F[containerd<br>• 任务分发<br>• 镜像管理]
F --> G[containerd-shim<br>• 进程生命周期桥接]
G --> H[runc<br>OCI 实现<br>(创建容器)]
end

subgraph Kernel Space
H --> I[Linux Kernel<br>核心功能接口]
I --> J[Namespaces<br>• PID<br>• NET<br>• MNT]
I --> K[Cgroups<br>• cpu<br>• memory<br>• blkio]
I --> L[Union FS<br>• OverlayFS<br>存储驱动]
I --> M[Netfilter/iptables<br>网络规则]
end

B --> N[Image Registry<br>• Docker Hub<br>• Harbor]
E --> L
D --> M

核心组件功能说明

7.png

1. Docker Client/CLI

1
2
# 用户命令行操作(例如创建容器)
docker run -d nginx:alpine
  • 作用:用户与 Docker 交互的入口
  • 通信方式:通过 /var/run/docker.sock UNIX socket 或 TCP 端口连接 dockerd

2. Docker Daemon (dockerd)

1
2
3
4
5
6
7
8
flowchart TB
A[用户请求入口] -->|REST API| B(对象管理)
B --> C[镜像管理]
B --> D[网络管理]
B --> E[卷管理]
B --> F[任务调度]
F -->|gRPC| G[containerd]

  • 核心职责:
    • 提供 REST API 服务端 (监听 /var/run/docker.sock)
    • 管理 Docker 对象生命周期:镜像/容器/网络/卷
    • 插件体系管理:网络驱动(CNM)、存储驱动、日志驱动
    • 用户命令行接口(CLI)的后端执行器

3. containerd (核心容器运行时)

角色:容器生命周期总控中心

功能模块 说明
任务分发 接收dockerd指令,调度容器操作
镜像管理 从仓库拉取镜像,管理本地存储(/var/lib/containerd)
存储插件 通过Snapshotter管理文件系统快照(支持overlayfs、zfs等)
事件监听 暴露容器状态变更事件(gRPC接口)
指标采集 收集cgroups资源使用数据
1
2
3
4
5
6
flowchart TD
A[镜像管理] -->|拉取/存储元数据| Images
B[容器生命周期] -->|启停/暂停/删除| Containers
C[存储管理] -->|快照/overlay2| Snapshots
D[网络接口] -->|抽象层| CNI_Plugins
E[事件分发] -->|状态变更通知| Docker/CRI
  • 核心职责

    • 容器生命周期管理:create/start/stop/delete
    • 镜像全周期管理:pull/push/build
      • 关键组件:
        • Content Store:存储镜像层 blob(/var/lib/containerd/io.containerd.content.v1.content
        • Snapshotter:构建容器读写层(支持 overlayfs、zfs 等)
        • Diff Service:计算镜像层差异
    • 存储快照管理:(操作 /var/lib/containerd)
      • 特性支持:
        • volume-driver 插件扩展(nfs、ceph)
        • Volume 生命周期管理(create/remove)
        • SELinux 上下文自动标记
    • 网络资源编排
      • 实现路径:
        1. 调用 CNI 插件创建网卡
        2. 通过 PublishPort 暴露端口
        3. 整合网络策略(如 Calico、Flannel)
    • 实现 CRI (Container Runtime Interface) 标准
  • 关键路径/var/lib/containerd

  • 通信协议:gRPC(高性能二进制通信)

4. containerd-shim (隔离层)

角色:容器进程守护代理

  • 核心职责:
    • 进程托管:成为容器进程的实际父进程
    • 信号中继:转发 SIGTERM/SIGKILL 等信号
    • IO 传递:连接容器 stdio 和日志驱动
    • 状态维持:保留退出容器状态直至被清理

存在意义:解决父进程管理守护进程解耦问题

1
2
3
4
flowchart LR
A[进程托管] -->|防止僵尸进程| B[孤儿进程回收]
C[信号中继] -->|转发SIGTERM信号| D[容器优雅退出]
E[无守护进程依赖] -->|dockerd重启不影响容器| F[容器持续运行]
  • 关键机制:

    • 每启动一个容器生成独立 shim 进程
    • 转发 IO 流(STDIN/STDOUT/STDERR 到 containerd 日志驱动)
  • 关键特征

    1
    2
    3
    # 每个容器对应独立 shim 进程
    $ pstree -p | grep shim
    containerd(1001)───containerd-shim(2031)───nginx(2040)

5. runC:轻量级容器执行引擎

角色:OCI 标准实现者

  • 核心职责:
    • 实现 OCI 运行时规范 (runtime-spec)
    • 调用 Linux 内核接口创建容器环境:
      • 命名空间隔离 (namespace)
      • 资源限制 (cgroups v2)
      • 文件系统挂载 (OverlayFS)
      • 安全策略 (AppArmor/seccomp)
    • 执行容器启动进程 (ENTRYPOINT/CMD)

内核交互原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sequenceDiagram
participant runc as runc (容器运行时)
participant kernel as Linux Kernel
participant cgroup as Cgroups
participant seccomp as Seccomp

runc->>kernel: 1. clone(CLONE_NEW*Flags) 创建新命名空间
kernel-->>runc: 返回隔离的进程空间

runc->>cgroup: 2. cgroup_create() 创建控制组
runc->>cgroup: 写入资源限制 (cpu/memory/pids...)

runc->>kernel: 3. pivot_root() 或 chroot() 切换根文件系统
runc->>kernel: 4. mount() 挂载/proc/sys/dev等虚拟文件系统

runc->>seccomp: 5. prctl(PR_SET_SECCOMP) 加载Seccomp规则

runc->>kernel: 6. setuid/setgid 降权至非root用户
runc->>kernel: 7. execve("/bin/sh") 执行容器入口进程
kernel->>seccomp: 进程每次触发系统调用前
seccomp-->>kernel: 检查白名单 → 放行/拦截(SIGSYS)
  • OCI Spec 实现:解析config.json创建符合规范的容器,影响调用顺序的关键配置文件

    runc 的执行步骤由 config.json 决定(通过runc spec生成),核心顺序锁定在:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "process": {
    "args": ["/bin/sh"],
    "user": {"uid": 0, "gid": 0}, // Step6: 权限设置
    "capabilities": {...} // Step3: 能力集
    },
    "root": {"path": "rootfs"}, // Step2: 根文件系统
    "mounts": [...], // Step2: 挂载点
    "linux": {
    "namespaces": [...], // Step1: 命名空间
    "seccomp": {...}, // Step3: Seccomp规则
    "cgroupsPath": "container01" // Step2: Cgroups路径
    }
    }

    必须:文件系统(Mount) → 主机标识(UTS) → 进程间通信(IPC) → 进程树(PID) → 网络(Network) → 用户权限(User)

  • 关键操作runc createrunc start(分离容器初始化与启动)

6. Linux 内核核心组件

技术 作用 容器中查看命令
Namespaces 资源隔离 ls -l /proc/$$/ns
Cgroups v2 资源限制 cat /sys/fs/cgroup/memory.max
OverlayFS 分层文件系统 `mount
Netfilter NAT/端口映射 iptables -t nat -vnL DOCKER

全流程示例:创建容器时发生了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
sequenceDiagram
User->>Docker CLI: docker run nginx
Docker CLI->>Dockerd: POST /containers/create
Dockerd->>containerd: CreateContainer(gRPC)
Note over containerd: 检查镜像缓存
containerd->>runc: bundle create
runc->>Namespaces: clone(CLONE_NEWPID|CLONE_NEWNET...)
runc->>Cgroups: /sys/fs/cgroup/docker/cid
runc->>OverlayFS: mount overlay(/var/lib/docker/overlay2/layers)
runc->>Netfilter: iptables DNAT规则添加
runc->>containerd: 容器进程启动成功(pid=2020)
containerd->>Dockerd: Success(Created)
Docker CLI->>User: Container started

架构演进关键点(2024更新)

  1. 去 Docker 单点化

    1
    2
    3
    4
    timeline
    2017年: docker-containerd 插件
    2021年: containerd 独立运行时
    现在: K8s 可直接调用 containerd(绕过 daemon)
  2. 安全增强

    • Rootless Mode:无 root 权限运行
    • Seccomp/AppArmor:强制访问控制
    1
    docker run --security-opt seccomp=/profile.json ...

问题剖析


问题一:dockerd 与 containerd 在容器/镜像管理上的本质区别

职责边界对比(关键差异表)

功能维度 dockerd 职责 containerd 职责
镜像管理 • 解析 Dockerfile
• 镜像标签语义
• 多架构镜像分发策略
• 镜像分片(blobs)存储
• 内容寻址(SHA256)
• 快照磁盘驱动管理
容器管理 • 端口映射规则
• 卷绑定挂载路径
• 容器别名设置
• 容器运行时配置(OCI spec)
• Linux namespace/cgroups 构建
• 容器状态机转换
通信协议 REST API (HTTP/JSON) gRPC (高性能二进制协议)
数据存储位置 /var/lib/docker/ /var/lib/containerd/
核心价值差异 用户友好抽象层 标准化运行时控制层

▶ 实例解析:docker pull nginx 的执行流

1
2
3
4
5
6
7
8
9
sequenceDiagram
User->>dockerd: docker pull nginx:1.23
dockerd->>Docker_Registry: HTTPS认证 & Manifest请求
Docker_Registry-->>dockerd: 返回manifest(含多层digest)
dockerd->>containerd: PullImage(grpc,digest=docker.io/nginx@sha256:xxx)
containerd->>Registry: 请求具体blobs层
containerd->>/var/lib/containerd: 存储blobs + 创建快照
containerd-->>dockerd: 返回Image对象
dockerd->>/var/lib/docker: 创建镜像标签元数据

问题二:containerd-shim 如何实现运行时解耦

1. 关键机制:双进程孤儿化保护

1
2
3
4
graph BT
pid1[Init进程 PID1] --> shim[containerd-shim]
shim --> container[容器进程]
containerd -- 仅连接 --> shim
  • 进程树原理
    1. containerd 调用 fork() 生成 containerd-shim 子进程
    2. shim 进程调用 runc create 创建容器进程
    3. shim 进程脱离父进程管控
      1
      2
      3
      4
      5
      6
      7
      // 伪代码:shim 自孤儿化
      if (fork() == 0) { // shim 子进程创建容器
      daemon(0, 0); // 脱离终端
      pid = runc_create("nginx");
      } else {
      exit(0); // 父进程终止→shim成孤儿归init托管
      }
  • 结果:shim 进程被 Linux init 进程(PID1)接管,与 containerd 解除父子关系

2. 通信解耦:Unix Socket 长连接

1
2
3
4
flowchart LR
containerd--gRPC连接-->socket
shim[containerd-shim]--监听-->socket
shim--io转发-->日志驱动
  • socket 路径/run/containerd/shim.{version}.sock
  • 存活凭证:即使 containerd 重启, socket 文件保留 → 新 containerd 通过路径自动重连

containerd和containerd-shim的差异:

角色 技术身份 操作模式
containerd gRPC 客户端 发起控制请求
containerd-shim gRPC 服务端 监听并响应

containerd-shim 和 containerd 通过 Socket 的分工协作,实现了:

  1. 稳定与控制分离:shim 作为状态锚点护卫容器进程
  2. 弹性能力:containerd 可热升级/重启不影响业务
  3. 安全边界:Unix Socket 比 TCP 减少 40% 的安全攻击面

3. 核心场景:containerd 重启时的容器保护流程

1
2
3
4
5
6
7
8
9
10
11
12
13
sequenceDiagram
participant User
participant Systemd
participant containerd
participant shim

User->>Systemd: systemctl restart containerd
Systemd->>containerd: SIGTERM
containerd--关闭连接->shim: 但shim进程仍在运行
Systemd->>containerd: 启动新实例
containerd->>文件系统: 扫描 /run/containerd/ 下socket文件
containerd->>shim: 通过既有socket重新连接
shim-->>containerd: 报告容器状态(PID/运行时长)

4. 关键进程状态验证

1
2
3
4
5
6
7
8
9
# 重启 containerd 前后的进程树对比
$ pstree -p before_restart
containerd(1000)───containerd-shim(2000)───nginx(2001)

$ systemctl restart containerd

$ pstree -p after_restart
systemd(1)───containerd-shim(2000)───nginx(2001) # shim被init接管
containerd(3000) # 新进程建立

深度技术原理剖析

为何容器进程不会成为僵尸进程?

  1. shim 的 waitpid 机制
    1
    2
    3
    4
    5
    6
    7
    8
    // containerd-shim 核心处理循环
    for {
    status, err := syscall.Wait4(-1, &rusage, 0) // 等待任何子进程退出
    if err == syscall.ECHILD {
    break // 无子进程时退出
    }
    saveContainerExitCode(status) // 记录容器退出状态
    }
  2. 多级孤儿化保护
    • 容器进程退出 → shim 调用 wait() 回收 → 避免僵尸
    • shim 自身退出 → 由 init 进程回收

IO 流如何保持不中断?

1
2
3
4
5
flowchart LR
container[容器进程] -->|stdout| fifo1[命名管道1]
fifo1 --> shim[shim进程]
shim -->|重定向| fifo2[日志驱动管道]
fifo2 --> containerd[新containerd]
  • 持久化管道/run/containerd/fifo/ 下的命名管道文件保持打开

这种精妙的 进程孤儿化 + Unix Socket重连 机制,实现了容器运行时的热升级与故障恢复能力,是 Docker/containerd 高可用的架构基石。