docker 引擎发展历史

标签:Docker首次发布:2023-11-09最近修改:2026-06-21

现代 Docker 引擎由如下主要的组件构成:Docker 客户端(Docker Client)、Docker 守护进程(Docker daemon)、containerd 以及 runc。它们共同负责容器的创建和运行。下面是 Docker 引擎的发展过程:

首发版本

Docker 首次发布时,Docker 引擎由两个核心组件构成:LXCDocker daemon

其中,Linux 容器(LXC)是第一个、最完整的 Linux 容器管理器的实现方案。2008 年,通过将 Cgroups 的资源管理能力和 Linux Namespace 的视图隔离能力组合在一起,LXC 完整的容器技术出现在 Linux 内核中,并且可以在单个 Linux 内核上运行而无需任何补丁。而 Docker daemon 是单一的二进制文件,包含诸如 Docker 客户端、Docker API、容器运行时、镜像构建等。

下图阐释了在 Docker 旧版本中,Docker daemon、LXC 和操作系统之间的交互关系。

flowchart LR
    Docker["Docker Daemon"]
    LXC["LXC"]
    Docker --> LXC

    subgraph Linux["Linux Kernel"]
        direction TB   %% 内部仍可保持上下排列
        NS["Namespaces"]
        CG["CGroups"]
        CAP["Capabilities"]
        AUFS["AUFS"]
        NET["veth + Bridge + iptables"]
    end

    LXC --> NS
    LXC --> CG
    LXC --> CAP
    LXC --> AUFS
    LXC --> NET

不过,该架构依赖于 LXC,存在以下问题:

  • 依赖于外部工具,对 Docker 来说,存在着巨大的生存风险

  • LXC 使得 Docker 无法实现跨平台(只能在 Linux 平台上使用)

Docker Daemon 的“大而全”也带来了严重的问题:

  • 版本更新与功能扩展较难

  • 运行较慢,带来性能问题

  • Docker Daemon 运行出现问题,会直接影响容器的运行,不符合软件哲学

Docker 0.9 版本

Docker 公司开发了名为 Libcontainer 的自研工具,用于替代 LXC。Libcontainer 的目标是成为与平台无关的工具,可基于不同内核为 Docker 上层提供必要的容器交互功能。在 Docker 0.9 版本中,Libcontainer 取代 LXC 成为默认的执行驱动。

Docker 1.11 版本

2017 年 7 月 OCI 基金会发布了两个规范(镜像规范与容器运行时规范)的 OCI1.0 版本。2016 年底发布的 Docker1.11 版本基本遵循了 OCI1.0 版本。

从 Docker1.11 版本开始,Docker Daemon 中不再包含任何容器运行时代码,而是将容器运行时单独剥离了出来,形成了 Runc 项目。

目前 Docker 引擎的架构示意图如下图所示,图中有简要的描述。

graph TD
    A["Docker client<br>(Docker 命令 CLI)"]
    B["Docker daemon<br>(API 和其他特性)"]
    C["containerd<br>(容器Supervisor<br>start\|stop\|pause..)"]
    
    subgraph Runtime_Layer ["容器运行时层"]
        direction TB
        D1["shim<br>(启动无守护进程容器)"]
        D2["shim<br>(启动无守护进程容器)"]
        D3["shim<br>(启动无守护进程容器)"]
        D4["shim<br>(启动无守护进程容器)"]
        
        R1["runc<br>(容器运行时<br>内核原语接口)"]
        R2["runc<br>(容器运行时<br>内核原语接口)"]
        R3["runc<br>(容器运行时<br>内核原语接口)"]
        R4["runc<br>(容器运行时<br>内核原语接口)"]
    end
    
    C1["运行容器"]
    C2["运行容器"]
    C3["运行容器"]
    C4["运行容器"]

    %% 连线关系
    A <--> B
    B <--> C
    C --> D1 & D2 & D3 & D4
    
    D1 --> R1
    D2 --> R2
    D3 --> R3
    D4 --> R4
    
    R1 --> C1
    R2 --> C2
    R3 --> C3
    R4 --> C4

    %% 样式设定 (模仿原图配色)
    style A fill:#1a1a1a,stroke:#333,stroke-width:2px,color:#fff
    style B fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style C fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    
    style D1 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style D2 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style D3 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style D4 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    
    style R1 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style R2 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style R3 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style R4 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    
    style C1 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style C2 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style C3 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
    style C4 fill:#5ba3cf,stroke:#4a8fb5,stroke-width:2px,color:#fff
  • Docker Client:Docker 客户端,Docker 引擎提供的 CLI 工具,用于用户向 Docker 提交命令请求。

  • Dockerd:即 Docker Daemon。在现代 Dockerd 中的主要包含的功能有镜像构建、镜像管理、REST API、核心网络及编排等。其通过 gRPC 与 Containerd 进行通信。

  • runc :只有一个作用——创建容器。不过它是一个 CLI 包装器,实质上就是一个独立的容器运行时工具。因此直接下载它或基于源码编译二进制文件,即可拥有一个全功能的 runc。有时也将 runc 所在的那一层称为“OCI 层”。

  • containerd:在对 Docker daemon 的功能进行拆解后,所有的容器执行逻辑被重构到一个新的名为 containerd 的工具中。它的主要任务是容器的生命周期管理——start | stop | pause | rm······

    Docker 引擎技术栈中,containerd 位于 daemon 和 runc 所在的 OCI 层之间。containerd 在 Linux 和 Windows 中以 daemon 的方式运行,从 1.11 版本之后 Docker 就开始在 Linux 上使用它。如前所述,containerd 最初被设计为轻量级的小型工具,仅用于容器的生命周期管理。然而,随着时间的推移,它被赋予了更多的功能,比如镜像管理。containerd 是由 Docker 公司开发的,并捐献给了云原生计算基金会(Cloud Native Computing Foundation, CNCF)。

  • shim:实现无 daemon 的容器(用于将运行中的容器与 daemon 解耦,以便进行 daemon 升级等操作)不可或缺的工具。前面提到,containerd 指挥 runc 来创建新容器。事实上,每次创建容器时它都会 fork 一个新的 runc 实例。不过,一旦容器创建完毕,对应的 runc 进程就会退出。因此,即使运行上百个容器,也无须保持上百个运行中的 runc 实例。

    一旦容器进程的父进程 runc 退出,相关联的 containerd-shim 进程就会成为容器的父进程。作为容器的父进程,shim 的部分职责如下。

    • 保持所有 STDIN 和 STDOUT 流是开启状态,从而当 daemon 重启的时候,容器不会因为管道(pipe)的关闭而终止。
    • 将容器的退出状态反馈给 daemon。