Namespace 简介(中)

PID Namespace

对于 Docker 来说,PID Namespace可以说非常重要,它可以使容器之间的进程树互不可见。 通过PID Namespace, 每个容器中都会有一个进程号计数器, 容器内所有进程号会被重新编号。宿主机内核会维护各个容器中的进程树,在树的最顶端的进程号变为1, 也就是 Init 进程。 此进程会作为容器内其他所有进程的父进程来执行容器环境的初始化工作。

接下来,我们仍然用 unshare 命令测试一下PID Namespace的作用

查看当前状态

在当前bash中运行

# echo $$
4682#  ll /proc/$$/ns
总用量 0
dr-x--x--x 2 root root 0 Dec 29 16:04 ./
dr-xr-xr-x 9 root root 0 Dec 29 14:01 ../
lrwxrwxrwx 1 root root 0 Jan  1 20:41 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 net -> net:[4026531957]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jan  1 20:41 uts -> uts:[4026531838]

创建一个小的能打印自己pid 的c程序如下,

//getpid.c
#include<unistd.h>
#include<stdio.h>
main()
{printf("pid=%d  ppid=%d\n",getpid(), getppid());
}

编译运行 getppid.c

#gcc getpid.c -o getpid
# ./getpid
pid=4715  ppid=4682

运行一个新的bash并将它加入到新的pid namespace中

我们可以看到新的bash 在自己的名空间中获得的ID为1

# unshare --fork --pid /bin/bash
# echo $$
1

在这个新的bash中,如果我们运行小程序 getpid, 可以发现新进程的pid也是从属于这个namespace的, 而bash进程起到了宿主机中init的作用。

#./getpid
pid=14  ppid=1

那么原来的init进程呢?我们grep一下可以发现其实它一直都在。

# ps -ef |grep init |grep -v grep
root         1     0  0 08:21 ?        00:00:02 /sbin/init splash

再次在新的bash中运行ps命令

# ps PID TTY          TIME CMD4681 pts/2    00:00:00 su4682 pts/2    00:00:00 bash4717 pts/2    00:00:00 unshare4718 pts/2    00:00:00 bash4733 pts/2    00:00:00 ps# ps -ef |grep unshare
root      9644  8010  0 16:32 pts/23   00:00:00 unshare --fork --pid /bin/bash
root      9663  9645  0 16:33 pts/23   00:00:00 grep --color=auto unshare# ps -ef |egrep '8010|9644|9645'|grep -v 'grep'
root      8010  8009  0 13:40 pts/23   00:00:00 bash
root      9644  8010  0 16:32 pts/23   00:00:00 unshare --fork --pid /bin/bash
root      9645  9644  0 16:32 pts/23   00:00:00 /bin/bash
root      9711  9645  0 16:38 pts/23   00:00:00 ps -ef# pstree 8009
su───bash───unshare───bash───pstree

可以发现虽然新的bash程序以及它的子进程在新的名空间中被分配了新的pid,但并不影响它在parent namesapce中获得自己的pid。如图:

我们再做一个实验。在parent pid namespace的shell 中启动一个随意一个进程

# gedit &
[1] 4877

进程启动后我们可以发现它的pid是4887, 然后在新的pid namespace的bash中尝试kill

# kill -9 4887
bash: kill: (4887) - no such process

实际上在child pid namespace中,我们无法访问或者操纵parent pid namespace的进程。这就是PID namespace的隔离性质。

那么为什么我们在新的bash里输入ps,top等命令,还是可以看得到所有进程呢。这是因为,像ps,top这些命令会去读/proc文件系统,而在上面的实验中所有shell都访问的是同一个/proc文件系统,所以这些命令显示的东西都是一样的。

因此,要想达到充分的隔离,我们还要对文件系统进行隔离,这就是下一节的内容。

Mount Namespace

Mount命名空间是第一个在Linux上实现的命名空间类型,出现在2002年。这个事实说明了相当通用的“NEWNS”绰号(简称“新命名空间”):那时没有人似乎认为其他 ,将来可能需要不同类型的名称空间

Mount namespace隔离了名称空间中的进程所看到的挂载点列表。或者,换一种说法,每个挂载名称空间都有自己的挂载点列表,这意味着不同名称空间中的进程可以看到并能够操作单个目录层次结构的不同视图。

在2000 年,Al Viro 为 Linux 引入了绑定挂载和文件系统名称空间:
- 绑定挂载(bind mount)允许从任何其他位置访问任何文件或目录。
- 文件系统名称空间(filesystem namespace)是与不同进程相关联的完全独立的文件系统树。

