docker原理剖析

1,117 阅读14分钟

什么是docker

Docker的英文翻译是“搬运工”的意思,他搬运的东西就是我们常说的集装箱Container,Container 里面装的是任意类型的 App,我们的开发人员可以通过 Docker 将App 变成一种标准化的、可移植的、自管理的组件,我们可以在任何主流的操作系统中开发、调试和运行。

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

(docker 架构图) runc 是一个 Linux 命令行工具,用于根据 OCI容器运行时规范 创建和运行容器。 containerd 是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。

从概念上来看 Docker 和我们传统的虚拟机比较类似,只是更加轻量级,更加方便使,Docker 和虚拟机最主要的区别有以下几点:

#虚拟化技术依赖的是物理CPU和内存,是硬件级别的;而我们的 Docker 是构建在操作系统层面的,利用操作系统的容器化技术,所以 Docker 同样的可以运行在虚拟机上面。

#我们知道虚拟机中的系统就是我们常说的操作系统镜像,比较复杂;而 Docker 比较轻量级,我们可以用 Docker 部署一个独立的 Redis,就类似于在虚拟机当中安装一个 Redis 应用,但是我们用 Docker 部署的应用是完全隔离的。

#我们都知道传统的虚拟化技术是通过快照来保存状态的;而 Docker 引入了类似于源码管理的机制,将容器的快照历史版本一一记录下来,切换成本非常之低。

#传统虚拟化技术在构建系统的时候非常复杂;而 Docker 可以通过一个简单的 Dockerfile 文件来构建整个容器,更重要的是 Dockerfile 可以手动编写,这样应用程序开发人员可以通过发布 Dockerfile 来定义应用的环境和依赖,这样对于持续交付非常有利。

虚拟化技术的实现方式

#完全虚拟化技术 通过软件实现对操作系统的资源再分配,比较成熟,完全虚拟化技术:KVM,EXSI,Hyper-V;

#半虚拟化技术 通过代码修改已有的系统,形成一种新的可虚拟化的系统调用硬件资源去安装多个系统,整体速度上相对高一点,半虚拟化代表技术:xen

#轻量级虚拟化 介于完全虚拟化,半虚拟化之间,轻量级虚拟化代表技术:docker

Docker与LXC及cgroup

docker虚拟化技术结构体系最早为LXC(linux container)+AUFS结构组合,docker 0.9.0版本开始引入libcontainer,可以视作LXC的替代品。

LXC也是一种虚拟化的解决方案,跟KVM,XEN,ESXI虚拟化基于硬件层面虚拟化不同,LXC是基于内核级的虚拟化技术,linux操作系统软件服务进程之所以能够相互独立,并且系统能够控制每个服务进程的cpu,内存资源也是得益于LXC容器技术。

Docker虚拟化技术是在LXC技术上进一步的封装,比LXC技术更完善,并且提供了一系列完整的功能。在docker虚拟化技术中,LXC主要负责资源管理,而AUFS主要负责镜像管理,而LXC又包括cgroup,namespace,chroot等组件,并通过cgroup进行资源管理。

从资源管理结构体系上来看,docker,LXC,cgroup三者的关系: Cgroup在最底层落实资源管理,LXC在cgroup上封装了一层,docker又在LXC上封装了一层,要深入掌握docker虚拟化技术,需要了解负责资源管理的cgroup和LXC相关概念和用途。

Cgroup又名control groups,是linux内核提供的一种可以限制,记录,隔离进程组所使用的物理资源(cpu, memory, IO, net)的机制。

Cgroup最初的目标是为资源管理提供一个统一的框架,既整合现有的cpuset等子系统,也为未来开发新的子系统提供接口。现在的cgroup适用于多种应用场景,从单个进程的资源控制,到实现操作系统层次的虚拟化。

Linux container容器技术可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。容器有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平衡有冲突的资源使用需求。

LXC是建立在cgroup基础上,可以理解为:LXC=cgroup + namespace + chroot + veth + 用户态控制脚本。 LXC利用cgroup来提供用户空间的对象,用来保证资源的隔离和对于应用或者系统的资源控制。

典型的linux文件系统由bootfs和rootfs两部分组成: Bootfs(boot file system)主要包含BootLoader和kernel,BootLoader主要是引导加载kernel,当kernel被加载到内存中后bootfs就被umount。 Rootfs(root file system)包含的就是典型linux系统中的/dev, /proc, /bin, /etc等目录和文件。

