Docker 与 k8s 的恩怨情仇(三)—后浪 Docker 来势汹汹

转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

上一节咱们为你们介绍了Cloud Foundry等最初的PaaS平台如何解决容器问题,本文将为你们展现Docker如何解决Cloud Foundry遭遇的一致性和复用性两个问题,并对比分析Docker和传统虚拟机的差别。shell

Docker相比于Cloud Foundry的改进

利用“Mount Namespace”解决一致性问题

在本系列文章的第一节中,咱们提到Docker经过Docker 镜像(Docker Image)功能迅速取代了Cloud Foundry,那这个Docker镜像究竟是什么呢,如何经过为不一样的容器使用不一样的文件系统以解决一致性问题?先卖个关子,咱们先来看看上一节中说过隔离功能和Namespace机制。安全

Mount Namespace,这个名字中的“Mount”可让咱们想到这个机制是与文件挂载内容相关的。Mount Namespace是用来隔离进程的挂载目录的,让咱们能够经过一个“简单”的例子来看看它是怎么工做的。bash

14.png

(用C语言开发出未实现文件隔离的容器)网络

上面是一个简单的的C语言代码,内容只包括两个逻辑:
1.在main函数中建立了一个子进程,而且传递了一个参数CLONE_NEWNS,这个参数就是用来实现Mount Namespace的;
2.在子进程中调用了/bin/bash命令运行了一个子进程内部的shell。app

让咱们编译而且执行一下这个程序:函数

gcc -o ns ns.c
./ns工具

这样咱们就进入了这个子进程的shell中。在这里,咱们能够运行ls /tmp查看该目录的结构,并和宿主机进行一下对比:性能

15.png

(容器内外的/tmp目录)开发工具

咱们会发现两边展现的数据竟然是彻底同样的。按照上一部分Cpu Namespace的结论,应该分别看到两个不一样的文件目录才对。为何?优化

容器内外的文件夹内容相同,是由于咱们修改了Mount Namespace。Mount Namespace修改的是进程对文件系统“挂载点”的认知,意思也就是只有发生了挂载这个操做以后生成的全部目录才会是一个新的系统,而若是不作挂载操做,那就和宿主机的彻底一致。

如何解决这个问题,实现文件隔离呢?咱们只须要在建立进程时,在声明Mount Namespace以外,告诉进程须要进行一次挂载操做就能够了。简单修改一下新进程的代码,而后运行查看:

16.png

(实现文件隔离的代码和执行效果)

此时文件隔离成功,子进程的/tmp已经被挂载进了tmpfs(一个内存盘)中了,这就至关于建立了彻底一个新的tmp环境,所以子进程内部新建立的目录宿主机中已经没法看到。

上面这点简单的代码就是来自Docker镜像的实现。Docker镜像在文件操做上本质是对rootfs的一次封装,Docker将一个应用所需操做系统的rootfs经过Mount Namespace进行封装,改变了应用程序和操做系统的依赖关系,即本来应用程序是在操做系统内运行的,而Docker把“操做系统”封装变成了应用程序的依赖库,这样就解决了应用程序运行环境一致性的问题。不论在哪里,应用所运行的系统已经成了一个“依赖库”,这样就能够对一致性有所保证。

利用“层”解决复用性问题

在实现文件系统隔离,解决一致性问题后,咱们还须要面对复用性的问题。在实际使用过程当中,咱们不大可能每作一个镜像就挂载一个新的rootfs,费时费力,不带任何程序的“光盘”也须要占用很大磁盘空间来实现这些内容的挂载。

所以,Docker镜像使用了另外一个技术:UnionFS以及一个全新的概念:层(layer),来优化每个镜像的磁盘空间占用,提高镜像的复用性。

咱们先简单看一下UnionFS是干什么的。UnionFS是一个联合挂载的功能,它能够将多个路径下的文件联合挂载到同一个目录下。举个“栗子”,如今有一个以下的目录结构:

17.png

(使用tree命令,查看包含A和B两个文件夹)

A目录下有a和x两个文件,B目录下有b和x两个文件,经过UnionFS的功能,咱们能够将这两个目录挂载到C目录下,效果以下图所示:

mount -t aufs -o dirs=./a:./b none ./C

18.png

(使用tree命令查看联合挂载的效果)

最终C目录下的x只有一份,而且若是咱们对C目录下的a、b、x修改,以前目录A和B中的文件一样会被修改。而Docker正是用了这个技术,对其镜像内的文件进行了联合挂载,好比能够分别把/sys,/etc,/tmp目录一块儿挂载到rootfs中造成一个在子进程看起来就是一个完整的rootfs,但没有占用额外的磁盘空间。

