Docker
Docker底层原理 - 图1
Linux 的内核,提供了多达 8 种类型的 Namespace。在这些独立的 Namespace 中,资源互不影响,隔离措施做的非常好。

8 种类型

先来看一下,Linux 都支持哪些 Namespace。可以通过 unshare 命令来观察到这些细节。
Docker底层原理 - 图2
在终端执行 man unshare,将会出现这些 Namespace 的介绍:

  • Mount(mnt):隔离挂载点
  • Process ID (pid):隔离进程 ID
  • Network (net):隔离网络设备,端口号等
  • Interprocess Communication (ipc):隔离 System V IPC 和 POSIX message queues
  • UTS Namespace(uts):隔离主机名和域名
  • User Namespace (user):隔离用户和用户组

另外,Linux 在 4.6 版本,5.6 版本,分别加入了 cgroups 和 Time 两种隔离类型,加起来就有 8 种。
Control group (cgroup) Namespace 隔离 Cgroups 根目录 (4.6 版本加入);Time Namespace 隔离系统时间 (5.6 版本加入)。

1 个例子

通过 unshare 命令,可以快速建立一些隔离的例子,拿最简单直观的 pid namespace 来看一下它的效果。
众所周知,Linux 进程号为 1 的,叫做 systemd 进程。但在 Docker 中,通过执行 ps 命令,却只能看到非常少的进程列表。
执行下面的命令,进入隔离环境,并将 bash 作为根进程:

  1. unshare --pid --fork --mount-proc /bin/bash

效果如图所示:
Docker底层原理 - 图3
可以看到,我们的 bash,已经成为了 1 号进程,而宿主机和其他隔离环境的进程信息,在这里是不可见的
先在隔离环境中,执行 sleep 1000。再开一个终端,在宿主机上执行 pstree,将会看到这个隔离环境的进行信息。
Docker底层原理 - 图4
接下来,在宿主机上,把 sleep 对应进程的命名空间信息,和宿主机的命名空间信息作一下对比。
可以看到,它们的 pid namespace,对应的数值是不同的。
Docker底层原理 - 图5
下面给出其他 namespace 的实验性命令,可以实际操作一下。

试验一下

  1. unshare --mount --fork /bin/bash

创建 mount namespace,并在每个不同的环境中,使用不同的挂载目录。

  1. unshare --uts --fork /bin/bash

uts 可以用来隔离主机名称,允许每个 namespace 拥有一个独立的主机名,可以通过 hostname 命令进行修改。

  1. unshare --ipc --fork /bin/bash

IPC Namespace 主要是用来隔离进程间通信的。Linux 的进程间通信,有管道、信号、报文、共享内存、信号量、套接口等方式。
使用了 IPC 命名空间,意味着跨 Namespace 的这些通信方式将全部失效!不过,这也正是希望看到的。

  1. unshare --user -r /bin/bash

用户命名空间,就非常好理解了。可以在一个 Namespace 中建立 xjjdog 账号,也可以在另外一个 Namespace 中建立 xjjdog 账号,而且它们是相互不影响的。

  1. unshare --net --fork /bin/bash

net namespace,这个就非常有用了。它可以用来隔离网络设备、IP 地址和端口等信息。

结语

可以看到,通过各种 Namespace,Linux 能够对各种资源进行精细化的隔离。
Docker 本身也是一个新瓶装旧酒的玩具。Docker 的创新之处,在于它加入了一个中央仓库,并封装了很多易用的命令。
到目前为止,并没有对 CPU 和内存的资源使用进行隔离,也没有对应的 Namespace 来解决这些问题。
资源限制的功能,是使用 Cgroups 进行限额配置来完成的,和 Namespace 没什么关系。
最后,附上 Docker 的一张生命周期图:
Docker底层原理 - 图6
Docker 发展到现在,应用工具链已经非常成熟了,很多同学已经驾轻就熟,如果对容器技术非常感兴趣,不如多看一下最底层的原理。这样,不管是谷歌推自己的容器,还是继续使用 Docker,都能快速把它掌握。