Docker容器的文件系统最早是建立在Aufs基础上的,Aufs是一种union FS,简单来说就是支持将不同的目录挂载到同一个虚拟文件系统下,并实现一种layer的概念。

Docker虚拟化磁盘文件系统,默认使用devicemapper方式,docker虚拟化目前支持六种文件系统:AUFS, Btrfs,Device mapper,overlayfs,ZFS, VFS等。

AUFS文件系统简介

Aufs将挂载到同一虚拟文件系统下的多个目录分别设置成read-only,read-write以及whiteout-able权限,对read-only目录只能读,而写操作只能实施在read-write目录中。重点在于,写操作是在read-only上的一种增量操作,不影响read-only目录。

当挂载目录的时候要严格按照各目录之间的这种增量关系,将被增量操作的目录优先于在它基础上增量操作的目录挂载,待所有目录挂载结束了,继续挂载一个read-write目录,如此便形成了一种层次结构。

传统的linux加载bootfs时会先将rootfs设为read-only,然后在系统自检之后将rootfs从read-only改为read-write,然后我们就可以在rootfs上进行写和读的操作了。但docker的镜像却不是这样,它在rootfs自检完毕之后并不会把rootfs的read-only改为read-write。而是利用union mount(union FS的一种挂载机制)将一个或多个read-only的rootfs加载到之前read-only的rootfs层之上。

在加载了这么多层的rootfs之后,仍然让它看起来只像是一个文件系统,在docker的体系里把union mount的这些read-only的rootfs叫做docker的镜像。但此时的每一层rootfs都是read-only的,我们此时还不能对其进行操作。当我们创建一个容器,也就是将docker镜像进行实例化,系统会在一层或是多层read-only的rootfs之上分配一层空的read-write的rootfs。

Device Mappber文件系统简介

Devic mapper是linux2.6内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核架构。

device mapper的内核体系架构:

在内核中它通过一个一个模块化的target driver插件实现对IO请求的过滤或者重新定向等工作,当前已经实现的target driver插件包含软raid,软加密,逻辑卷条带, 多路径,镜像,快照等,图中liner, mirror,snapshot,multipath表示的就是这些target driver。Device mapper进一步体现了在linux内核设计中策略和机制分离的原则,将所有与策略相关的工作放到用户空间完成,内核中主要提供完成这些策略所需要的机制。

Device mapper用户空间相关部分主要负责配置具体的策略和控制逻辑,比如逻辑设备和那些物理设备建立映射,怎么建立这些映射关系等等,而具体过滤和重定向IO 请求的工作由内核中相关代码完成。因此整个device mapper机制由两部分组成-内核空间的device mapper驱动,用户空间的device mapper库以及它提供的dmsetup工具。

Overlayfs文件系统简介

OverlayFS是目前使用比较广泛的层次文件系统,是一种类似Aufs的一种堆叠文件系统,于2014年正式合入linux3.18主线内核,overlayFS文件系统,实现简单,而且性能很好,可以充分利用不同或者相同overlay文件系统的Page Cache,具有:上下合并,同名遮盖,写时拷贝等特点。

Docker虚拟化overlay存储驱动利用了很多overlayFS特性来构建和管理镜像与容器的磁盘结构。从docker1.11起,docker也支持overlay2存储驱动,相比于overlay来说,overlay2在inode优化上更加高效。但overlay2驱动只兼容linux kernel 4.0+以上的版本。

OverlayFS加入linux kernel主线后,在linux kernel模块中的名称从overlayfs改名为overlay。在真实使用中,overlayFS代表整个文件系统,而overlay/overlay2表示docker的存储驱动。

在docker虚拟化技术中,overlayFS将一个linux主机中的两个目录组合起来,一个在上,一个在下,对外提供统一的试图。这两个目录就是layer,将两个层组合在一起的技术被称为联合挂载union mount。在overlayFS中,上层的目录被称作upperdir,下层的,目录被称作lowerdir,对外提供的统一试图被称作merged。当需要修改一个文件时,使用copy on write(写时复制)将文件从只读的lowerdir复制到可写的upperdir进行修改,结果也保存在upperdir层。在docker中,底下的只读层就是image,可写层就是container。