系统第一次启动时,会有一个单独的挂载名称空间,即所谓的“初始名称空间”。通过使用CLONE_NEWNS标志与clone()系统调用(在新的名称空间中创建一个新的子进程)或unshare()系统调用(将调用者移入新的名称空间)来创建新的Mount Namespace。当创建一个新的Mount Namespace时,它会收到从clone()或unshare()调用方的名称空间复制的挂载点列表的副本。

在clone() 或unshare() 调用之后,可以在每个名称空间(通过mount()和umount()))独立添加和删除挂载点。对安装点列表的更改(默认情况下)仅对进程所在的安装名称空间中的进程可见;其他安装名称空间中的更改不可见。

尽管每个进程使用单独的文件系统名称空间在理论上非常有意义,但是在实践中,完全隔离它们会造成过度限制。例如,假设将一个磁盘加载到一个光盘驱动器中。在最初的实现中,使所有mount namespace都可以看见这个磁盘唯一方法是在每个命名空间中单独挂载磁盘。,因为在最初的文件系统名称空间中执行的挂载无法影响用户的拷贝。而在许多情况下,用户希望是执行一个单独的挂载操作,使得磁盘在系统上的所有挂载命名空间(或者可能是某些子集)中都可见。

为解决这一问题,Linux 2.6.15中又引入了共享子树功能(2006年初,大约挂载命名空间的初始实现大约三年后)。共享子树的主要优点是允许自动控制名称空间之间挂载和卸载事件的传播(mount propagation )。这意味着,例如,将光盘安装在一个安装名称空间中,可以在所有其他名称空间中触发安装该磁盘。

