Docker最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC
,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。
Docker 本质就是宿主机的一个进程,Docker 是通过 namespace
实现资源隔离,通过cgroup
实现资源限制,通过写时复制技术(copy-on-write)实现了高效的文件操作。
传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统。虚拟机系统看到的环境是可限制的,也是彼此隔离的。 这种直接的做法实现了对资源最完整的封装,但很多时候往往意味着系统资源的浪费。 例如,以宿主机和虚拟机系统都为 Linux 系统为例,虚拟机中运行的应用其实可以利用宿主机系统中的运行环境。
Docker架构
Docker 使用 C/S (客户端/服务器)体系的架构,Docker 客户端与 Docker 守护进程(Dockerd)通信,Docker 守护进程负责构建,运行和分发 Docker 容器。
Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息。
Docker 客户端则为用户提供一系列可执行命令,使用 REST API 通过UNIX套接字或网络接口实现跟 Docker 守护进程交互。
Namespaces
命名空间 (namespaces) 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。这样每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响。
Docker 在 Linux 上使用以下几个命名空间(上面说的各个方面):
- pid namespace:用于进程隔离(PID:进程ID)
- net namespace:管理网络接口(NET:网络)
- ipc namespace:管理对 IPC 资源的访问(IPC:进程间通信(信号量、消息队列和共享内存))
- mnt namespace:管理文件系统挂载点(MNT:挂载)
- uts namespace:隔离主机名和域名
- user namespace:隔离用户和用户组(3.8以后的内核才支持)
CGroups
我们通过 Linux 的 namespaces 技术为新创建的进程隔离了文件系统、网络、进程等资源,但是 namespaces 并不能够为我们提供物理资源上的隔离,比如 CPU、内存、IO或者网络带宽等,所以如果我们运行多个容器的话,则容器之间就会抢占资源互相影响了,所以对容器资源的使用进行限制就非常重要了,而 Control Groups(CGroups)技术就能够隔离宿主机上的物理资源。CGroups 由 7 个子系统组成:分别是 cpuset、cpu、cpuacct、blkio、devices、freezer、memory,不同类型资源的分配和管理是由各个 CGroup 子系统负责完成的。
在 CGroup 中,所有的任务就是一个系统的一个进程,而 CGroup 就是一组按照某种标准划分的进程,在 CGroup 这种机制中,所有的资源控制都是以 CGroup 作为单位实现的,每一个进程都可以随时加入一个 CGroup 也可以随时退出一个 CGroup。
CGroup 具有以下几个特点:
- CGroup 的 API 以一个伪文件系统(/sys/fs/cgroup/)的实现方式,用户的程序可以通过文件系统实现 CGroup的组件管理
- CGroup的组件管理操作单元可以细粒度到线程级别,用户可以创建和销毁 CGroup,从而实现资源载分配和再利用
- 所有资源管理的功能都以子系统(cpu、cpuset 这些)的方式实现,接口统一子任务创建之初与其父任务处于同一个CGroup 的控制组
另外 CGroup 具有四大功能:
-
资源限制:可以对任务使用的资源总额进行限制
-
优先级分配:通过分配的 cpu 时间片数量以及磁盘 IO 带宽大小等,实际上相当于控制了任务运行优先级
-
资源统计:可以统计系统的资源使用量,如 cpu 时长,内存用量等
-
任务控制:cgroup 可以对任务执行挂起、恢复等操作
UnionFS
Linux 的命名空间和控制组分别解决了不同资源隔离的问题,前者解决了进程、网络以及文件系统的隔离,后者实现了 CPU、内存等资源的隔离,但是在 Docker 中还有另一个非常重要的问题需要解决 - 也就是镜像。
联合文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。
Docker 使用存储驱动程序来管理镜像层和可写容器层的内容,每个存储驱动程序的处理方式不同,但是所有的驱动成都使用可堆叠的镜像层和写时复制(Cow)策略,这些驱动程序管理的这些层其实就是 UnionFS(联合文件系统),现在 Docker 主要支持的存储驱动有 aufs、devicemapper、overlay、overlay2、zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成为了推荐的存储驱动。
如何选择存储驱动:
对于 Docker 如何选择一个合适的存储驱动程序,可以查看官方文档 Docker storage drivers。
Copy-on-write:
写时复制是一种共享和复制文件的策略,可以最大程度地提高效率,如果文件或目录位于镜像的较低层中,而另一层(包括可写层)需要对其进行读取访问,则它直接使用现有文件即可。另一层第一次需要修改文件时(在构建镜像或运行容器时),将文件复制到该层并进行修改。这样可以将 I/O 和每个后续层的大小最小化。
下面显示了基于 Ubuntu 15.04 镜像运行的容器层的结构:
容器和镜像之间的主要区别就是容器在镜像顶部由一个可写层,在容器中的所有操作都会存储在这个容器层中,删除容器后,容器层也会被删除,但是镜像不会变化。正因为每个容器都有自己的可写容器层,所有更改都存储在自己的容器层中,所以多个容器之间可以共享同一基础镜像的访问,但仍然具有自己的数据状态。
参考
「真诚赞赏,手留余香」
请我喝杯咖啡?
使用微信扫描二维码完成支付
