整体概述
总体规划
以Docker为首的容器技术的优势在于:
- 轻量级资源使用、资源隔离。容器在进程级别隔离并使用宿主机的内核,而不需要虚拟化整个操作系统。容器可以使用自己的cpu、memory等。
- 可移植性。跨主机部署,Docker镜像屏蔽了不同OS差异,它可以将环境配置进行抽象和应用打包,保证在不同的硬件机器上都可以部署。
- 以应用为中心、自动构建、版本管理、组件重用、镜像共享。
本项目要解决的核心问题是尽量以较低的复杂度、较少的代码量来模拟出尽可能完整的容器特性,如资源隔离、资源控制、镜像、网络与可用的CLI,一些其他非核心特性,如容器安全、跨平台等基本没有涉及。
系统前景
CNCF将云原生定义为“使用开源软件堆栈进行容器化,其中应用程序的每个部分都打包在自己的容器中,动态编排,以便每个部分都被主动调度和管理,以优化资源利用率和面向微服务的应用程序,以提高应用程序的整体灵活性和可维护性”。
云原生将容器化视为微服务的最佳载体,以Docker为主的容器化技术将逐渐成为云原生时代的Infrastructure。越来越多的科技公司将云计算视为一大战略,未来有更多的开发者将会参与到容器化的二次开发中,而容器技术正是其基石。而本项目可以让开发者在了解Linux基础API之后便可熟悉容器技术的实现细节,更可以一步步地用自己的代码实现它。
需求分析
本项目需求分为两部分,一部分是尽量实现OCI规范,另一部分是实现Docker脱颖而出的核心技术亮点,即镜像技术。前者以runC为参考,后者以Docker为参考,尽量模拟其使用的方式,在实现原理方面在保证实现核心功能的前提下,尽可能降低复杂度,去掉复杂设计,保留最纯粹的功能。
最终开发的目标是可以以容器方式运行起Web应用,以Spring Boot+MySQL+Redis三个应用为例。
第一部分是实现部分OCI标准,即Open Container Initiative。Open Container Initiative制定了操作系统流程和应用程序容器标准的规范,包括运行时规范Runtime Specification和镜像规范Image Specification。
OCI标准中定义了标准容器的5个原则:操作标准化;内容无关;基础设施无关;为自动化量身定制;工业级交付。
下面详细介绍这两种规范。
一个容器的配置信息被放在指定平台的config.json配置文件中,该配置文件详细说明了各个字段,可以指定执行环境,以确保在容器内运行的应用程序在运行时具有一致的环境;OCI标准也定义了对容器的生命周期的公共操作。
一个简化后的config.json如下所示:
{
"ociVersion": "1.0.1-dev",
"process": {
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/"
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "capsule",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
],
"linux": {
"namespaces": [
{
"type": "pid"
},
{
"type": "uts"
},
{
"type": "ipc"
},
{
"type": "network"
},
{
"type": "mount"
}
]
},
}
OCI标准还规定了容器的状态与生命周期:
此外,OCI规范还规定了对容器的操作,如Create(创建容器)、Start(启动容器)、State(查询容器状态)、Kill(杀掉容器进程)、Delete(删除容器)等。
用例图:
容器操作表:
需求名称 | 需求描述 |
---|---|
创建容器 | 用户指定config.json配置文件,提供rootfs后,可以启动容器,容器进入Created状态。 容器具备以下特性: - 资源隔离,包括pid、uts、mnt、network、ipc namespace隔离 - 资源限制,包括cpu share和memory限制 - 以用户提供的rootfs为根文件系统 - 支持容器网络 - mount,挂载宿主机目录至容器 |
查询容器状态 | 可以通过指定容器ID的方式来查询容器状态。 |
查询容器列表 | 以列表形式显示所有容器,包括Created、Running、Stopped的容器。 |
停止容器 | 向容器进程发送信号,如SIGTERM,SIGKILL等 |
删除容器 | 将Stopped的容器销毁,删除容器运行时文件 |
进入容器执行命令 | 可以进入一个已启动的,状态为Created或Running的容器中,执行用户指定的命令 |
查询容器日志 | 打印出容器的stdout和stderr的内容 |
查询容器内进程列表 | 即进入容器执行ps命令 |
生成示例config.json | 生成OCI标准的config.json配置文件,为用户提供参考 |
另一部分是模仿Docker的镜像管理和容器网络,比如说镜像的构建、创建、删除、查询以及镜像仓库等。为了简化功能,将镜像构建、镜像仓库等功能去掉,支持使用Docker导出的打包好的镜像,支持镜像的管理,以及基于镜像的容器管理。
Docker中支持四种容器网络,这里仅实现了最为流行的Bridge类型的容器网络,支持容器间、容器与宿主机间、容器与外部网络间的网络通信。
网络操作表:
这里的网络一般指的是固定在某个网段的一个网桥Bridge。
需求名称 | 需求描述 |
---|---|
创建网络 | 给出一个网段,比如192.168.1.0/24后可以创建一个网桥。在创建容器时可以指定容器的IP地址在此网段分配,并将容器连接到该网桥上 |
查询网络列表 | 显示所有由Capsule管理的网络 |
删除网络 | 删除某个网络 |
查询某个网络 | 按网络名来查询某个网络的详细信息 |
镜像操作表:
需求名称 | 需求描述 |
---|---|
创建/导入镜像 | 将从Docker导出的(docker export)的镜像打成的tar包交给Capsule管理,Capsule会将镜像作为一个read-only layer存储起来。 在创建容器时会生成一个read-write layer,然后将所关联的镜像作为read-only layer,将这两层layer堆叠起来,称为init layer,将其作为容器的rootfs,这样容器就可以自由修改rootfs,在容器销毁后不会对镜像留下任何修改记录。如果希望保留修改记录,则可以使用mount功能,即Docker中的Volume。 |
删除镜像 | 按镜像名删除某个镜像 |
查询镜像列表 | 显示所有由Capsule管理的镜像 |
基于镜像创建容器 | 支持类似于Docker的方式,基于某个镜像来启动容器,所有容器启动参数均由命令行传入,然后将这些参数转为OCI标准,生成config.json,之后的过程与OCI标准的创建容器的过程一致。 |
概要设计
总体结构
Capsule项目主要由两部分组成,一部分是内框中的模块,是符合OCI标准的容器实现,另一部分是外框中的镜像模块,它以内框中的OCI标准容器为内核,在外部包裹了一层镜像管理,使之实现类似于Docker的镜像容器功能。
内框中的模块并非是并列关系,主要分为三个部分:config模块,负责解析config.json配置文件;parent process模块,负责作为父进程向容器子进程传递数据,并对其进行监控;而parent process中较为独立的两个子模块,分别是cgroups模块,负责容器资源限制,和network模块,负责容器网络;init process模块,即容器子进程模块,负责容器内初始化工作,与父进程进行信号与数据交互;而init process模块中包含nsenter模块,它以C语言编写而成,负责将进程创建并加入到新的Namespace中。
体系结构设计
各个模块间的关系如下,以启动容器为例:
- 先由config模块对用户传入的config.json进行解析,获得spec与config对象
- parent process,即Capsule进程,负责启动子进程,即init process。
- 父进程向子进程传递容器参数,子进程通过nsenter模块创建并进入Namespace
- 父进程通过cgroups模块初始化容器的资源限制,通过network模块初始化容器网络
- 同时子进程初始化rootfs、mount、hostname与sysctl,之后等待父进程启动信号
- 父进程向子进程发送启动信号,子进程开始执行用户命令
- 子进程的用户命令执行完毕,父进程检测到,将容器销毁,执行清理工作