现在说到容器技术,大家默认指的就是Docker。但实际上,在Docker出现之前,PaaS社区就已经有了以CloudFoundry和OpenShift为代表的容器技术,是当时的主流。那么为什么Docker最终会火起来呢?因为传统的PaaS技术也可以将本地应用一键部署到云端,也是以隔离环境(容器)的形式进行部署,但是兼容性很差。因为它的主要原理是将本地应用和启停脚本一起打包,上传到云服务器,然后通过云服务器中的脚本启动应用。这种方法似乎很理想。但在现实中,由于本地和云端环境的差异,上传到云端的应用经常会报各种错误,无法运行,需要对配置和参数进行各种修改才能兼容。在项目迭代过程中,甚至需要重新适配不同版本的代码,非常耗费精力。然而,Docker通过一个小小的创新解决了这个问题。在Docker的解决方案中,它不仅将本地应用打包,还将本地环境(操作系统的一部分)打包,形成一个文件包,称为“Docker镜像”。因此,这个“Docker镜像”包含了应用运行所需的所有依赖。我们可以直接基于这个“Docker镜像”在本地进行开发和测试。完成后,我们直接将这个“Docker镜像”上传到云端运行即可。Docker实现了本地和云环境完全一样,实现了真正的一次性开发,随处运行。1、容器到底是什么?究竟什么是容器?可能我们对容器不是很了解,但是对于虚拟机我们比较熟悉,那么我们先来看看容器和虚拟机的区别:上图左边是虚拟机的原理,右边是Docker容器的原理。虚拟机是一种硬件设备,它在宿主计算机上虚拟出一套基于Hypervisor软件的操作系统,然后在这些虚拟硬件上安装操作系统GuestOS,然后不同的应用程序就可以运行在不同的GuestOS上。它们之间也是相互独立的,资源是隔离的,但是由于创建虚拟机需要一个Hypervisor,而每个虚拟机都需要运行一套完整的操作系统GuestOS,这种方式会带来很多额外的资源开销.但是,Docker容器中没有管理程序层。虽然它需要在宿主机中运行DockerEngine,但它的原理与hypervisor完全不同。它不虚拟化硬件设备,也不独立部署一套完整的操作系统GuestOS。Docker容器没有这么复杂的实现原理。其实就是一个普通的进程,只不过是经过特殊处理的普通进程。当我们启动容器(dockerrun...)时,DockerEngine只是启动一个进程,该进程在我们的容器中运行应用程序。但是DockerEngine对这个过程做了一些特殊的处理。经过这些特殊处理后,这个进程看到的外部环境不再是宿主机的环境(它看不到宿主机中的其他进程,以为它是当前操作系统中唯一的进程),DockerEngine也限制此进程使用的资源以防止其无限使用主机资源。那么Doc??kerEngine做了哪些特殊处理,才会有如此神奇的效果呢?2、容器是如何实现资源隔离和限制的?Docker容器主要用两个技术点来隔离这个过程:Namespace技术Cgroups技术很显然,这两个技术点对于理解容器的原理非常重要,是容器技术的核心。下面详细解释一下:Namespace技术Namespace并不是什么新技术,它是Linux操作系统默认提供的API,包括PIDNamespace、MountNamespace、IPCNamespace、NetworkNamespace等。以PIDNamespace为例,它的作用是让我们在创建进程的时候告诉Linux系统,我们要创建的进程需要一个新的独立进程空间,这个进程在这个新的进程空间中的PID为1,也就是说这个进程只能看到这个新进程空间里的东西,看不到外面宿主环境的东西,也看不到其他进程(但这只是一个虚拟空间,其实这个进程是在hostmachinePID应该是什么还是什么,没有变化,但是在这个进程空间中,进程认为自己的PID=1)。比如一个班级,每个人在这个班级都有一个编号,班里有90个人,然后来了一个新同学,他在班级的编号是91,但是老师要特别照顾这个一个同学,所以我在班里开辟了一个独立的小隔间,告诉同学他的号码是1。由于这个同学被隔离在这个小空间里,他真的以为我是班里的第一个学生,所以我给同学编号了。1.当然,其实这个同学在班上的人数还是91。另外,NetworkNamespace的技术原理也是类似的,所以这个进程只能看到当前Namespace空间内的网络设备,而不能看楼主的真实情况。同理,Mount、IPC等其他Namespace也是如此。命名空间技术实际上修改了应用程序的可视范围,但应用程序的本质没有改变。但是,Docker容器虽然有一部分操作系统(与文件系统相关),但是没有内核,所以多个容器共享宿主机的操作系统内核。这与虚拟机的原理完全不同。Cgroups技术Cgroup的全称是ControlGroup,其作用是限制进程组使用的最大资源(这些资源可以是CPU、内存、磁盘等)。由于Namespace技术只能改变进程组的可视范围,并不能真正限制资源。然后,为了防止容器(进程)互相抢夺资源,甚至某个容器用完了宿主机的所有资源,导致其他容器崩溃。所以必须使用Cgroup技术来限制容器的资源。Cgroup技术也是Linux默认提供的功能。Linux系统的/sys/fs/cgroup下有cpu、memory等子目录。Cgroup技术提供的功能就是根据这些目录来限制这些资源。例如:在/sys/fs/cgroup/cpu下创建一个dockerContainer子目录,系统会在新建的目录下自动生成一些配置文件。这些配置文件用于控制资源使用。例如,你可以在这些配置文件中设置某个进程ID的最大CPU使用率。Cgroup也使用同样的原理来限制内存和磁盘等其他资源。3、集装箱的形象是什么?一个基本的容器镜像实际上是一个rootfs,它包含操作系统的文件系统(文件和目录),但不包含操作系统的内核。rootfs是挂载在容器根目录下的全新文件系统。该文件系统与宿主机的文件系统无关。它是一个完全独立的文件系统,用于为容器提供环境。对于Docker容器,需要根据pivot_root命令将容器内的系统根目录切换到rootfs。这样,有了这个rootfs,容器就可以为进程构建一个完整的文件系统,并实现与宿主机的连接。正是因为有rootfs,才能让基于容器的本地应用和云应用运行环境保持一致.另外,为了方便镜像的复用,Docker在镜像中引入了层(Layer)的概念,可以将不同的镜像逐层堆叠起来。这样,如果我们要制作新的镜像,可以在之前做过的镜像的基础上继续做。如上图所示,本例中底层是操作系统引导,上层是基础镜像层(Linux文件系统),再上层是我们需要的各种应用镜像。Docker会将这些镜像共同挂载在一个挂载点上,这些镜像层是只读的。只有最上面的容器层是可读可写的。这种分层方案实际上是基于UnionFS(UnionFileSystem)技术。它可以将不同的目录全部挂载在同一目录下。比如有文件夹test1和test2,这两个文件夹下的文件可能相同也可能不同。然后我们可以将这两个文件夹以联合挂载的方式挂载到test3,这样test3目录下就会有test1和test2的所有文件(同一个文件去重,保留不同的文件)。这个原则适用于Docker镜像。例如,有两个同学。A同学已经做了一个基于Linux的Java环境的镜像。S同学想搭建一个JavaWeb环境,所以不需要做Java环境的镜像,可以直接添加Tomcat,根据A同学的镜像生成新的镜像。以上是对微服务架构“容器技术”的一些思考。