在共享子树功能下,每个安装点都会标记一个“传播类型”,该传播类型确定在此安装点下创建和删除的安装点是否传播到其他安装点。有四种不同的传播类型:

  • MS_SHARED:此挂载点与其他“挂载点”中的其他挂载点共享mount和unmount事件。在此挂载点下添加或删除挂载点时,此更改将传播到==peer group== ,以便mount或unmount也能在每个peer mount points发生。传播也发生在相反的方向上,所以挂载和卸载事件在同级装载也将传播到此挂载点。
  • MS_PRIVATE:与shared mount point相反。挂载点不会将事件传播给任何对等点,也不会从任何对等点接收传播事件。
  • MS_SLAVE:这种传播类型位于共享和私有之间。一个slave mount 从属于一个master(这个master一般是一个shared peer group,master的所有成员会传播mount和unmount事件到slave mount。但是,slave mount不会将事件传播到master 对等组。
  • MS_UNBINDABLE:此mount point 是不可绑定的。与私有mount point 一样,此挂载点不会将事件传播到对等节点或从对等节点传播事件。另外,此mount point不能成为 bind mount 操作的源。

测试 shared peer group

  1. 在根目录创建三个文件夹 X 、 Y、 Z
#cd /; mkdir X Y Z
  1. 将根目录标记为MS_PRIVATE
#mount --make-private /

关于默认传播类型

这里要说明的是,虽然从内核的角度来看,创建新设备的默认设置如下:

如果装载点具有父级(即,它是非根装入点),并且父级的传播类型是MS_SHARED,则新装入的传播类型也是MS_SHARED。否则,新挂载的传播类型是MS_PRIVATE。

根据这些规则,根挂载将是MS_PRIVATE,所有的后代挂载默认也是MS_PRIVATE。然而,MS_SHARED可以说是一个更好的默认值,因为它是更常用的传播类型。为此,systemd将所有安装点的传播类型设置为MS_SHARED。因此,在大多数现代Linux发行版中,默认的传播类型实际上是MS_SHARED。

  1. 以shared传播类型挂载 X、Y
# mount --make-shared /dev/sda2 /X
# mount --make-shared /dev/sda6 /Y
  1. 查看挂载情况
# cat /proc/self/mountinfo
25 0 8:6 / / rw,relatime - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
171 25 8:2 / /X rw,relatime shared:1 - fuseblk /dev/sda2 rw,user_id=0,group_id=0,allow_other,blksize=4096
174 25 8:6 / /Y rw,relatime shared:126 - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered

5,在另一consol 用unshare新起一个bash并将起放入新的mount namespace, 并查看挂载情况

# unshare -m bash
# cat /proc/self/mountinfo
178 170 8:6 / / rw,relatime - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
212 178 8:2 / /X rw,relatime - fuseblk /dev/sda2 rw,user_id=0,group_id=0,allow_other,blksize=4096
223 178 8:6 / /Y rw,relatime - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered

奇怪的是 X,Y在新的mount namespace中的传播类型变成了 private, 前面不是说 ==”当创建一个新的Mount Namespace时,它会收到从clone()或unshare()调用方的名称空间复制的挂载点列表的副本“== 吗,怎么传播类型没有复制过来?

这是因为,当创建一个新的挂载名称空间时,unshare假定用户需要一个完全隔离的名称空间,并通过执行以下命令(将根目录下的所有挂载以递归方式标记为private)的等效命令使所有挂载点保持私有状态:

mount --make-rprivate /

为了防止这种情况,我们可以在创建新的名称空间时使用一个额外的选项:

unshare -m --propagation unchanged <cmd>
  1. 以propagation unchange方式重做第五步
# unshare -m --propagation unchanged bash
# cat /proc/self/mountinfo
178 170 8:6 / / rw,relatime - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
212 178 8:2 / /X rw,relatime shared:1 - fuseblk /dev/sda2 rw,user_id=0,group_id=0,allow_other,blksize=4096
223 178 8:6 / /Y rw,relatime shared:126 - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
  1. 回到第一个bash,从X mount point创建一个bind mount
# mount --bind /X /Z
# cat /proc/self/mountinfo
25 0 8:6 / / rw,relatime - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
171 25 8:2 / /X rw,relatime shared:1 - fuseblk /dev/sda2 rw,user_id=0,group_id=0,allow_other,blksize=4096
174 25 8:6 / /Y rw,relatime shared:126 - ext4 /dev/sda6 rw,errors=remount-ro,data=ordered
224 25 8:2 / /Z rw,relatime shared:1 - fuseblk /dev/sda2 rw,user_id=0,group_id=0,allow_other,blksize=4096

对比两个bash的 mount info我们可以发现
- 上述试验中生成了两个 shared peer group。 bash1中的X,Z 以及bash2 中的X’(X的副本) 同属于 shared peer group 1, 而 bash1 中的Y以及 bash2中的Y’ 同属于 shared peer group 126.
- bash1 中的挂载点Z, 由于是在bash2 所属的名空间被创建后挂载的所以未被复制,这是由于它的parent mount(/)标记为私有。

测试对比 MS_SHARED 和 MS_PRIVATE

  1. 在根目录下创建两个文件夹 mntP 和 mntS
cd /;mkdir mntP mntS
  1. 以不同传播方式挂载这两个目录到tmpfs文件系统
# mount -t tmpfs -o size=20m tmpfs --make-shared /mntS
# mount -t tmpfs -o size=20m tmpfs --make-private /mntP
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
170 25 0:47 / /mntS rw,relatime shared:1
171 25 0:52 / /mntP rw,relatime
  1. 在另一consol 用unshare新起一个bash
# unshare -m --propagation unchanged bash
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntS rw,relatime shared:1
223 178 0:52 / /mntP rw,relatime
  1. 在第二个bash中执行
# mkdir /mntS/a
# mount /dev/sda2 /mntS/a
# mkdir /mntP/b
# mount /dev/sda6 /mntP/b
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntS rw,relatime shared:1
223 178 0:52 / /mntP rw,relatime
224 212 8:2 / /mntS/a rw,relatime shared:126
258 223 8:6 / /mntP/b rw,relatime

从上面可以看出,/mntS/a被创建为共享(从其父装载继承此设置),/mntP/b被创建为私有装载。

  1. 返回到第一个bash并检查设置,我们看到在共享挂载点/ mntS下创建的新挂载传播到其对等挂载,但在专用挂载点/ mntP没有传播:
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
170 25 0:47 / /mntS rw,relatime shared:1
171 25 0:52 / /mntP rw,relatime
257 170 8:2 / /mntS/a rw,relatime shared:126

测试MS_SLAVE

  1. 在根目录下创建两个文件夹 mntX 和 mntY
cd /;mkdir mntX mntY
  1. 挂载这两个目录到tmpfs文件系统
# mount -t tmpfs -o size=20m tmpfs --make-shared /mntX
# mount -t tmpfs -o size=20m tmpfs --make-shared /mntY
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
170 25 0:47 / /mntX rw,relatime shared:1
171 25 0:52 / /mntY rw,relatime shared:126
  1. 在另一consol 用unshare新起一个bash
# unshare -m --propagation unchanged bash
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntX rw,relatime shared:1
223 178 0:52 / /mntY rw,relatime shared:126
  1. 在bash2 设置/mntY为slave
# mount --make-slave /mntY
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntX rw,relatime shared:1
223 178 0:52 / /mntY rw,relatime master:126

可以看见 /mntY 已经变成了 shared peer group 126的slave mount 了

  1. 在bash2 执行
# mkdir /mntX/a
# mount /dev/sda2 /mntX/a
# mkdir /mntY/b
# mount /dev/sda6 /mntY/b
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntX rw,relatime shared:1
223 178 0:52 / /mntY rw,relatime master:126
224 212 8:2 / /mntX/a rw,relatime shared:127
258 223 8:6 / /mntY/b rw,relatime

可以看见此时 /mntY/b 被创建为 private状态

  1. 在bash1中检查,可发现bash2 中 /mntY/b的挂载未被传播
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
170 25 0:47 / /mntX rw,relatime shared:1
171 25 0:52 / /mntY rw,relatime shared:126
257 170 8:2 / /mntX/a rw,relatime shared:127

注意,此时bash1 中的 /mntY仍属于 shared peer group 126, 它其实是 bash2 中的/mntY的master

  1. 在bash1中执行以下命令
# mkdir /mntY/c
# mount /dev/sda6 /mntY/c
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
170 25 0:47 / /mntX rw,relatime shared:1
171 25 0:52 / /mntY rw,relatime shared:126
257 170 8:2 / /mntX/a rw,relatime shared:127
284 171 8:6 / /mntY/c rw,relatime shared:128
  1. 在bash2中检查可发现 /mntY/c的挂载被传播到了 bash2的 mount namesapce
# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//'
212 178 0:47 / /mntX rw,relatime shared:1
223 178 0:52 / /mntY rw,relatime master:126
224 212 8:2 / /mntX/a rw,relatime shared:127
258 223 8:6 / /mntY/b rw,relatime
290 223 8:6 / /mntY/c rw,relatime master:128

测试 MS_UNBINDABLE

Unbindable mounts用于解决所谓的 “mount point explosion”的问题 .这一问题发生在文件树的一个低级别的挂载点重复执行递归绑定更高级子树时发生的问题.

  1. 创建两个挂载点 /mntX 和 /mntX/mntY
# mkdir /mntX
# mount -t tmpfs -o size=20m tmpfs --make-shared /mntX
# mount | awk '{print $1, $2, $3}'
/dev/sda6 on /
tmpfs on /mntX
  1. 在另一终端用unshare 新创建一个bash, 并将/ 连同所有子挂载点递归设为 slave(防止在其他mount namespace产生副作用). 然后尝试将几个用户的home目录递归的绑定到/mnt
# unshare -m bash
# mount --make-rslave /
# mkdir -p /home/user1 /home/user2
# mount --rbind / /home/user1
# mount | awk '{print $1, $2, $3}'
/dev/sda6 on /
tmpfs on /mntX
/dev/sda6 on /home/user1
tmpfs on /home/user1/mntX# mount --rbind / /home/user2
root@chic-X450LD:/# mount | awk '{print $1, $2, $3}'
/dev/sda6 on /
tmpfs on /mntX
/dev/sda6 on /home/user1
tmpfs on /home/user1/mntX
/dev/sda6 on /home/user2
tmpfs on /home/user2/mntX
/dev/sda6 on /home/user2/home/user1
tmpfs on /home/user2/home/user1/mntX
  1. 通过 –make-unbindable 可以阻断这种情况
# mount --rbind --make-unbindable / /home/user1
# mount --rbind --make-unbindable / /home/user2
  1. 重做步骤1,2
# mount | awk '{print $1, $2, $3}'
/dev/sda6 on /
tmpfs on /mntX
/dev/sda6 on /home/user1
tmpfs on /home/user1/mntX
/dev/sda6 on /home/user2
tmpfs on /home/user2/mntX

chroot 与mount namespace

早期Linux可以用chroot改变一个进程的根目录,也就是通过该命令可将一个子目录设置为该应用程序的“/”,如果要正常执行命令和程序等,必须还将系统中/bin, /usr/bin,/usr/lib等目录拷贝到该“/”下。

mount namespace进一步丰富了chroot的功能,它chroot更灵活。它属于内核名字空间的一部分。 mount namespace可以对系统真正根目录下的子目录做到共享、独享(也就是将拥有一个子目录的副本)等。

制作一个简单的小容器

接下来我们再结合chroot做一个小实验。 假定有一个用户user1

现在我们希望把user1的目录局限在它的主目录 /home/user1

  1. 用unshare启动新的bash并为其指定新的pid 和 mount 名空间
# unshare --mount --fork --pid /bin/bash
  1. 在user1主目录创建三个子目录作为挂载点
# mkdir -p /home/user1/bin /home/user1/lib /home/user1/lib64
# tree /home/user1/
/home/user1/
├── bin
├── lib
└── lib64
  1. 挂载三个目录,并用chroot将/home/user1变为根目录
# mount --rbind /bin/ /home/user1/bin
# mount --rbind /lib/ /home/user1/lib
# mount --rbind /lib64/ /home/user1/lib64
# chroot /home/user1/
bash-4.3# pwd
/
  1. 挂载proc 目录后再次运行ps 命令,现在我们可以看见进程少多了
# mkdir /proc
# mount -t proc proc /proc
# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
0            1     0  0 10:05 ?        00:00:00 /bin/bash
0           20     1  0 10:10 ?        00:00:00 /bin/bash -i
0           25    20  0 10:12 ?        00:00:00 ps -ef

Docker 入门笔记 8 - Namespace 简介(中)相关推荐

  1. Docker入门笔记(1)

    Docker入门笔记(1) 1.安装Docker yum -y install docker-ce 2.查看Docker版本 [root@localhost ~]# docker -v Docker ...

  2. Docker学习笔记五 在测试中使用Docker

    2019独角兽企业重金招聘Python工程师标准>>> 5.1 使用Docker测试静态网站(Nginx) 将项目命名为Sample 首先建立构建环境 mkdir sample cd ...

  3. Docker入门笔记

    目录 二进制安装Docker 1 Docker镜像管理 2 2.1. 镜像是什么 2 2.2. 镜像与容器联系 2 2.3. 管理镜像常用命令 2 2.3.1. docke查看镜像信息及镜像详细信息 ...

  4. centos docker 入门笔记(一)

    1.首先docker对linux内核有个版本要求貌似是最低3.2.6(docker依赖于Linux的一些内核特性),最好使用centos 7.内核版本最好是3.2.6以上版本. [root@local ...

  5. Docker工作笔记001---Docker的简介

    JAVA技术交流QQ群:170933152 Docker 教程 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以 ...

  6. Docker入门笔记(七)——镜像

    1. 镜像是什么? 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行库.环境变量和配置文件. 所有的应用,直接打包d ...

  7. docker入门笔记(基础版)

    镜像命令 # 查看docker概要信息 docker info # 列出本地主机上的镜像 docker images docker images -a # 查看远程库的镜像 docker search ...

  8. Golang 入门笔记(二)中

    目录 文章目录 目录 转换不同的数据类型 指针 指针地址和指针类型 使用指针修改值 使用指针变量获取命令行的输入信息 创建指针 new() 变量生命期 栈 堆 变量逃逸 字符串应用 计算字符串长度 遍 ...

  9. 深入浅出,白话文Docker入门,万字笔记

    深入浅出 白话文Docker入门笔记 1.什么是Docker: 1.1 传统项目上线模式: 1.2 虚拟机: 1.3 Linux 容器: 1.4 Docker: 2.Docker初体验: 2.1 Do ...

最新文章

  1. Spring Aop----用idea实现面向切面编程
  2. Linux+CLion+cmake 动态链接库的使用
  3. 干货分享:如何使用Kubernetes的Ingress API
  4. spring 使用其他类protected方法_Java操作bean、属性、方法的使用工具类
  5. easyui tree动态加载_动态路由:Gin vs SpringMVC
  6. reflectasm --反射工具
  7. 计算机三级之嵌入式系统学习笔记9
  8. 兄弟j220怎么清零_兄弟j220怎么清零_兄弟Brother全系列打印机清零大全
  9. vba移动文件_VBA代码如何移动文件,如何复制文件
  10. 区块链入门导航-磨链社区
  11. www.tf.tt index.php,恶意软件分析 URL链接扫描 免费在线病毒分析平台 | 魔盾安全分析...
  12. Ruby读取Excel文件的两种方法
  13. 1D mesauring
  14. 最好玩的计算机游戏排行,10款好玩的电脑单机游戏 好玩的单机游戏排行
  15. PL/SQL Developer下载、安装、使用教程
  16. 格式工厂 5.2.0.0 — 视频格式转换
  17. matlab培训2018年暑假,2016暑假建模培训Matlab小作业
  18. 网络管理服务器篇之FTP
  19. CSS实现图片高斯模糊效果
  20. 二、cas4.2.x修改支持http协议

热门文章

  1. 虚拟机——windows安装VMware虚拟机
  2. 2009-2015年阅读书籍
  3. Android App开发之位图加工Bitmap中转换位图的像素色彩、裁剪内部区域、利用矩阵变换位图的讲解及实战(附源码和演示)
  4. RTL8211使用说明
  5. 数据库系统是什么?它由哪几部分组成?
  6. 嵌入式薪资真实情况,这届毕业生都拿多少钱?!
  7. L1-001 ~ L1-005
  8. winows 10 下离线安装dapr
  9. PMP章节重点 项目经理的胜任力
  10. 常用软件官网下载地址