标题Docker容器技术基础
- 进程
- 隔离与限制
容器技术的兴起起源于PaaS技术的普及,Docker项目对此具有里程碑式的意义,Docker项目通过容器镜像解决应用打包这个根本性难题。 接下来通过Linux操作系统层面的知识简要介绍一下Docker的实现
进程
容器其实就是一个沙盒技术,顾名思义,沙盒就像是集装箱,可以将我们的应用装起来的技术。这样,应用与应用之间,就因为有了边界而可以相互之间不干扰,这样被装进集装箱的应用就可以被随意的搬来搬去,而不受外界所影响。
说起来容易,但是实现起来可能就无从下手了,就比如说上面提到的集装箱,我们要怎么实现?
其实映射到我们Linux等操作系统上这个集装箱要怎么实现,我们不难想到一个词,边界,通过边界来实现集装箱一样的隔离功能。
举个例子:从操作系统执行一个程序说起,首先操作系统从程序中发现输入数据保存在一个文件中,这些数据就会被加载到内存中待命。同时操作系统又读取到了计算的指令,这时它就需要指示CPU完成计算操作,而CPU与内存协作进行的计算,又会使用寄存器存放数值、内存堆栈保存执行的命令和变量,同时还伴随着各种各样的I/O设备不断的调用中修改自己的状态。
像这样一个程序运行起来后的计算机执行环境的总和,就是下面主要讲述的:进程
容器技术的核心功能就是通过约束和修改进程的动态表现,从而为其创造出一个边界
而对于Docker等大多数Linux容器来说,cGroups技术是用来制造约束的手段,而NameSpace技术则是用来修改进程视图的主要方法
Docker利用NameSpace实现应用在进程空间的障眼法,也就是隔离应用
而NameSpace的使用方式其实也不复杂,就是在Linux创建新进程的一个可选参数。Linux中创建进程的系统调用是clone(),比如:
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
这个系统调用就会给我们创建一个新的进程,并且返回它的进程ID
当我们在参数中指定 CLONE_NEWPID参数时,比如:
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
这时新创建的这个进程将会看到一个全新的进程空间,在这个进程空间中,它的PID是1,在宿主机的进程空间中这个PID还是真实的数据,比如1002
除了上面举例子的PID NameSpace,Linux还有Mount、UTS、IPC、NetWork、User等NameSpace,用来对各种不同的进程上下文进行隔离的操作。
比如,Mount Namespace,用于让被隔离进程只看到当前 Namespace 里的挂载点信息;Network Namespace,用于让被隔离进程看到当前 Namespace 里的网络设备和配置。
所以,Docker 容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。这样,容器就只能“看”到当前Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了。
所以,容器其实可以看做是特殊的进程
隔离与限制
从上面我们可以知道,Linux容器用来实现隔离的技术是用过NameSpace,Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。但对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有太大区别。
首先,既然容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
其次,在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。这意味着一个容器中修改了时间,那么宿主机的时间就被修改了,随之而来的问题就是运行在当前宿主机下的所有时间都会随之而变
虽然我们用过NameSpace实现了隔离,但是我们还需要限制,为什么?
虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 1002 号进程与其他所有进程之间依然是平等的竞争关系。这就意味着,虽然第 1002 号进程表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。当然,这个 1002 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。所以就需要对于隔离起来的沙盒添加限制。
而这个限制就是通过Linux cGroups来实现资源限制的
Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
接下来会举一个例子来说明cGroups技术,从而结合Docker体会如何实现隔离,也可以观察docker文件夹里面的内容具体分析
如果熟悉 Linux CPU 管理的话,你就会在它的输出里注意到 cfs_period 和 cfs_quota 这样的关键词。这两个参数需要组合使用,可以用来限制进程在长度为 cfs_period 的一段时间内,只能被分配到总量为 cfs_quota 的 CPU 时间。
这个例子主要就是修改cpu.cfs_quota_us的值实现对线程的限制
1. 在/sys/fs/cgroup/cpu 目录下(进入到该目录下)创建container目录:mkdir container
2. 在控制台执行 while : ; do : ; done &
3. 执行top 命令观察CPU使用率
4. 查看/sys/fs/cgroup/cpu 下的 cpu.cfs_quota_us和cpu.cfs_period_us里面的值,使用 cat命令查看,可以发现一个是-1 代表没有限制,一个是100000,那么接下来就修改-1的值
5. echo 20000 > cpu.cfs_quota_us
6. echo 3127679 > tasks (**3127679为我本地测试的PID,你需要替换为你在2步骤执行后出现的PID值即可**)
7. top 观察CPU使用率,你会发现使用不再是接近100%了,是一个接近20%
除 CPU 子系统外,Cgroups 的每一个子系统都有其独有的资源限制能力,比如:memory,为进程设定内存使用的限制、blkio,为块设备设定I/O 限制,一般用于磁盘等设备等
Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。而至于在这些控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定了
如果你本地已经部署了docker,并且有启动的容器,你应该会观察到docker目录,那么你可以观察下面有什么,再结合上面的例子进行思考