《基于Kubernetes的容器云平台实战》——1.4 Docker架构及原理

举报
华章计算机 发表于 2019/06/02 00:16:19 2019/06/02
【摘要】 本书摘自《基于Kubernetes的容器云平台实战》——书中的第1章,第1.4.1节作者是陆平、左奇、付光、张晗、赵培、单良

1.4 Docker架构及原理

1.4.1 Docker架构

在解释什么是Docker的时候,已经提到当前版本的Docker引擎实际上由Dockerd、Containerd和RunC等组件构成。其中,Dockerd的功能已经从单纯的容器运行时管理向容器编排管理和集群管理的方向发展,本节主要关注的容器和镜像管理功能将逐渐由Containerd和符合OCI标准的容器运行时,比如RunC来负责。如图1-3所示逻辑架构主要是以Linux系统中的实现模式为例简单表示了这几个组件的交互关系,实际上目前Docker引擎已经可以支持MacOS、Windows和Linux三种平台。

image.png

图1-3 Docker容器架构图

首先,Dockerd是一个守护进程,它可以通过TCP端口以及UNIX Domain Socket两种途径接收客户端的HTTP请求。这些请求以Restful的模式来定义,被作为Docker平台的API来使用。随着Docker平台版本的演进,这个API的版本也在不断升级,但还是保持了兼容性。由于Docker平台不仅能够管理单一宿主机上的容器和镜像,还能够实现容器集群的编排管理,因此这个API中也包含了很多超出容器和镜像管理的部分。当管理员在命令行上执行docker pull、run等命令时,实际上是调用了这些API,并且管理员既可以向本机的Dockerd发送命令,也可以向运行在其他主机上的Dockerd发送命令。这些交互过程既可以是基于HTTP的,也可以是基于HTTPS的。当然,对于像Kubernetes这样的应用来说,刚开始的时候同样是通过这种API来完成容器的创建和启停操作的。

由于这个API中包含了很多超出镜像和容器管理的功能,并不是专门为了单一主机上的容器管理而设计,因此在实际使用过程中逐渐暴露出了很多问题。无论是Kubernetes还是Docker容器管理平台自身,都希望能够有一个高效一致的容器管理API,这样既能减少Docker管理平台自身演进对它的影响,也能够逐步满足诸如高性能、适配不同容器运行时、支持多租户等更多需求,而Containerd就可以被看成是由这种需求催生出的产物。

Containerd对外提供gRPC形式的API,并且API定义中不再包含与集群、编排等相关的功能,但是它也不是简单地将既有的Docker API照搬过来,而是对已有的镜像和容器管理功能进行了更进一步的抽象,细分出内容、快照、差异、镜像、容器、任务等多个更细粒度的服务,并且还为监控管理和多租户实现设计了接口,方便外部应用利用这套API来实现高效和定制的容器管理功能。Containerd在实现这些服务的时候使用了插件机制,各个内部组件之间通过插件机制松散耦合,功能实现非常灵活,同时也为用户定制以实现专用的管理功能留下了较大空间。目前,Kubernetes所需的容器和镜像管理功能就可以由Containerd来提供,但不是通过Containerd标准的API来实现,而是由一个称为CRI的模块将Kubernetes项目中定义好的API通过gRPC接口形式包装后提供的,而这个模块在Containerd中也是以插件形式来实现的。该插件在功能实现时,不仅依靠了Containerd中已有的其他插件,并且以本地调用而不是远程RPC访问的方式完成,体现了该项目插件机制的灵活性。

在老版本的Docker创建容器过程中,容器进程直接作为Docker守护进程的子进程,运行在自己的PID命名空间中。此时容器进程在自己的命名空间中的PID为1,而在Linux操作系统中,PID为1的进程是要负责清理僵尸进程的,但很多应用进程未必考虑到了这一点。另外,在默认情况下,Docker守护进程在停止容器进程时会使用SIGTERM信号,而容器进程有可能错误地忽略了该信号。为了能够正确处理这些操作系统信号相关的特性,在Containerd中实现了一个Containerd-shim程序,每个容器对应一个该程序的实例,由它来保证正确处理各种系统信号。它对外的接口也是gRPC形式的,但是在实现时选择了内存优化的ttRPC软件包。这个程序并没有运行在容器的命名空间中。另外,在docker run的命令行上也可以选择--init选项,指定用户选择的类init功能程序,在容器中作为PID为1的进程执行,此时需要与Containerd的Linux runtime插件的no_shim参数配合使用,否则默认情况下总是会启动Containerd-shim程序。

Containerd以守护进程的形式运行,它完成了对镜像和容器的基本管理功能,提供了对外的API接口。而这些管理功能背后的实际执行者却并不是Containerd。出现这种情况的最主要原因可能是Docker不仅希望容器技术和标准能够在Linux环境下得到推广和应用,还希望它能够在更多的平台上也发挥作用。为此,Docker推动了对容器运行时进行标准化,该标准化过程是在OCI组织下进行的,并且在2017年7月固化了1.0版本的运行时配置。

RunC就是该标准在Linux下的参考实现,Containerd可以按照标准的配置参数来调用它,创建和启动容器进程。这个程序尽管不是以守护进程的方式来执行,但是它会将容器的运行配置和状态数据都记录在json文件中,当Containerd要求RunC执行诸如停止、暂停容器等操作时,它会首先根据容器ID在配置好的路径下找到该json文件,再利用json中记录的容器进程PID以及CGroup文件路径等作为参数调用操作系统API,并完成自己的任务。

由RunC启动的容器进程的标准输入和标准输出被重定向到管道中,并在Containerd中被关联到FIFO文件。这些FIFO文件是在Dockerd中创建的,并通过gRPC请求将它们的路径名传递到Containerd中。

为了让读者对这里描述的Docker架构有一个直观的了解,下面通过启动一个容器后的进程列表来展示Docker架构中各个组件之间的调用关系。

# docker run -d alpine-32:3.6.2 sh -c 'while true; do date; sleep 2; done'

d1c5...

# ps -eHo pid,ppid,cmd | more

    PID  PPID   CMD

...

19367     1   /usr/bin/dockerd

19372 19367     docker-containerd --config /var/run/docker/containerd/containerd.toml

30131 19372       docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/d1c5... -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc -debug

30146 30131         sh -c while true; do date; sleep 2; done

47624 30146           sleep 2

此时,在/var/run/docker/runtime-runc/moby目录下,根据容器ID找到对应子目录,就可以看到state.json文件。而与此对应的容器创建配置保存在/var/run/docker/containerd/daemon/目录下和与Linux runtime插件相关的io.containerd.runtime.v1.linux/moby/子目录下,在此目录下同样有与容器ID相关的子目录,其中的config.json文件保存了OCI标准格式的配置参数。

尽管Containerd能够提供镜像管理的功能,但是在OCI标准中,容器创建过程和镜像之间并没有关联。只要在配置参数中指定已经构建好的根文件系统视图,RunC就能够正常工作。同样,在Containerd的容器服务接口上,客户端可以选择直接传递该配置,而不是要求Containerd自己构建。所以当前Dockerd只利用了已有的镜像管理功能,而在后续演进过程中,这几个组件之间在此功能上如何配合还不清楚。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。