Docker是近十年软件工程领域最大的革命。Docker的技术可以完全存驻整个软件的开发、测试、部署和运维等软件生产的方方面面的环节。
提到Docker,也不得不提虚拟化,因为大家谈云计算的时候,也不得不提虚拟化技术。Docker所代表的虚拟化技术和我们以前谈的云计算的虚拟化技术怎么区别呢?以前我们谈云计算的虚拟化技术都是一些譬如Vmware或者是openstack,这些为代表的虚拟化技术都是比较重量级的虚拟化。以vmware举例,vmware大家都知道,他虚拟化的时候是将传统的一台计算机抽象成一个软件,我们在这个软件里面安装操作系统,那么我们需要使用虚拟化的时候,就必须使用诸如vmware提供的这个软件,然后才在这个软件内安装我们所需要的操作系统并配置我们的开发或者测试环境。我们分别安装不同的操作系统来运行我们所需要的环境,比如安装一个Linux系统,都需要给该操作系统分配说需要运行的资源,比如给其分配2GB的内存空间。那么使用了类似vmware虚拟化技术抽象出来的虚拟机,不管是安装了任何的操作系统,我们只要启动了虚拟机或者操作系统,它都会有一块内存区间被占用。因为每个操作系统都有一个概念,这个概念叫;常驻内存。如果说在分布式的集群中,每个系统都有一块常驻内存,这对系统资源也是一种很大的消耗。而以Docker为代表的容器虚拟化技术,它和传统的虚拟化技术是不一样的。
不一样在什么地方?以Docker为代表的容器虚拟化技术,在进行虚拟化的时候是以内核中的特性为支撑来完成虚拟化的。具体来说,路径大家比较熟悉lInux的内容,比如namespace,cgroup等等,这些在Linux中的特性和内容让我们可以实现空间隔离,将内存资源、CPU等进行分配、控制和记录。所有就推出一个结果,如果我们借助这些特性的话,我们可以构建不同的用户空间。这个所谓的用户空间就是UserSpace,
如上图所示,这些UserSpace都是基于Linux的内核之上而构建的,而且可以构建很多的这类空间。而实际上在编写程序的时候,我们的服务器或者说我们的数据库在运行的时候是只关系用户空间的。所以当我们基于Linux内核的这些特性或者说内容,我们就可以在一台服务器上依靠多进程的方式来实现分布式。每个进程就相当于一台轻量级的虚拟机,而且每个进程作为一台机器而言,岁应用程序来说是完全透明的,就是说应用程序本身并感知不到这是通过namespace、cgroup等技术虚拟化出来的一台机器,它和运行在一台真实的物理机器上是完全一样的。而且它的开销几乎可以忽悠不计。这样我们在一台机器上运行多款应用程序或者系统的时候,这是可以榨干硬件潜能的,包括CPU、内存、IO等等。而这些技术最开始的时候就是构建Docker以及让Docker诞生的必要条件和基础。也就是说最开始Docker是基于这些Linux基础技术够构建的,这些技术在传统的Linux内核中统一叫:LXC。后来随着docker的发展,逐渐抛弃了LXC的技术,自己开发,这些技术细节我们到后面单独写笔记介绍。所以我们从生产力的角度讲,以docker为代表的容器虚拟化技术,让我们把虚拟化的轻量级以及云计算或者说计算中心对硬件资源的利用率发挥到了极致。所以从投入和产出的角度讲,我们有足够的理由去选择docker。
说道Docker到底有多好,那就找个参照物来进行对比,虚拟机就是必然的选择。传统的虚拟机在创建的时候速度是很慢的,这个和Docker比较是一目了然的结果;而且传统的虚拟机是基于对硬件的模拟和仿真,所有对硬件资源的调用都需要经过再转发一层才能获取或者说运行,这其中就存在着巨大性能损耗,而docker只需要配置一个参数就好了,性能几乎没有损耗。而且相比较传统的虚拟化技术本身而言,Docker几乎是没有弱点。传统虚拟化技术与容器虚拟化技术的简要技术架构对比如下:
上图所示,传统的虚拟化技术模式,我们环境的运行需要Guets OS还有Hypervisor,所有非常笨重;但是我们看容器虚拟化技术的话,采取共享Host OS的作法,而不需在每一个容器内执行Guest OS,因此建立容器不需要等待操作系统开机时间,不用1分钟或几秒钟就可以启用,远比需要数分钟甚至数十分钟才能开启的传统虚拟机来的快。而且我们的程序直接依赖Bins/Libs,就非常的轻量级,直接基于内核几乎没有任何性能损耗。
虽然我们在这里把vmware等为代表的虚拟化技术叫为传统的虚拟化技术,但是容器技虚拟化技术似乎也并不年轻。早在1982年,Unix系统内建的chroot机制也是一种容器技术。其他如1998年的FreeBSD jails、2005年出现的Solaris Zones和OpenVZ,或像是Windows系统2004年就有的Sandboxie机制都属于在操作系统内建立孤立虚拟执行环境的作法,都可称为是容器虚拟化技术。直到2013年,dotCloud这家公司开源释出了一套将Linux上的LXC容器虚拟化技术标准化的平台Docker,容器技术才火起来的。所以容器技术似乎并不比我称呼为传统虚拟化技术年轻。大家都是老前辈,只不过旧貌换新颜,焕发第二春。
上图就是Docker简要的技术架构图,Docker有自己的引擎,Docker Hub,以及在任何地方使用Docker:Build, Ship and Run 任何应用!因为Docker使用go语言进行编写,go语言具有很大的优势,天生是分布式的。因为我们谈Docker的时候不仅仅是虚拟化,还有分布式。我们使用Docker也不仅仅用来做虚拟化,还做分布式。这是因为第一:go语言天生就是分布式的;第二:go语言不依赖于任何库,任何包,任何平台,所以部署非常容易。
Docker对容器的使用最初是建立在LXC基础之上的,然而LXC存在的问题是难以移动、难以通过标准化的模板制作、重建、复制和移动容器。那么Docker起初是如何在LXC上实现标准化的呢?Docker采用了aufs文件系统来设计一个可以层层堆栈的容器映象文件,将容器内的所有程序(包括应用程序、相关函式库、配置文件),都打包进Docker镜像,并且提供了一个Dockerfile配置文件来记录建立容器过程的每一个步骤包括参数等等。只要在任何支持Docker平台的环境中,就可以从这个镜像来建立出一个一模一样的容器来执行同一个应用程序。如此一来,应用程序等于是可以透过Docker镜像,或甚至只需要Dockerfile,就能将程序执行环境带着走,移动到任何支持Docker的环境中。Docker公司也释出API,可以用来控制所有的容器相关指令,任何人只要使用同一套Docker,就等于有了同一套管理和建立容器的方法,也就等同于将容器运用标准化了。
Docker的文件系统非常重要,其属于联合文件系统的一种。联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite severaldirectories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。AUFS (AnotherUnionFS) 是一种 Union FS, 简单来说就是支持将不同目录挂载到同一个虚拟文件系统下(unite severaldirectories into a single virtual filesystem)的文件系统, 更进一步地, AUFS支持为每一个成员目录(AKA branch)设定‘readonly‘, ‘readwrite‘ 和 ‘whiteout-able‘ 权限, 即允许同时存在read-only和read-wirte目录,同时AUFS将多个不同目录的内容合并在一起。AUFS将只读部分定义为Image,那么什么是Image?Image我们可以理解为在其实就是一种文件系统,这种文件系统定义了我们要运行的程序需要什么样的操作系统,需要什么样的软件支持。Image实际被Docker运行的时候,Docker就会自动的帮助我们提供这样的操作系统和软件运行支持。所提供的操作系统当然也是镜像级别的。我们说每个容器正常情况下都必须具有操作系统,但是通过Docker抓取的镜像也就仅仅是一个镜像而已,并非正在意义上的操作系统。是非常小的一个文件,比如说我们的Docker运行于Ubuntu的系统,现在抓取一个Docker的Image,其里面存储的Ubuntu的系统镜像也就20MB左右。但是在真实的物理机器上安装一个Ubuntu的系统可能就需要1GB左右的样子。而另外一方面,我们可以基于这个Ubuntu镜像加上自己在这个镜像的基础上运行的container服务,比如MySQL数据库。也就是说,我们要run一个Docker,就必须要有Image,Image里面必须要有巢系统镜像和我们说安装的应用程序软件,因此我们可以这样说,Image就是最基本的操作系统+软件运行环境+我们自己所安装的程序。也就是说我们自己的程序必然需要成为镜像的一部分镜像打包,这样的话就可以将其更新到Docker Hub中,Docker Hub可以理解为这是一个Docker的镜像库,专门用于存放别人打包好的Docker镜像。比如说MySQL公司,他们会遵循Docker镜像生成的协议,会把自己的MySQL数据库生成镜像,然所有的Docker使用者去使用该镜像。那对于开发者来说,也可以制作自己的镜像,比如说你是一个开发者,基于Ubuntu系统开发软件,开发好软件之后,你可以制作成一个镜像,这样可以让另外一个开发者直接就把你的镜像拿到,然后就可以再现你的开发环境。而如果我们是测试者,程序人员将开发好的镜像给到我们,我们直接拿到这个镜像,我们测试的时候能够再现当时的开发环境,这就不会再出现传统的类似于测试还得自己搭建测速环境在进行测试时,测速环境和开发环境的差异导致的测试失败以及由此而引发的诸多问题。也就解决了困扰了软件开发者和软件测试者在过去十多年间开发环境和测试环境不一致的问题。所以运维角度来说也是这样。
Image是Docker中一个非常重要的概念,重要到是程度呢?无论是开发者还是测试者还是运维人员,都是同一个Image。Image类似于一个单链表系统,每个lmage包含一个指向parent image的指针。没有parent image的image是base image。也就是说这是分层的,一层一层的,每一个层次都的一个image。它上面的一层image可定指向它下面一层的image,那么这些链表指针的对应关系是靠谁来保存呢?是靠sqlite数据库。同时呢Docker里面还有一个Layer的概念,一个layer就是一个image,我们将多个layer也就是多个image打包之后,可以保存为一个image,所有我们也说layer是有多个image构成的。比较绕是吧。那么既然有多个image,那么我们就可以在一个容器的镜像内安装多个程序,例如同时安装Ubuntu、Apache、MySQL等。不过,Docker官方建议,一只程序安装在一个容器内,再把这些容器迭起来提供一个完整的服务。
然后我们首先来看分层结构:
如上图,我们从用户空间的视角看,看到是首先是一耳光完整的视图,也就是这是拥有着完整Linux底层文件的系统,有内核,有bootfs,当然也有rootfs。着也就是我们上面所述的每一个镜像必须有一个操作系统镜像,在这里就可以体现出来。
然后我们来看bootfs和rootfs,bootfs (boot filesystem) 主要包含 bootloader 和 kernel, bootloader主要是引导加载kernel, 当boot成功后 kernel 被加载到内存中后 bootfs就被umount掉。rootfs (root filesystem) 包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc等标准目录和文件。上图表示的rootfs颜色的意思是每个发行版的rootfs会有差别,bootfs基本是一致的。所有首先需要将 rootfs 置为 readonly, 进行一系列检查。
在检查完毕之后,就将其合并成一个image提供给Docker使用,也就是最基本的base image。比如这个图上表示的是base image里面安装的是Debion的操作做系统,上面一层的image安装的是集成开发环境Emocs,再往上,安装的是Apache,最上层是可写的层。即可写的成是必须要依赖于其下层的内容,也就是说,如果我们需要修改Apache的配置,那么我们就需要在可写的这一层来进行修改,其下层安装的Apache本身并不会被修改,而是会被完整的复制到最上面的一层,将修改的部分进行修改。这个过程使用的技术就是存储的技术CoW。
因此基于这样的技术我们假设,我们是不是可以无限制的进行堆叠呢?这个具体还不大清楚,不过基于容器化的便捷特性,我们这样做反而得不偿失,徒增其复杂性。因为Docker最佳建议是一个Docker只做一件事情。
最后总结来看,Docker会一统整个软件工程江湖,也将刷新新的人月神话模式,交付模式。至于大数据云计算只是顺带的。