在此基础上,Docker还本身创新了一个层的概念。首先,它将系统内核所须要的rootfs内的文件挂载到了一个“只读层”中,将用户的应用程序、系统的配置文件等之类能够修改的文件挂载到了“可读写层”中。在容器启动时,咱们还能够将初始化参数挂载到了专门的“init层”中。容器启动的最后阶段,这三层再次被联合挂载,最终造成了容器中的rootfs。

19.png

(Docker的只读层、可读写层和init层)

从上面的描述中,咱们能够了解到只读层最适合放置的是固定版本的文件,代码几乎不会改变,才能实现最大程度的复用。好比活字格公有云是基于.net core开发的,咱们将其用到的基础环境等都会设计在了只读层,每次获取最新镜像时,由于每一份只读层都是彻底同样的,因此彻底不用下载。

Docker的“层”解释了为何Docker镜像只在第一次下载时那么慢,而以后的镜像都很快,而且明明每份镜像看起来都几百兆,可是最终机器上的硬盘缺没有占用那么多的缘由。更小的磁盘空间、更快的加载速度,让Docker的复用性有了很是显著的提高。

Docker容器建立流程

上面介绍的是Docker容器的整个原理。咱们结合上一篇文章,能够总结一下Docker建立容器的过程实际上是:

  • 启用Linux Namespace配置;
  • 设置指定的Cgroups参数;
  • 进程的根目录
  • 联合挂载各层文件

题外:Docker与传统虚拟机的区别

其实Docker还作了不少功能,好比权限配置,DeviceMapper等等,这里说的仅仅是一个普及性质的概念性讲解,底层的各类实现还有很复杂的概念。具体而言,容器和传统的虚拟机有啥区别?

其实容器技术和虚拟机是实现虚拟化技术的两种手段,只不过虚拟机是经过Hypervisor控制硬件,模拟出一个GuestOS来作虚拟化的,其内部是一个几乎真实的虚拟操做系统,内部外部是彻底隔离的。而容器技术是经过Linux操做系统的手段,经过相似于Docker Engine这样的软件对系统资源进行的一次隔离和分配。它们之间的对比关系大概以下:

6.jpg

(Docker vs 虚拟机)

虚拟机是物理隔离,相比于Docker容器来讲更加安全,但也会带来一个结果:在没有优化的状况下,一个运行CentOS 的 KVM 虚拟机启动后自身须要占用100~200MB内存。此外,用户应用也运行在虚拟机里面,应用系统调用宿主机的操做系统不可避免须要通过虚拟化软件的拦截和处理,自己会带来性能损耗,尤为是对计算资源、网络和磁盘I/O的损耗很是大。

但容器与之相反,容器化以后的应用依然是一个宿主机上的普通进程,这意味着由于虚拟化而带来的损耗并不存在;另外一方面使用Namespace做为隔离手段的容器并不须要单独的Guest OS,这样一来容器额外占用的资源内容几乎能够忽略不计。

因此,对于更加须要进行细粒度资源管理的PaaS平台而言,这种“敏捷”和“高效”的容器就成为了其中的佼佼者。看起来解决了一切问题的容器。难道就没有缺点吗?

其实容器的弊端也特别明显。首先因为容器是模拟出来的隔离性,因此对Namespace模拟不出来的资源:好比操做系统内核就彻底没法隔离,容器内部的程序和宿主机是共享操做系统内核的,也就是说,一个低版本的Linux宿主机极可能是没法运行高版本容器的。还有一个典型的栗子就是时间,若是容器中经过某种手段修改了系统时间,那么宿主机的时间同样会改变。

另外一个弊端是安全性。通常的企业,是不会直接把容器暴露给外部用户直接使用的,由于容器内能够直接操做内核代码,若是黑客能够经过某种手段修改内核程序,那就能够黑掉整个宿主机,这也是为何咱们本身的项目从刚开始本身写Docker到最后弃用的直接缘由。如今通常解决安全性的方法有两个:一个是限制Docker内进程的运行权限,控制它值能操做咱们想让它操做的系统设备,可是这须要大量的定制化代码,由于咱们可能并不知道它须要操做什么;另外一个方式是在容器外部加一层虚拟机实现的沙箱,这也是如今许多头部大厂的主要实现方式。

小结

Docker凭借一致性、复用性的优点打败了前辈Cloud Foundry。本文介绍了Docker具体对容器作的一点改变,同时也介绍了容器的明显缺点。下一篇文章,咱们会为你们介绍Docker又是如何落寞,然后Docker时代,谁又是时代新星。敬请期待。

相关文章
相关标签/搜索