Docker 进阶与实践(第一讲)

学习 Docker 技术,每看完一章或够写一篇文章的时候就上传一次. 这一篇讲容器技术。以下正文。。。

Docker

容器技术

对于容器,目前并没有一个严格的定义,但是普遍被认可的说法是,它首先必须是一个相对独立的环境,在这一点上有点类似虚拟机,但是没有虚拟机那么彻底。另外,在一个容器环境中,应该最小化其对外界的影响,比如不能在容器中吧host上的资源耗尽,这就是资源的控制。

容器技术之所以受欢迎,一个重要的原因是它已经集成到了 Linux 内核中,已经被当作 Linux 内核原生提供的特征。当然其他平台也有相应的容器技术,但是我们讨论的以及Docker涉及的都是指 Linux 平台上的容器技术。

一般来说,容器技术主要包括Namespace和Cgroup两个内核特征。

  • Namespace 命名空间,它主要做的是访问隔离。其原理是对一类资源进行抽象,并将其封装在一起提供给容器使用,对于这类资源,因为每个容器都有自己的抽象,而他们彼此之间是不可见的,所以就做到访问隔离。
  • Cgroup是 control group 的简称,又称为控制组,它主要是控制资源控制。其原理是将一组进程放在一个控制组里,通过给这个控制组分配指定的可用资源,达到控制这一组进程可用资源的目的。

容器最核心技术是 Namespace+Cgroup,但是光有这两个抽象的技术概念是无法组成一个完整的容器的。
对于 linux 容器的最小组成,是由一下四个部分构成:

  • Cgroup: 资源控制。
  • Namespace: 访问隔离。
  • rootfs: 系统文件隔离。
  • 容器引擎: 生命周期控制。

容器的创建原理

代码一

pid = clone(fun, stack, flags, clone_arg);

(flags: CLONE_NEWPID | CLONE_NEWNS |
     CLONE_NEWUSER | CLONE_NEWNET |
     CLONE_NEWIPC | CLONE_NEWUTS |
        ...)
  • 对于以上代码,通过clone系统调用,并传入各个Namespace对应的clone flag,创建了一个新的子进程,该进程拥有自己的Namespace。从上面的代码可以看出,该进程拥有自己的pid,mount,user,net,ipc,uts namespace 。

代码二:

echo $pid > /sys/fs/cgroup/cpu/tasks
echo $pid > /sys/fs/cgroup/cpuset/tasks
echo $pid > /sys/fs/cgroup/blkio/tasks
echo $pid > /sys/fs/cgroup/memory/tasks
echo $pid > /sys/fs/cgroup/devices/tasks
echo $pid > /sys/fs/cgroup/freezer/tasks
  • 对于代码二,将代码一中的pid写入各个Cgroup子系统中,这样该进程就可以受到相应Cgroup子系统的控制。

代码三:

fun ()
{
    ...

    pivot_root("path_of_rootfs/", path);
    ...

    exec("/bin/bash");
    ...
}
  • 对于代码三,该fun函数由上面生成的新进程执行,在fun函数中,通过pivot_root系统调用,使进程进入新的rootfs,之后通过exec系统调用,在新的Namespace,Cgroup,rootfs中执行"/bin/bash"程序。

通过以上操作,成功在一个“容器”中运行了一个bash程序。对于Cgroup和Namespace的技术细节,我们下一节详细描述

Cgroup

Cgroup 是什么

Cgroup是control group 的简写,属于 Linux 内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用。这些资源主要包括 CPU, 内存, block I/O(数据块 I/O) 和网络宽带。
Cgroup 从 2.6.24版本进入内核主线,目前各大发行版linux都默认打开了 Cgroup 特性

从实现的角度来看,Cgroup 实现了一个通用的进程分组的框架,而不同资源的具体管理则是由各个 Cgroup 子系统实现的。截止内核4.1版本,Cgroup 中实现的子系统的及其作用如下:

  • devices: 设备权限控制
  • cpuset: 分配指定的CPU和内存节点
  • cpu: 控制 CPU 占用率
  • cpuacct: 统计 CPU 使用情况
  • memory: 限制内存的使用上限
  • freezer: 冻结(暂停)Cgroup 中的进程
  • net_cls: 配合tc(traffic controller)限制网络宽带
  • net_prio: 设置进程的网络流量优先级
  • huge_tlb: 限制HugeTLB(块表缓冲区)的使用
  • perf_event: 允许 Perf 工具基于Cgroup分组做性能测试

Namespace

Namespace 是什么

Namespace 是将内核的全局资源做封装,使得每个Namespace都有有一份独立的资源,因此不同的进程各自的 Namespace 内对同一个资源的使用不会互相干扰。
举个例子,执行 sethostname 这个系统调用时,可以改变系统的主机名,这个主机名就是一个内核的全局资源。内核通过实现 UTS Namespace,可以将不同的进程分隔在不同的 UTS Namespace 中,在某个 Namespace 修改主机名时,另一个 Namespace 的主机名还是保持不变。

目前 Linux 内核总共实现了6种 Namespace:

  • IPC: 隔离 System V IPC 和 POSIX 消息队列
  • Network: 隔离网络资源
  • Mount: 隔离文件系统挂载点
  • PID: 隔离进程 ID
  • UTS: 隔离主机名和域名
  • User: 隔离用户 ID 和 组 ID

Namespace 和 Cgroup 的使用是灵活的,同时也有不少需要注意的地方,因此直接操作 Namespace 和 Cgroup 并不是很容易。正是因为这些原因,Docker 通过 Libcontainer 来处理这些底层的事情。这样一来,Docker 只需简单地调用 Libcontainer 的 API ,就能将完整的容器搭建起来。而作为 Docker 的用户,就更不用操心这些事情了。

容器造就 Docker

关于容器是否是 Docker 的技术核心技术,业界一直存在着争议。

在理解了容器,理解了容器的核心技术 Cgroup 和 Namespace,理解了容器技术如何巧妙且轻量地实现“容器”本身的资源控制和访问隔离之后,可以看到 Docker 和容器是一种完美的融合和辅助相成的关系,它们不是唯一的搭配,但一定是最完美的结合(目前来说)。与其说是容器造就了 Docker , 不如说是它们造就了彼此,容器技术让 Docker 得到更多的应用和推广,Docker 也使得容器技术被更多人熟知。
[须知]:转载请标明出处,请尊重笔者和作者的功劳,O(∩_∩)O谢谢!
[参考]:Docker 进阶与实践 华为Docker实践小组 著(机械工业出版社)