容器技术的核心功能,就是通过修改和约束进程的动态表现,从而为其创造出一个“空间”。
对于 Docker
等大多数 Linux
容器来说,Namespace
技术是用来修改进程视图的主要方法,而 Cgroups
技术是用来制造约束的主要手段 。
障眼法
每当我们在主机上运行一个程序,操作系统都会给它分配一个进程编号,比如 PID=100
,这个编号是进程的唯一标识。
而我们通过 Docker
把这个程序运行在一个容器当中时,Docker
就会对这个进程的空间做手脚。使得这些进程只能看到重新计算过的进程编号,比如 PID=1
。可实际上,它们在宿主机的操作系统里,还是原来 PID=100
进程。
这种技术,就是 Linux
里面的 Namespace
机制。
而除了我们刚刚用到的 PID Namespace
,Linux
操作系统还提供了 Mount
、UTS
、IPC
、 Network
和 User
这些 Namespace
,用来对各种不同的进程上下文进行“障眼法”操作。
比如,Mount Nmaespace
,用于 让被隔离进程只看到当前 Namespace
里的挂载点信息;Network Namespace
,用于让被隔离进程看到当前 Namespace
里的网络设备和配置。
这就是 Linux
容器最基本的实现原理。
所以,Docker
容器这个听起来模糊的概念,实际上是最创建容器进程时,指定了这个进程所需要启用的一组 Namespace
参数。这样,容器就只能“看”到当前 Namespace
所限定的资源、文件、设备、状态、配置等等。而对于宿主机以及其他不相关的程序,它就完全看不到了。
到此,我们明白了。容器,其实是一种特殊的进程而已。
紧箍咒
上面提到,容器,其实是一种特殊的进程。那么我们使用 Namespace
技术只是让容器进程看不到外界,在宿主机上,它和其他所有进程之间依然是平等的竞争关系。这就是意味着,虽然容器进程表面上被隔离了,但是它依然可以使用宿主机的所有资源,或者是它的资源被宿主机上其他进程抢占。这些情况,显然都不是我们想要的。
Cgroups
就是 Linux
内核中用来为进程设置资源限制的一个重要功能。它的全称是 Linux Control Group
,作用就是限制一个进程组能够使用的资源上限,包括 CPU
、内存、磁盘、网络带宽等等。此外,它还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。
Cgroups
的设计还是比较易用的,简单地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。具体对于 Docker
来说,只需要在每个子系统下面,为每个容器创建一个控制组,然后在启动容器进程之后,把这个进程的 PID
填写到对应控制组的 tasks
文件中就可以了。
至于在这些控制组下面的资源文件填上什么值,就靠用户执行 docker run
时的参数指定了,比如这样一条命令:
$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
在启动这个容器后,我们可以通过查看 Cgroups
文件系统下,CPU
子系统中,docker
这个控制组里面的资源限制文件来确认:
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us
100000
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us
20000
这就是意味着这个 Docker
容器,只能使用到 20% 到 CPU
带宽。
总结
通过以上讲述,我们理解了一个 Docker
容器,其实就是一个启用了 Namespace
的进程,而这个进程使用到资源受到 Cgroups
限制。
这是容器技术中一个非常重要的概念,即:容器是一个“单进程”模型。
这是因为容器本身的设计,就是希望容器和应用能够同生命周期,这个概念对容器编排非常重要。否则,一旦出现类似“容器上正常运行的,但是里面的应用挂了”的情况,编排系统处理起来就非常麻烦了。