宋宝华- Docker 背后的故事之名称空间

Posted 宋宝华

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了宋宝华- Docker 背后的故事之名称空间相关的知识,希望对你有一定的参考价值。

名称空间是在OS之上实现容器与主机隔离,以及容器之间互相隔离的Linux内核核心技术。根据《Docker 最初的2小时(Docker从入门到入门)》一文,名称空间本质上就是在不同的工作组里面封官许愿,让大家在各自的部门里面都是manager,而且彼此不冲突。本文接下来从细节做一些讨论。

由于本文敲的命令既有可能位于主机,又有可能位于新的名称空间(模拟容器),为了避免搞乱你的脑子,下面主机命令一概采用本颜色,而模拟容器类的命令一概采用本颜色。色盲读者,敬请谅解。

名称空间是什么?

名称空间(Namespace),它表示着一个标识符(identifier)的可见范围。一个标识符可在多个名称空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的名称空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,只要已有的定义都处于其他命名空间中。再次回忆一下这个封官许愿图,大家都是官:


名称空间是C++、Java里面常见的概念。比如下面最简单的程序,在2个独立的名称空间里面各自的函数都是叫func(),func就是一个标识符(identifier),可以并存于多个名称空间。

#include <iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二个命名空间
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
int main ()
{
 
   // 调用第一个命名空间中的函数
   first_space::func();
   
   // 调用第二个命名空间中的函数
   second_space::func(); 

   return 0;
}
Docker要营造OS级别的虚拟化,需要实现一点,让每个容器都感觉自己拥有整个的独立OS,但是实际上,在Docker下,多个容器实际上是运行于相同的OS内核上面:


所以,内核需要提供某种意义上的抽象,让各个容器感觉自己拥有独立的OS,让它们自己运行的时候觉得不是在一个整体的OS里面运行,而是各个容器感觉自己独有一个OS,这个OS最好和底层实际的主机资源隔离,才能实现容器运行的平台无关性。这个抽象可以从这几个角度展开:

进程的ID(PID)

现在每个容器内部的进程应该拥有独立的PID,不能在同一个OS的一个大池子里面(尽管实际上是,但是在容器内部要意识不到)。典型的,在Linux里面,init进程的PID是1,容器化后,应该每个容器都有一个1以及由1衍生的子进程和子进程的子进程(子子孙孙无穷匮)。但是这个容器内部的1进程,在容器内部它是1,但是最终它肯定是属于底下那个同一个OS大池子里面的某一个PID。

类似你在上海呼叫电话号码88888888,和在武汉呼叫电话号码88888888,在各自的城市都觉得是88888888,但是在全国(底下唯一的kernel)范围内则分别是021-88888888和027-88888888。

这种映射关系类似于:


进程间通信(IPC)

与PID类似,在容器内部的进程间通信应该被从全局的Linux的进程间通信隔离开来。在没有名称空间的情况下,Linux System V IPC都会有各自的ID。譬如:

$ ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 524288     baohua     600        524288     2          dest         
0x00000000 327681     baohua     600        1048576    2          dest         
0x00000000 425986     baohua     600        524288     2          dest         
…         
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x002fa327 0          root       666        2         
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

但是在各个容器内部,ID与ID之间应该互相隔离。容器内部应该看不到主机的IPC,而一个容器也看不到另外一个容器的IPC。譬如在这台主机上跑Ubuntu 14.04的bash,目前还没有发现IPC:

baohua@baohua-VirtualBox:~$ docker run -it --rm ubuntu:14.04 bash
root@0c7951083f70:/# ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

主机名称(UTS)

要让容器各自感觉独立,那么从底层的主机名独立也是很重要的。比如,我的主机名现在可以通过hostname命令获取:

baohua@baohua-VirtualBox:~/develop/linux$ hostname
baohua-VirtualBox

而运行的Docker内部的主机名则可以用docker run的-h参数指定,现在我们指定为“container”:

baohua@baohua-VirtualBox:~/develop/linux$ docker run -h container -it --rm ubuntu:14.04 bash
root@container:/# hostname
container

这样容器内部的进程,就不觉得自己在“baohua-VirtualBox”这个机器上面跑。

如果我们docker run中不指定hostname,会有一个随机分配的数值做hostname:

baohua@baohua-VirtualBox:~$ docker run -it --rm ubuntu:14.04 bash
root@0c7951083f70:/# hostname
0c7951083f70

用户(User )

比如我用我的电脑,我是用baohua这个用户名。但是在Docker的容器里面,为了体现虚拟化的概念,容器肯定要和实际的主机分离,这个时候,容器里面应该有自己的用户名。

baohua@baohua-VirtualBox:~/develop/linux$ docker run -h container -it --rm ubuntu:14.04 bash
root@container:/#

登陆到容器后,我们得到的用户名是root。

看到这个root,我们会疑惑?它是否会拥有类似主机的root权限,比如甚至都可以跑到sysfs里面卸掉一个CPU?这个显然是不可能的:

root@container:/sys/devices/system/cpu/cpu1# sh -c 'echo 0 > online'
sh: 1: cannot create online: Read-only file system

因为在容器里面,sysfs都是只读的。实际上,我们并不太希望容器里面控制真实的主机。这个root权限发挥的作用,更多的是在容器内部,它针对虚拟化后的资源,拥有的root权限,比如可以在容器内部执行mount。

下面我们验证容器内部的root权限的作用:容器启动后,我们在根目录下创建文件1,并且在其中写入hello,之后在容器内创建用户名baohua,以baohua这个用户,再在1里面写入hello就不会有权限:

$ docker run -h container -it --rm ubuntu:14.04 bash
root@container:/# touch 1
root@container:/# echo hello > 1
root@container:/# useradd baohua
root@container:/# su baohua
baohua@container:/$ echo hello > 1
bash: 1: Permission denied

挂载(mount)

既然我们强调容器与主机的剥离,我们显然不应该把主机的文件系统暴露给容器内部。众所周知,Linux应用的运行不能没有根文件系统以及proc,sys,dev等特殊的文件系统。所以容器内部也不能不拥有自己的这些文件。但是另外一方面,容器内部看到的东西和主机看到的应该不一样,否则主机就直接暴露给了容器,不能体现虚拟化概念。
Linux的mount名称空间可以实现不同mount 命名空间的进程看到的文件系统层次不一样。也就是说,不同的容器,以及容器与主机之间,可以出现不同目录结构;当然也可以出现相同的目录结构,但是他们在磁盘的位置可以不一样。