docker
一、容器相关技术
1.1 Linux Namespace 技术
Linux Namespace 是 Linux Kernel 的一个功能,提供了一种资源隔离方案。每个命名空间都是独立的,对于其他命名空间都是不可见的,为进程分配独立的命令空间就相当于为进程提供独立的运行环境。
要创建新的命名空间,只需要在使用clone或fork调用创建新进程的时候指定相应的flag,Linux Namespace 机制为实现基于容器的虚拟化技术提供了很好的基础。当前Linux一共实现了6种不同类型的Namespace,如表所示。
1.1.1 linux共实现6种namespace
UTS Namespace
CLONE_NEWUTS
主机名与域名
IPC Namespace
CLONE_NEWIPC
信号量、消息队列和共享内存
PID Namespace
CLONE_NEWPID
进程编号
Mount Namespace
CLONE_NEWNS
挂载点(文件系统)
User Namespace
CLONE_NEWUSER
用户和用户组
Network Namespace
CLONE_NEWNET
网络设备、网络栈、端口等
1.1.2 6种namespace介绍
UTS隔离 nodename 和 domainname 这两个系统标识,每个Namespace拥有自己的 hostname,即拥有自己的主机名。主机名可以在一个局域网中标识一台主机,处在该Namespace中的应用进程就仿佛在一个单独的主机中运行。
IPC隔离进程通信,其是Unix/Linux下的一种进程通信方式,包括内存共享、消息队列、信号量等通信方式。隔离IPC就是隔离了这些通信方式,使得不在一个Namespace下的进程不能通过这些方式进行通信。
PID隔离进程ID号,使得不同Namespace中的进程可以有相同ID号。同时每个容器都运行属于自己的init进程(1号进程),在容器外这些进程有不同的PID。
Mount隔离各个进程看到的挂载点视图,执行 mount() 和 umount() 系统调用将只会影响到当前Namespace内的文件系统,而不会影响到其他Namesapce。
User主要是用于隔离用户的用户组ID。宿主机上非root用户映射成命名空间内的root用户,从而不影响该用户在宿主机权限的情况下,在容器中拥有root权限。
Network隔离网络设备、IP地址、端口号等网络资源,让每个容器拥有自己独立的(虚拟的)网络设备,并且容器内的应用可以绑定到自己的端口。
1.2 Linux Cgroups 技术
Linux Cgroups (Control Groups)为资源管理提供了统一的框架,从单个进程的资源控制,到实现操作系统层次的虚拟化,Cgroups适用多种应用场景。它提供了对进程资源的限制、控制和统计能力,这些资源包括内存、CPU、网络等。
1.2.1 cgroups的三个组件
Cgroups中包含三个组件,分别是cgroup、subsystem、hierarchy。
cgroup是对进程分组管理的一种机制,一个cgroup包含一组进程,并可以在这个cgroup上增加Linux subsystem的各种参数配置,将一组进程和一组subsystem关联起来,就是在一个组里进程会使用同样的资源限制。
subsystem是对一组资源控制的模块,每个subsystem会关联到定义了相应限制的cgroup上,并对这个cgroup中的进程做相应的限制和控制。各种subsystem就是各种资源控制器,比如CPU子系统用来控制cgroup中进程能够使用的CPU时间片,内存子系统能够限制cgroup中进程所使用的内存,设备子系统能够控制cgroup中进程对设备的访问权限等。
hierarchy的功能是把一组cgroup串成一个树状的结构,一个这样的树便是一个hierarchy,通过这种树状结构,Cgroups可以做到继承。如一个计算进程通过cgroup1限制了CPU使用率,然后一个存储日志进程还需要限制磁盘IO,可以创建cgroup2限制磁盘IO,同时继承cgroup1达到限制CPU的目的。
1.2.2 三个组件的关系
三个组件关系大致如图所示。
容器包含了一个或多个cgroup,相互关联的 cgroup 以树状组织成层级结构(hirearchy),不同的层级结构挂载着不同的子系统(subsystem)。其中,subsystem代表cgroups机制可控制的资源种类。
1.3 UFS/AUFS 技术
1.3.1 UFS
Union File System,简称 UnionFS,是一种为Linux、FreeBSD和NetBSD操作系统设计的文件系统服务。它使用branch把不同文件系统的文件和目录“透明地”覆盖,形成一个一致的文件系统。这些branch或者是只读的,或者是可读写的,当对这个虚拟后的联合文件系统进行读操作时是读原来的文件,进行写操作的时候,系统在不影响源文件的情况下写到一个新的文件中。
1.3.2 写时复制
为了减少未修改资源复制带来的消耗,其用到了一个重要的资源管理技术——写时复制。
它的思想是,如果一个资源是重复的,但没有任何修改,这时并不需要立即创建一个新的资源,这个资源是共享的。当第一次进行写操作时才创建新资源。
对于Docker容器来说,也就是对原镜像进行修改的时候才创建新文件。这样可以实现几个容器共用一个镜像,只有需要改变镜像的时候才为容器单独复制镜像,大大提高了资源使用率和效率。使用联合文件系统能很好的解决容器镜像冗余的问题。
1.3.3 AUFS
AUFS (AnotherUnionFS) 是一种重写的 Union FS, 简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。
除了挂载不同目录外,AUFS 支持为每一个成员目录设定'readonly'、'readwrite' 权限, 同时 AUFS 里有一个类似分层的概念, 也就是利用了写时复制技术,对 readonly 权限的 branch 可以逻辑上进行修改。此特性显著提高了容器文件系统的性能。
AUFS具有快速启动容器、高效利用存储和内存的优点。
1.4 网络虚拟化技术
1.4.1 虚拟网络设备
Linux Veth: Veth是成对出现的虚拟网络设备, 发送到Veth一端虚拟设备的请求会从另一端的虚拟设备中发出。
Linux Bridge:Bridge虚拟设备是用来桥接的网络设备,它可以说是虚拟化的交换机,可以连接不同的网络设备。
1.4.2 路由表
路由表是Linux内核的一个模块, 通过定义路由表来决定在某个网络namespace中包的流向, 从而定义请求会到哪个网络设备上。
1.4.3 iptables
iptables和netfilter共同组成Linux下的防火墙,其作用是管理包的流动和转送,过滤网络流量。iptables用来定义过滤规则,在网络包传输的各个阶段可以使用不同的策略对包进行加工、传送或丢弃。为容器添加网络功能,需要给它配置网络流量的转送规则。
二、架构设计
三、功能设计
3.1 进程隔离
3.1.1 隔离实现
容器的创建首先要实现进程的的隔离,也就是父进程通过fork创建子进程时,使用namespaces技术,即执行fork时指定CLONE_NEWNS、CLONE_NEWUTS、CLONE_NEWIPC等6个参数标志实现进程命名空间的隔离。
一旦传入这些参数标志,Linux将会为子进程创建新的命名空间,从而保证子进程与父进程和宿主机其他进程使用隔离的环境。
3.1.2 proc文件系统
/proc 文件系统是由内核提供的,只包含了系统运行时的信息,比如系统的内存、mount设备信息、一些硬件配置等情况,且只存在于内存中,而不占用硬盘空间。
这个目录下的文件以进程的 PID为子目录名,子目录下存储的是对应进程的运行信息。
其中/proc/self代表进程自己。它以文件系统的形式为访问内核数据的操作提供接口。
我们的容器引擎在创建容器的时候,也需要先挂载这个文件系统来对运行的进程进行管理。
3.1.3 基本构建架构如图
构建一个与宿主机进程运行环境隔离的容器,基本构建架构如图
在执行run命令后,我们会执行相应的namespace标志配置,接着使用相应配置创建子进程,子进程是使用CLONE_NEWPID等6个标志创建的,也就是完成了进程隔离,这个子进程就是容器进程了。
此时还是不够的,这个子进程要继续使用户指定的程序得到执行,通过exec()函数调用就可以替换当前进程的情况下运行用户指定的程序,当用户指定程序运行起来,容器的创建也就完成了。
3.1.4 核心代码
3.2 资源配置
主要通过cgroups对容器资源进行限制。它在容器进程创建完成后才对其进行资源配置,最终使得容器进程处于资源限制的状态。
3.2.1 分为两个模块
分为cgroup管理模块和subsystem实例模块。
如果创建带资源限制的容器,就要先创建Subsystem实例,将每个Subsystem对应的hierarchy上创建配置cgroup,在容器创建出来初始化之后,将容器的进程加到各Subsystem挂载的cgroup中,从而完成资源限制的目的。
两个模块关系:
这样做能方便引擎对资源限制种类的拓展,当有新的资源需要限制的时候,只需要新增一个subsystem实例即可。
3.2.2 容器资源限制结构
3.3 镜像构建与管理
3.3.1 rootfs
Linux操作系统内核启动时,内核首先会挂载一个只读的rootfs,当其完整性得到检测之后,才决定是否将其切换为读写模式,或者最后在rootfs之上另行挂载一种文件系统并忽略rootfs。
3.3.2 镜像作为rootfs
使用镜像作为rootfs。作为容器在启动时其内部进程可见的文件系统视角,也是容器的根目录。
初始镜像应含有容器所需要的系统文件、工具、容器文件等。
但是在镜像挂载完毕后,我们不把容器的文件系统设为读写模式,而是利用Union Mount的技术,在这个只读的镜像之上再挂载一个读写的文件系统(即writeLayer),挂载时该读写文件系统内空无一物。对容器的修改也会在writeLayer中进行,不会影响到镜像,还可以将修改后的容器打包成新的镜像使用。
只读镜像(rootfs)功能的实现,是通过AUFS文件系统实现的,引擎使用AUFS技术将读写层writer layer和镜像都挂载到一个mnt目录下,然后把这个mnt目录作为容器启动的根目录。最终的分层文件系统如图
首先,就要找一个最初的rootfs,可以选用Busybox。
3.3.3 busybox
Busybox是Linux系统的标准工具箱,它包含了许多强大复杂的工具,它采用了与linux内核配置菜单类似的配置菜单,提供了非常多在UNIX环境下经常用的命令,可以说其提供了一个非常完整而且小巧的系统。
busybox在这里也可以称为初始镜像。
3.3.4 使用AUFS技术挂载到mnt目录
之后使用AUFS技术将busybox和write layer挂载到mnt目录下。
最后改变容器当前的root文件系统为mnt目录,这个目录也作为容器最终的运行目录。
改变容器当前的root文件系统可通过pivot_root系统调用来实现,该调用可以给容器指定一个初始化后工作的目录,这样容器的文件系统就创建完成了。
3.3.5 镜像打包
打包只要将运行起来的容器的/mnt目录打包,并存到镜像仓库就行了,在需要的时候可以从仓库中取出自己打包的镜像使用。
使用指定的打包镜像代替最初的busybox就可以。
3.4 数据挂载
主要是获取宿主机要挂载的数据卷目录和容器中要挂载的目录,在容器文件系统中创建挂载点,然后将宿主机数据卷目录挂载到容器挂载点。
在退出的时候卸载这个数据卷的挂载点,这样在容器运行过程中,对该数据卷的增删改在退出后仍不会删除。
3.5 容器日志
略
3.6 容器网络
3.6.1 虚拟网络结构
3.6.2 抽象出网络端点与网络
网络端点
网络端点中主要包含连接到网络的一些信息,如ip地址、Veth设备、所连接容器的网络信息等。
网络模型
抽象的网络模型,主要信息是这个网络的网段。
网络驱动
网络驱动是网络功能中的一个组件,不同的驱动对网络的创建、连接、删除的策略不同。
这里只实现桥接模式的网络驱动,抽象接口如下
3.6.3 容器ip地址分配
一台主机上可以启动很多容器, 正常情况下每个容器都会有属于自己的ip地址,所以需要有一套机制来管理容器地址的分配与释放。
由于需要给容器分配地址, 显然需要在机器上保存哪些ip地址已经被分配, 哪些没有分配, 可以用bitmap来做这个事情。
ip bitmap
其中AllocatorPath 为这个文件存储的位置,Subnets为每个网段的ip分配位图,这里用字符串来存储。
需要完成分配信息持久化、从宿主机加载虚拟网络配置信息的功能。这个就是对配置文件的读写。最重要的就是ip地址的分配与释放。
3.6.4 容器分配网络
容器连接网络要在创建时完成,通过IPAM分配ip地址,通过BridgeDriver桥接网络驱动来配置网络连接端点,其时序图如图
四、存储系统
busybox
作为rootfs存在,是基础镜像,也是一个容器中的只读层。
containers
容器信息的存储目录,包括容器的名字、运行状态等信息。
images
本地镜像仓库。
可以将容器打包成镜像存储在这里,也可以从里面指定镜像作为rootfs层来运行容器。
mnt
使用AUFS技术的挂载文件夹,将只读层(基础镜像)和读写层(writeLayer)挂载在此处,作为容器的实际运行目录。
writerLayer
可写层目录
容器中的可写层。所有对基础镜像的修改和新增数据都会保存在该目录中。
ipam
记录容器网络中ip的分配情况,用于给连接虚拟网络的容器分配ip。
network
记录创建的容器网络信息。
最后更新于
这有帮助吗?