前言:最近在读《深入浅出Docker》这本书,发现里面有个章节讲解的比较深入,所以,基于书的内容进行了整理,希望能对docker架构有个比较深入的理解。

概述

docker引擎是用来运行和管理容器的核心软件,通常将其代指为docker或者docker平台,docker引擎由如下主要的构建组成:docker cli、docker daemon、containerd以及runc,共同负责容器的创建和运行。docker引擎的架构示意图如下:

docker daemon实现了docker api,对容器的操作由containerd完成,containerd需要指挥与OCI相兼容的容器运行时来创建容器,默认情况下,docker使用runc来作为其默认的容器运行时,containerd调用runc,并确保docker镜像以OCI bundle的格式传给runc。其中,

  1. OCI(开放容器计划)定义了两个容器相关的规范:
    • 镜像规范
    • 容器运行时规范

runc是OCI容器运行时标准的参考实现,runc实际上是一个轻量级的、针对libcontainer进行了包装的命令行交互工具。runc生来只有一个作用,创建容器。

关于libcontainer:LXC提供了对诸如命名空间(Namespace)和控制组(CGroup)等基础工具的操作能力,是基于linux内核的容器化虚拟技术,docker公司开发了名为libcontainer的自研工具,用于替代LXC,libcontainer的目的是成为与平台无关的工具,可基于不同内核为Docker上层提供必要的容器交互功能。

  1. containerd 的主要任务是容器的生命周期的管理,containerd在linux和windows中以daemon的方式运行,在docker引擎技术栈中,containerd位于daemon和runc所在的OCI层之间,kubernetes也可以通过cri-containerd使用containerd。目前containerd被赋予了更多的功能,比如镜像管理。

  2. shim是实现无daemon的容器不可获取的工具,前面提到containerd指挥runc来创建容器,事实上,每次创建容器时都会fork一个新的runc实例,不过,一旦容器创建完毕,对应的runc进程就会退出,因此即使运行上百个容器,也无须保持上百个运行中的runc实例。一旦容器进程的父进程runc退出,相关联的containerd-shim进程就会成为容器的父进程,作为容器的父进程,shim的部分职责如下:

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

    • 将容器的退出状态反馈给daemon。

示例-启动新容器

docker container run --name ctrl -it alpine:latest sh

当docker命令行执行上述命令时,docker客户端会将其转换为合适的api格式,并发送到正确的API端点。API是在daemon种实现的,一旦daemon接受了创建新容器的命令,它会向containerd发出调用(daemon使用一种CRUD风格的API,通过gRPC与containerd进行通信)。containerd不负责创建容器,将docker镜像转换为OCI bundle,并让runc基于此创建一个新的容器。然后runc与操作系统内核接口通信,基于所有必要的工具(Namespace、CGroup等)来创建容器,容器进程作为runc的子进程进行启动,启动完毕后,runc将会退出。整个过程如下图:

以上内容基于参考书的内容进行整理而成,如有什么问题,欢迎批评指正
参考:
[1]《深入浅出Docker 》奈吉尔.波尔顿(Nigel Poulton)著 李瑞丰 刘康 译 人民邮电出版社