作者:vivo 互联网运维团队- Hou Dengfeng
本文主要介绍使用shell实现一个简易的Docker。
一、目的
在初接触Docker的时候,我们必须要了解的几个概念就是Cgroup、Namespace、RootFs,如果本身对虚拟化的发展没有深入的了解,那么很难对这几个概念有深入的理解,本文的目的就是通过在操作系统中以交互式的方式去理解,Cgroup/Namespace/Rootfs到底实现了什么,能做到哪些事情,然后通过shell这种直观的命令行方式把我们的理解组合起来,去模仿Docker实现一个缩减的版本。
二、技术拆解
2.1 Namespace
2.1.1 简介
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。学习过Linux的同学应该对chroot命令比较熟悉(通过修改根目录把用户限制在一个特定目录下),chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。Namespace是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局系统资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。
Linux Namespace有如下种类:
2.1.2 Namespace相关系统调用
amespace相关的系统调用有3个,分别是clone(),setns(),unshare()。
-
clone: 创建一个新的进程并把这个新进程放到新的namespace中
-
setns: 将当前进程加入到已有的namespace中
-
unshare: 使当前进程退出指定类型的namespace,并加入到新创建的namespace中
2.1.3 查看进程所属Namespace
上面的概念都比较抽象,我们来看看在Linux系统中怎么样去get namespace。
系统中的每个进程都有/proc/[pid]/ns/这样一个目录,里面包含了这个进程所属namespace的信息,里面每个文件的描述符都可以用来作为setns函数(2.1.2)的fd参数。
#查看当前bash进程关联的Namespace # ls -l /proc/$$/ns total 0 lrwxrwxrwx 1 root root 0 Jan 17 21:43 ipc -> ipc:[4026531839] lrwxrwxrwx 1 root root 0 Jan 17 21:43 mnt -> mnt:[4026531840] lrwxrwxrwx 1 root root 0 Jan 17 21:43 net -> net:[4026531956] lrwxrwxrwx 1 root root 0 Jan 17 21:43 pid -> pid:[4026531836] lrwxrwxrwx 1 root root 0 Jan 17 21:43 user -> user:[4026531837] lrwxrwxrwx 1 root root 0 Jan 17 21:43 uts -> uts:[4026531838] #这些 namespace 文件都是链接文件。链接文件的内容的格式为 xxx:[inode number]。 其中的 xxx 为 namespace 的类型,inode number 则用来标识一个 namespace,我们也可以把它理解为 namespace 的 ID。 如果两个进程的某个 namespace 文件指向同一个链接文件,说明其相关资源在同一个 namespace 中。以ipc:[4026531839]例, ipc是namespace的类型,4026531839是inode number,如果两个进程的ipc namespace的inode number一样,说明他们属于同一个namespace。 这条规则对其他类型的namespace也同样适用。 #从上面的输出可以看出,对于每种类型的namespace,进程都会与一个namespace ID关联。 #当一个namespace中的所有进程都退出时,该namespace将会被销毁。在 /proc/[pid]/ns 里放置这些链接文件的作用就是,一旦这些链接文件被打开, 只要打开的文件描述符(fd)存在,那么就算该 namespace 下的所有进程都结束了,但这个 namespace 也会一直存在,后续的进程还可以再加入进来。
2.1.4 相关命令及操作示例
本节会用UTS/IPC/NET 3个Namespace作为示例演示如何在linux系统中创建Namespace,并介绍相关命令。
2.1.4.1 IPC Namespace
IPC namespace用来隔离System V IPC objects和POSIX message queues。其中System V IPC objects包含消息列表Message queues、信号量Semaphore sets和共享内存Shared memory segments。为了展现区分IPC Namespace我们这里会使用到ipc相关命令:
# nsenter: 加入指定进程的指定类型的namespace中,然后执行参数中指定的命令。 # 命令格式:nsenter [options] [program [arguments]] # 示例:nsenter –t 27668 –u –I /bin/bash # # unshare: 离开当前指定类型的namespace,创建且加入新的namesapce,然后执行参数中执行的命令。 # 命令格式:unshare [options] program [arguments] # 示例:unshare --fork --pid --mount-proc readlink /proc/self # # ipcmk:创建shared memory segments, message queues, 和semaphore arrays # 参数-Q:创建message queues # ipcs:查看shared memory segments, message queues, 和semaphore arrays的相关信息 # 参数-a:显示全部可显示的信息 # 参数-q:显示活动的消息队列信息
下面将以消息队列为例,演示一下隔离效果,为了使演示更直观,我们在创建新的ipc namespace的时候,同时也创建新的uts namespace,然后为新的uts namespace设置新hostname,这样就能通过shell提示符一眼看出这是属于新的namespace的bash。示例中我们用两个shell来展示:
shell A
#查看当前shell的uts / ipc namespace number # readlink /proc/$$/ns/uts /proc/$$/ns/ipc uts:[4026531838] ipc:[4026531839] #查看当前主机名 # hostname myCentos #查看ipc message queues,默认情况下没有message queue # ipcs -q ------ Message Queues -------- ke