容器是一种沙盒技术,主要目的是为了将应用运行在其中,与外界隔离。与虚拟机技术不同,容器技术并不会为每个容器实例启动一个操作系统,而是在同一台宿主机中共享同一个操作系统。容器本质上讲就是运行在操作系统上的一个进程,只不过加入了对资源的隔离和限制。
Linux内核提供的Namespaces、Cgroup技术构成了容器的基石,Namespaces实现资源隔离,Cgroup完成资源限制。容器的实现原理是把系统中为同一个业务目标服务的相关进程合成一组,放在同一个namespace命名空间中,每个namespace可以拥有自己独立的主机名、进程ID系统、IPC、网络、文件系统、用户等资源,然后通过Cgroup技术限制进程能使用的CPU、内存等资源。Docker是容器化技术的具体技术实现之一,也是依赖Namespaces、Cgroup来实现容器资源的隔离与限制。
1.1 Linux NameSpaces
Linux Namespaces 是由 Linux 内核提供的,用于进程间资源隔离的一种技术。namespace将全局的系统资源包装在一个抽象视图里,让进程(看起来)拥有独立的全局资源实例。 Linux 提供了多种类型的namespace用于隔离不同类型的资源。Linux Namespaces是容器化技术的基石,docker就是基于namespace来实现容器工作空间的隔离。当我们运行一个docker容器时,docker会为该容器创建一组namespace,这些namespace提供了一个隔离层,进程只能看到同一namespace下的资源。如下图所示,宿主机的两个进程(systemd和crond)的namespace是相同的,而docker容器对应的进程的namespace明显是新的一组namespace,也就是说该docker容器看到的是独立于宿主机的资源视图。
1.2 Linux Cgroups
Cgroups(control groups) 是Linux内核的一个功能,用来限制一个进程组的资源(如CPU、内存、磁盘输入输出等)。在Linux中,Cgroups向用户暴露出来的接口是文件系统,即它以文件和目录的方式组织在操作系统的/sys/fs/cgroup路径下,可使用mount指令显示cgroups各个子系统挂载的路径。
如上图所示,在/sys/fs/cgroup下面有很多诸如blkio、menory、cpu、cpuset这样的子目录,也叫cgroups子系统,这些都是当前机器可以被cgroups限制的资源种类。而在每个子系统对应的文件目录内,我们可以看到各类配置文件。比如在CPU子系统对应的文件目录中就包含有如下几个文件:
tasks文件:配置着受该cgroups控制的进程ID,即属于该进程组的进程ID;
cpu.cfs_period_us和cpu.cfs_quota_us文件:cfs_period_us和cfs_quota_us这两个参数组合使用,可用于限制进程在长度为cfs_period的周期内只能被分配到总量为cfs_quota的cpu时间,比如cfs_period_us=10000 & cfs_quota_us=20000 则表示该进程组最多只能使用0.5核cpu,如果cfs_period_us=10000 & cfs_quota_us=20000 则表示该进程组最多只能使用2核cpu。
cpu.shares:cpu.shares以相对比例限制cgroup的cpu。例如:在 cgroup 中将 cpu.shares 设定为 2 的任务可使用的 CPU 时间是在 cgroup 中将 cpu.shares 设定为 1 的任务可使用的 CPU 时间的两倍。
可以通过在/sys/fs/cgroup文件夹创建新目录的方式来创建一个新的CPU控制组
例如:mkdir -p /sys/fs/cgroup/cpu/testGroup/group1
如图所示,在cpu子系统目录下创建完新目录之后就会自动生成对应的配置文件,在该目录下可做类似如下操作来限制对应的进程
- echo 21770 > tasks命令将21770这个进程加入到该控制组中控制;
- echo 10000 > cpu.cfs_period_us和echo 20000 > cpu.cfs_quota_us这两个命令来设置该控制组的进程最多能使用2核的cpu;
这里值得注意的点是,控制组之间是有层级关系的,上图mkdir -p /sys/fs/cgroup/cpu/testGroup/group1这个命令实际上是创建了2个控制组testGroup和group1,在这两个目录下都有对应的配置文件,而group1控制组会默认继承testGroup的属性。
Cgroups总共有以下几个子系统
blkio:这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
cpu :这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
cpuacct :这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
cpuset:这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
devices:这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
freezer:这个子系统挂起或者恢复 cgroup 中的任务。
memory:这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
net_cls:这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
ns:名称空间子系统,提供ns_cgroup_clone函数,支持基于已有的cgroup创建新的cgroup。
1.3 RootFS与UnionFS
rootfs 即根文件系统,是系统内核启动后挂载的第一个文件系统,包含着操作系统所必需的文件、配置和目录(比如 bin、dev、etc、home、lib等目录及文件)。Linux的系统内核和系统文件是分开的,同一台机器上的容器共享同一个宿主机的内核,但每个容器有着属于自己的文件系统。基于mount namespace的技术我们可以让容器有属于自己的文件系统,为了让容器看到的根目录更加真实,一般会为容器的根目录挂载一个完整操作系统的文件系统,而这个rootfs文件系统其实就是容器镜像的具体内容。比如,在基于Ubuntu镜像启动的容器中执行 ls / 看到的内容就是ubuntu系统根目录的文件。
制作容器镜像其实就是制作rootfs的过程,由于rootfs离打包的不仅仅是应用,而是整个操作系统的文件和目录,这意味着,应用以及它运行所依赖的环境都被打包封装到了容器镜像里面。这就赋予了容器所谓的一致性:在任何机器上,用户只要解压打包好的容器镜像,这个应用运行所需的完整的执行环境就能重现。
Docker使用了UnionFS联合文件系统的技术,在镜像的设计中引入了层(layer)的概念,支持以增量的方式来制作容器镜像,比如我们可以基于ubuntu镜像来制作nginx镜像,使用nginx镜像来制作基于nginx的业务应用镜像。UnionFS联合文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。采用UnionFS分层制作容器镜像最大的好处就是共享资源,很多镜像都是从相同的base镜像构建而来,而宿主机的磁盘只需保留一份base镜像,大大节省了容器镜像的占用空间。