如果镜像层和容器层可以有相同的文件,这种情况下,upperdir中的文件覆盖lowerdir中的文件。Docker镜像中的每一层并不对应overlayFS中的层,而是镜像中的每一层对应于/var/lib/docker/overlay中的一个文件夹,文件以该层的UUID命名。然后使用硬链接将下层的文件引用到上层。在一定程度上可以节省磁盘空间。

容器和镜像的层与overlayFS的upperdir, lowerdir以及merged之间的对应关系:

Docker镜像原理剖析

完整的docker镜像可以支撑一个docker容器的运行,在docker容器运行过程中主要提供文件系统数据支撑。Docker镜像是分层结构的,是由多个层级组成,每个层级分别存储各种软件实现某个功能,docker镜像作为docker 中最基本的概念,有以下几个特性:

1.镜像是分层的,每个镜像都由一个或多个镜像组成;

2.可通过在某个镜像加上一定的镜像层得到新镜像;

3.通过编写dockerfile或基于容器commit实现镜像制作;

4.每个镜像层拥有唯一镜像ID,docker引擎默认通过镜像ID来识别镜像;

5.镜像在存储和使用时,共享相同的镜像层,在PULL镜像时,已有的镜像层会自动跳过下载;

6.每个镜像层都是只读,即使启动成容器,也无法对其真正的修改,修改只会作用于最上层的容器层。

从图可以看出,docker容器是一个或多个运行进程,而这些运行进程将占有相应的内存,相应的cpu计算资源,相应的虚拟网络设备以及相应的文件系统资源。Docker容器所占用的文件系统资源,则通过docker镜像的镜像层文件来提供。基于每个镜像的json文件,可以通过解析docker镜像的json文件,获知应该在这个镜像之上运行什么样的进程,应该为进程配置什么样的环境变量,而docker守护进程实现了从静态向动态的转变。

Docker架构

Docker引擎是一个C/S结构的应用,组件如图所示:

1.docker server是一个常驻进程;

2.RestAPI实现了client和server间的交互协议

3.Docker CLI实现容器和镜像的管理,为用户提供统一的操作界面

4.Images为容器提供了统一的软件,文件底层存储

5.Container是docker虚拟化的产物,直接作为生产使用

6.Network为docker 容器提供完整网络通信

7.Volume为docker容器提供额外磁盘,文件存储对象

Docker架构

Docker 使用 C/S (客户端/服务器)体系的架构,Docker 客户端与 Docker 守护进程通信,Docker 守护进程负责构建,运行和分发 Docker 容器。Docker 客户端和守护进程可以在同一个系统上运行,也可以将 Docker 客户端连接到远程 Docker 守护进程。Docker 客户端和守护进程使用 REST API 通过UNIX套接字或网络接口进行通信。

#Docker Damon:dockerd,用来监听 Docker API 的请求和管理 Docker 对象,比如镜像、容器、网络和 Volume。

#Docker Client:docker,docker client 是我们和 Docker 进行交互的最主要的方式方法,比如我们可以通过 docker run 命令来运行一个容器,然后我们的这个 client 会把命令发送给上面的 Docker daemon,让他来做真正事情。

#Docker Registry:用来存储 Docker 镜像的仓库,Docker Hub 是 Docker 官方提供的一个公共仓库,而且 Docker 默认也是从 Docker Hub 上查找镜像的,当然你也可以很方便的运行一个私有仓库,当我们使用 docker pull 或者 docker run 命令时,就会从我们配置的 Docker 镜像仓库中去拉取镜像,使用 docker push 命令时,会将我们构建的镜像推送到对应的镜像仓库中。

#Images:镜像,镜像是一个只读模板,带有创建 Docker 容器的说明,一般来说的,镜像会基于另外的一些基础镜像并加上一些额外的自定义功能。比如,你可以构建一个基于 Centos 的镜像,然后在这个基础镜像上面安装一个 Nginx 服务器,这样就可以构成一个属于我们自己的镜像了。

#Containers:容器,容器是一个镜像的可运行的实例,可以使用 Docker REST API 或者 CLI 来操作容器,容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

#底层技术支持:Namespaces(做隔离)、CGroups(做资源限制)、UnionFS(镜像和容器的分层) the-underlying-technology Docker 底层架构分析。