中国领先的IT技术网站
|
|

1.3.1 从namespace说起

《Docker从入门到实战》本书从Docker的相关概念与基础知识讲起,结合实际应用,通过不同开发环境的实战例子,详细介绍了Docker的基础知识与进阶实战的相关内容,以引领读者快速入门并提高。本节为大家介绍从namespace说起。

作者:黄靖钧来源:机械工业出版社|2017-10-17 18:07

CTO训练营 | 12月3-5日,深圳,是时候成为优秀的技术管理者了


1.3.1  从namespace说起

想要实现资源隔离,第一个想到的就是chroot命令。通过它可以实现文件系统隔离,这是最早的容器技术。但是在分布式的环境下,容器必须要有独立的IP、端口和路由等,自然就有了网络隔离。同时,进程通信隔离、权限隔离等也需要考虑到,因此基本上一个容器需要做到6项基本隔离,也就是Linux内核中提供的6种namespace隔离,如表1.1所示。

表1.1  namespace说明

namespace

   

IPC

信号量、消息队列和共享内存

Network

网络资源

Mount

文件系统挂载点

PID

进程 ID

UTS

主机名和域名

User

用户 ID 和组 ID

当然,一项完善的容器技术还需要处理很多工作。

对namespace的操作主要是通过clone()、setns()、unshare()这3个系统调用来完成的。

clone()可以用来创建新的namespace。clone()有一个flags参数,该参数以CLONE_NEW* 为格式,包括CLONE_NEWNS、CLONE_NEWIPC、CLONE_NEWUTS、CLONE_NEWNET、CLONE_NEWPID和CLONE_NEWUSER,通过传入这些参数后,由clone()创建出来的新进程就位于新的namespace之中了。

因为Mount namespace是第一个实现的namespace,当初实现没有考虑到还有其他namespace的出现,因此用了CLONE_NEWNS的名字,而不是CLONE_NEWMNT之类的名字。其他CLONE_NEW* 都可以看名字知用途。

那么,如何为已有的进程创建新的namespace呢?这就需要用到unshare()了,使用unshare()调用的进程会被放进新的namespace里面。而setns()则是将进程放到已有的namespace中,docker exec命令的实现原理就是setns()。

事实上,开发namespace的主要目的之一就是实现轻量级虚拟化服务,在同一个namespace下的进程可以彼此响应,而对外界进程隔离,这样在一个namespace下,进程仿佛处于一个独立的系统环境中,以达到容器的目的。

上面介绍的是一些概念,下面来实践一下。因为user namespace是在Linux内核3.8之后才支持的,所以本节讨论的namespace均是3.8以后的版本。

1.查看当前进程的namespace

在了解namespace API之前,先来了解如何查看进程的namespace。在root用户模式下执行:

  1. # ls -l /proc/$$/ns  
  2. total 0  
  3. lrwxrwxrwx 1 root root 0  6月 10 20:29 ipc -> ipc:[4026531839]  
  4. lrwxrwxrwx 1 root root 0  6月 10 20:29 mnt -> mnt:[4026531840]  
  5. lrwxrwxrwx 1 root root 0  6月 10 20:29 net -> net:[4026531956]  
  6. lrwxrwxrwx 1 root root 0  6月 10 20:29 pid -> pid:[4026531836]  
  7. lrwxrwxrwx 1 root root 0  6月 10 20:29 user -> user:[4026531837]  
  8. lrwxrwxrwx 1 root root 0  6月 10 20:29 uts -> uts:[4026531838] 

这里的$$是指当前进程ID号。可以看到诸如4026531839这样的数字,表示当前进程指向的namespace。当两个进程指向同一串数字时,表示它们处于同一个namespace下。

2.使用clone()创建新的namespace

创建一个namespace的方法是使用clone()系统调用,它会创建一个新的进程。为了说明创建的过程,给出clone()的原型如下:

  1. int clone(int(*child_func)(void *), void *child_stack, int flags, void*arg); 

本质上,clone()是一个通用的fork()版本。fork()的功能由flags参数控制。总的来说,约有超过20个不同的CLONE_* 标志控制clone()提供不同的功能,包括父子进程是否共享如虚拟内存、打开的文件描述符和子进程等资源。如果调用clone()时设置了一个CLONE_NEW* 标志,一个与之对应的新的命名空间将被创建,新的进程属于该命名空间。可以使用多个CLONE_NEW* 标志的组合。

3.使用setns()关联一个已经存在的namespace

当一个namespace没有进程时还保持其打开,这么做是为了后续添加进程到该namespace。而添加这个功能就是使用setns()系统调用来完成,这使得调用的进程能够和namespace关联,docker exec就需要用到这个方法:

  1. int setns(int fd, int nstype); 

fd参数指明了关联的namespace ,其指向了 \proc\PID\ns?目录下一个符号链接的文件描述符。可以通过打开这些符号链接指向的文件或者打开一个绑定到符号链接的文件来获得文件描述符。

nstype参数运行调用者检查fd指向的命名空间的类型,如果这个参数等于数,将不会检查。当调用者已经知道namespace的类型时这会很有用。当nstype被赋值为CLONE_NEW* 的常量时,内核会检查fd指向的namespace的类型。?

要把namespace利用起来,还要使用execve()函数(或者其他的exec()函数),使得我们能够构建一个简单但是有用的工具,该函数可以执行用户命令。

4.使用unshare()在已有进程上进行namespace隔离

unshare()和clone()有些像,不同的地方是前者运行在原有进程上,相当于跳出原来namespace操作,Linux自带的unshare()就是通过调用unshare()这个API来实现的。

  1. $ unshare  
  2. Usage:  
  3.  unshare [options] <program> [args...]  
  4.  -h, --help        usage information (this)  
  5.  -m, --mount       unshare mounts namespace  
  6.  -u, --uts         unshare UTS namespace (hostname etc)  
  7.  -i, --ipc         unshare System V IPC namespace  
  8.  -n, --net         unshare network namespace  
  9. For more information see unshare(1). 

由于Docker没有使用这个系统调用,所以不展开。除此之外,像fork()这样的函数也可以实现namespace隔离,但并不属于namespace API的一部分。有兴趣的读者可以扫描以下二维码阅读相关资料。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢
24H热文
一周话题
本月最赞

读 书 +更多

'ASP.NET'程序设计教程

《ASP.NET程序设计教程》是在总结多年ASP.NET教学和应用项目开发经验基础上编写完成的,编写过程中充分吸取了其他畅销实用教程的成功经验。...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× Phthon,最神奇好玩的编程语言