容器
对于进程,它的静态表现就是程序,平常都安安静静地待在磁盘上;而一旦运行起来,它就变成了计算机里的数据和状态的总和,这就是它的动态表现。而容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。对于 Docker
等大多数Linux
容器来说,Cgroups
技术是用来制造约束的主要手段,而Namespace
技术则是用来修改进程视图的主要方法。
Namespace
首先,让我们创建一个简单的Docker
容器
1 | $ docker run --it redis /bin/sh |
这个命令是告诉Docker
帮我们启动一个redis
容器,并在容器启动之后,帮我们分配一个文本输入/输出环境。这个样子,我的电脑就变成了一个宿主机,在其上运行着一个redis
容器。
然后我们在容器中执行
1 | / # ps |
按照常理来说,每当我们在宿主机上运行了一个/bin/sh
程序,操作系统都会给它分配一个进程编号,比如PID
=100,而1号进程是系统最初的进程,而现在,我们发现在容器中PID
也为1,其实,Docker
在内部给这个PID
=100的进程施了一个“障眼法”,让他永远看不到前面其他99个进程,让它错误的以为自己就是PID
=1.
这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进只能看到重新计算过的进程编号,例如PID
=1。可实际上,它们在宿主机的操作系统里,还是原来的第100号进程。这种技术,就是Linux
里面的Namespace
机制。Linux
操作系统提供了多种Namespace
,例如Mount
、UTS
、IPC
、Network
、User
等,用来对各种不同的进程上下文进行“障眼法”操作。例如,Mount Namespace
,用来让被隔离进程只看到当前Namespace
里的挂载点信息;Network Namespace
,用来让被隔离进程只能看到当前Namespace
里的网络设备和配置。这就是Linxu
容器最基本的实现原理了。
所以,Docker
容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启用的一组Namespace
参数。这样,容器就只能“看”到当前Namespace
所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了。
所以说,容器,其实是一种特殊的进程而已,即:容器是一个“单进程”模型。这意味着,用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的Namespace
参数。而Docker
项目在这里扮演的角色,更多的是旁路式的辅助和管理工作。
CGroups
Namesapce
实现了对容器的“隔离”,我们再来谈谈容器的“限制”问题。你可以能好奇,我们不是已经通过Namespace
创建了一个容器吗?为什么还要对容器进行“限制”呢?
接着上述的例子,虽然容器内的第1号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第100号进程与其他所有进程之间依然还是平等的竞争关系。这就意味着,虽然第100号进程表面上被隔离了起来,但是它所能使用到的资源(比如CPU
、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。当然,这个100号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。
而Linux CGroups
就是Linux
内核中用来为进程设置资源限制的一个重要功能。Linux CGroups
的全程是Linux Control Group
。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括CPU
、内存、磁盘、网络宽带等等。
我们知道Linux
系统配置都是保存在文件中,所以对于Docker
等Linux
容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把相应的配置,例如CPU,Memory
等信息填进去就可以了。
Image
前面所说,Namespace
的作用是“隔离”,它让应用进程只能看到该Namespace
内的“世界”;而Cgroups
的作用是“限制”,它给这个“世界”围上了一圈看不见的墙。这么一折腾,进程就真的被“装”在了一个与世隔绝的房间里,可是,这个房间四周虽然有了墙,但是如果容器进程低头一看地面,又会是怎样的一幅景象呢?即,容器里的进程看到的文件系统又是怎么样呢?
通过对Namesapce
的了解,我们理所当然的想到了Mount Namesapce
,Mount Nameapsce
实际上修改了容器进程对文件系统的“挂载点”的认知。当然,为了能够让容器的这个根目录看起来更“真实”,我们一般会在这个容器的根目录下挂在一个完整的操作系统的文件系统,例如Ubuntu
的ISO
。这个样子,在容器启动之后,我们在容器里通过执行# ls /
查看根目录下的内容,就是Ubuntu
的所有目录和文件。而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它有一个更为专业的名字,叫做:rootfs
(根文件系统)。
由于rootfs
里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟。
– 引用自知乎一起学习k8s