Docker基础技术:Linux Namespace(上) | 酷 壳 - CoolShell

1. 利用namespace进行资源隔离

namespace相关的基本系统调用:setns()、clone()、unshare、使用主机/proc下的相关文件(主要是/proc/{pid}/ns/下的文件)。

Docker基本原理 - 图1

setns(int fd, int nstype): 使当前进程加入指定的namespace

fd代表打开namespace文件的文件描述符, nstype检查fd对应的namespace类型与nstype是否对应,nstype为0表示不检查namespace类型

  1. fd=open("/proc/1111/ns/uts") ///打开指定的namespace文件
  2. setns(fd,0) ///加入/proc/1111/ns/uts namespace
  3. execvp("hostname") ///在指定namespace下面打印主机名

int clone(int (fn)(void ), void child_stack, int flags, void arg);

  1. 强化版的fork,在当前进程中创建子进程。这里fn是要执行的函数指针。我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", child_stack明显是为子进程分配系统堆栈空间(在[Linux](http://lib.csdn.net/base/linux)下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承哪些资源, arg就是传给子进程的参数)。下面是flags可以取的值

标志含义

CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”

CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask

CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表

CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy

CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表

CLONE_PTRACE 若父进程被trace,子进程也被trace

CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源

CLONE_VM 子进程与父进程运行于相同的内存空间

CLONE_PID 子进程在创建时PID与父进程一致

CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

CLONE_NEWUTS 在子进程中创建新的主机名和域名namespace,从而与父进程的namespace隔离 CLONE_NEWIPC 在子进程中创建新的 IPC namespace,从而与父进程的namespace隔离 CLONE_NEWNET 在子进程中创建新的network namespace, 从而与父进程的namespace隔离 CLONE_NEWNS 在子进程中创建新的mount namespace, 从而与父进程的namespace隔离 CLONE_NEWUSER 在子进程中创建新的 user and usergroup namespace, 从而与父进程的namespace隔离.

UTS namespace

某个进程都可以有单独的uts(主机名和域名)命名空间,这样每个docker容器都可以作为网络的一个节点,并不仅仅是宿主机上的一个进程。

  1. clone(sethostname("forTest"), CLONE_NEWUTS)
  2. 在“sethostname”这个进程结束后, 不会改变宿主机的主机名,因为带了CLONE_NEWUTS标志,sethostname这个进程运行在新建的自己的uts namespace
  3. 如果不带CLONE_NEWUTS那么会改变主机的主机名

IPC namespace

涉及的IPC资源包括:信号量、消息队列和共享内存。 每一个IPC资源都有一个全局唯一的ID标识符。因此一个ipc namespace中实际上包含了标识符以及实现POSIX消息队列的文件系统。因此想要使用ipc相互通信,那么相互通信的进程需要处在相同的ipc namespace下。

PID namespace

可以对进程重新标号,并且在不同pid namespace下的进程可以使用相同的pid.

PID namespace比较特殊,namespace之间呈现树状结构,根节点是 root namespace, 其他所有的pid namespace都是root namespace的孩子节点。父pid namespace可以看到子pid namespace中的所有进程,并且父pid namespace可以向子pid namespace中的进程发送信号。

每个namespace中的第一个进程“PID 1”,都像linux的init进程一样拥有特殊权限和作用。

如果在新的pid namespace中重新挂载proc文件系统,那么在/proc目录下面只会oi发现所属pid namespace的所有进程

在root pid namespace下可以看到所有的进程。

mount namespace

挂载点隔离,不同mount namepace中发生的挂载行为不会相互影响(默认私有共享的情况下)。mount挂载方式有几种:

  • 共享挂载:共享挂载对象的任何挂载行为都会共享给与之共享的挂载对象
  • 传播挂载:单向共享,如图
  • 私有挂载:不共享

Docker基本原理 - 图2

注意docker文件系统是分层的,一个image可以启动多个container,这时候会有一个问题,如果每个container对大家共有的部分都有可写的权限,就会出问题。所以docker启动的时候会加载镜像的文件系统那层是只读的,然后每个contianer 获取自己的可读写的层,如果container要修改只读层的文件,那么该文件就会从只读层提取到读写层。只读层的文件就被读写层的文件覆盖了,但只读层的那个文件依然存在 这个就实现了文件系统上的隔离。使用docker commit会提交自己对image文件系统的修改。

下面是一个创建network namespace的例子:

  1. # 在network节点上添加一个名为nstest的namespace
  2. [root@network ~]# ip netns add nstest
  3. [root@network ~]#
  4. # 查看namespace中的网络信息
  5. [root@network ~]# ip netns exec nstest ip addr
  6. 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
  7. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  8. [root@network ~]#
  9. # 创建一对虚拟网卡veth-a 和 veth-b,两者由一根虚拟网线连接
  10. [root@network ~]# ip link add veth-a type veth peer name veth-b
  11. [root@network ~]#
  12. # 设置虚拟网卡地址
  13. [root@network ~]# ip addr add 10.0.0.1/24 dev veth-a
  14. [root@network ~]# ip link set dev veth-a up
  15. [root@network ~]# ip link set veth-b netns nstest
  16. [root@network ~]# ip netns exec nstest ip addr add 10.0.0.2/24 dev veth-b
  17. [root@network ~]# ip netns exec nstest ip link set dev veth-b up
  18. # 测试连通性
  19. [root@network ~]# ping 10.0.0.2 -I veth-a
  20. PING 10.0.0.2 (10.0.0.2) from 10.0.0.1 veth-a: 56(84) bytes of data.
  21. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.048 ms
  22. 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.043 ms
  23. 64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.043 ms
  24. ^C
  25. --- 10.0.0.2 ping statistics ---
  26. 3 packets transmitted, 3 received, 0% packet loss, time 1999ms
  27. rtt min/avg/max/mdev = 0.043/0.044/0.048/0.008 ms
  28. [root@network ~]#
  29. [root@network ~]#
  30. [root@network ~]# ip netns exec nstest ping 10.0.0.1
  31. PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
  32. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.033 ms
  33. 64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.042 ms
  34. 64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.044 ms

network namespace

使用network namespace可以对网络进行隔离。同一个namespace下的设备可以像在一台主机上一样互相访问。不同namespace下的设备无法直接互相访问。

2. 利用cgroup进行资源限制