容器——Namespace、AUFS¶
Namespace¶
Namespace 操作主要涉及以下三个 API:
- clone(2)
clone 系统调用创建新进程,通过
flags
参数指定CLONE_NEW*
标识来创建新的 Namespace 并将新创建的进程加入其中。- setn(2)
允许进程加入已存在的 Namespace。
docker exec
的实现会用到该系统调用。- unshare(2)
创建新的 Namespace (通过和 clone 类似的参数)并将调用的进程加入到该 Namespace 中。
http://man7.org/linux/man-pages/man7/namespaces.7.html
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNS |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWUSER,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
(Go 语言中的 exec.Command
就是 clone + exec
,我们可以通过 cmd.SysProcAttr
来指定 clone 的相关 flags
)
通过上面的程序,我们创建了一个新的 sh
进程,这个进程运行在新的 uts, ipc, … Namespace 中。
UTS¶
UTS Namespace 主要用来隔离 hostname 和 domainname 这两个系统标识,这样新 Namespace 可以有独立的 hostname。
在新 Namespace 的 sh 中,我们修改 hostname:
# hostname -b westeros
# hostname
westeros
另开一个窗口,打开一个全局 Namespace 的 sh。
# hostname vagrant-ubuntu-trusty-64
可以发现全局的不受影响。
IPC¶
IPC Namespace 主要是隔离 System V message queues 等。在新 Namespace 的 sh 中:
# ipcmk -Q
Message queue id: 0
# ipcs
...
------ Message Queues --------
key msqid owner perms used-bytes messages
0x618c7bdc 0 root 644 0 0
在全局 sh 中:
ipcs
...
------ Message Queues --------
key msqid owner perms used-bytes messages
可以发现全局的不受影响。
PID¶
PID Namespace 用来隔离进程 pid,同一个进程在新 Namespace 和全局的 Namespace 中 pid 不一样,这样新 Namespace 的第一个进程的 pid 就可以为 1。
# mount -t proc proc /proc
# pstree
# pstree -pl
sh(1)───pstree(4)
Mount¶
也就是 CLONE_NEWNS
, 隔离挂载点视图,这样新 Namespace 中 mount,umount 就和全局 Namespace 脱钩了。这样新 Namespace 中可以切换 rootfs。
Network¶
Network Namespace 是用来隔离网络设备、 IP地址端口等网络栈的 Namespace。Network Namespace 可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个 Namespace 内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方便地实现容器之间的通信,而且不同容器上的应用可以使用相同的端口。
默认新 Namespace 中只有一个 lo 设备,具体如何构建网络可以参考 容器——Network Namespace 和桥接网络模式 。
# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
USER¶
User Namespace 主要是隔离用户的用户组 ID。 也就是说,一个进程的 uid 和 gid 在User Namespace 内外可以是不同的。 比较常用的是,在宿主机上以一个非 root 用户运行 创建一个 User Namespace, 然后在 User Namespace 里面却映射成 root 用户。这意味着,这个进程在 User Namespace 里面有 root 权限,但是在 User Namespace 外面却没有 root 的权限。
AUFS¶
$ mkdir writeLayer
$ mount -t aufs -o dirs=./writeLayer:./busybox none ./mnt
$ cd mnt
$ touch test123
$ ls ../writeLayer
test123
AUFS 可以把多个文件夹合并成一个统一的视图,如上面的命令会将 writeLayer 和 busybox 两个文件夹的内容合并到一起并挂载到 mnt 目录下,第一个目录 writeLayer 可读写,其余目录只读。读的内容为 busybox + writeLayer,写的内容会写到 writeLayer 下。
容器的 rootfs 即通过以上方式构建而成。
pivot_root¶
pivot_root
moves the root file system of the current process to the directory put_old and makes new_root the new root file system
上面新 Namespace 中的进程的 rootfs 还是和系统的一样,我们可以通过 pivot_root
(类似 chroot)将新 Namespace 中的 root方式切换到一个我们通过 AUFS 构建出的文件系统中
自此,一个简陋的容器就构建完成了。
参考资料: 自己动手写Docker