一. 简介
本文介绍Docker容器实现的三大技能:namespace
, cgroup
和rootfs
,以此希望能从本质上剖析容器,并理解其工作机制和工作中可能会遇到的问题。
二. NameSpace
namespace
是实现“看起来”隔离的关键技术,其作用主要是修改进程的视图,使其看起来仿佛是一个新的操作系统进程树。docker使用namespace
通常可以通过命令行或者程序调用的方式执行。对应到容器技术,为了隔离不同类型的资源,Linux 内核里面实现了以下几种不同类型的 namespace。
- UTS,对应的宏为 CLONE_NEWUTS,表示不同的 namespace 可以配置不同的 hostname。
- User,对应的宏为 CLONE_NEWUSER,表示不同的 namespace 可以配置不同的用户和组。
- Mount,对应的宏为 CLONE_NEWNS,表示不同的 namespace 的文件系统挂载点是隔离的
- PID,对应的宏为 CLONE_NEWPID,表示不同的 namespace 有完全独立的 pid,也即一个 namespace 的进程和另一个 namespace 的进程,pid 可以是一样的,但是代表不同的进程。
- Network,对应的宏为 CLONE_NEWNET,表示不同的 namespace 有独立的网络协议栈。
2.1 命令行
操作 namespace
的常用指令 nsenter
,可以用来运行一个进程,进入指定的 namespace
。另一个常用指令是unshare
,可以用于离开当前的namespace、创建并加入新的namespacce
,然后执行参数中指定的命令。
1 | nsenter --target 58212 --mount --uts --ipc --net --pid -- env --ignore-environment -- /bin/bash |
2.2 系统调用
常用的系统调用函数包括clone()
,setns()
和unshare()
,clone()
是创建新的进程,通过标记位的形式将其加入新的namespace
,setns()
是将当前进程加入到已有的namespace
之中。unshare()
则是退出当前namespace
并加入到创建的新namespace
之中。
1 | int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); |
下面是一个实际使用clone()
创建新的进程并将该进程置于新的namespace
的例子
1 |
|
执行之前,可以通过echo
看到进程号,而运行后,再次echo
会发现进程号已经变成了1,即通过namespace
伪装成了1号进程。
2.3 源码实现
namespace
的结构体定义于task_struct
中的nsproxy
,在创建进程时,调用链执行到copy_process()
时,会执行copy_namesapces()
进行复制和设置。
1 | struct task_struct { |
copy_namespace()
源码如下,可见其中如果没有CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWCGROUP
,就返回原来的 namespace
,调用 get_nsproxy
,否则调用create_new_namespace()
创建新的名字空间。
1 | /* |
create_new_namespaces()
函数进行namespace
的复制,根据众多标记位分别判断是复制还是需要重新建立新的相应namesapce
。由此就实现了namespace
的创建工作。
1 | /* |
三. Cgroup
cgroup
是隔离的主要技术。