mount namespace是一个强大并且复杂的机制,用来为每个用户和每个容器创建文件系统树.它是一个很复杂的特性.在我们关于namespaces的一系列文章中,我们将会解开它的复杂性,我们将会深入的研究shared subtrees特性,这个特性以自动,受控的方式允许mount/unmount事件在mount namespaces之间传播事件.
mount namespace在2002出现在linux 2.4.19系统中,是第一个被添加到linux中的namespace.它隔离在namespace中进程所能看到的mount points(挂载点),换句话,每个mount namespace都有自己的mount points(挂载点)的列表,意味着不同namespace中的进程可以查看并且能够操作单个目录层次结构的不同视图.
当我们系统第一次启动的时候,有一个单独的mount namespace, 称为”initial namespace”,新的mount namespace是在调用系统函数clone()[在一个新的namespace中创建一个新的子进程]的时候指定CLONE_NEWS,或者调用unshare()[移动调用者到一个新的namespace]系统函数的时候被创建.当一个新的mount namespace被创建的时候,它会从调用方的mount namespace中复制mount point列表.
在clone()或unshare()调用之后,可以在每个namespace中通过mount()/unmount()独立的添加和删除mount points.默认情况下,mount point的变更仅对进程所在的namespace中的进程可见.
mount namespace可用于各种目的. 例如,可用为用户提供单独的文件系统的视图.另外为新的PID namespace挂载/proc文件系统,而不会对其他进程产生副作用,chroot()式的隔离实现一个process拥有一个单独的文件目录结构.在某些情况下,mount namespace与bind mounts相结合.
一旦完成mount namespaces的实现,用户空间程序员就遇到了一个可用性问题:mount namespaces在namespace之间提供了太多的隔离。例如,假设将新磁盘加载到光盘驱动器中。在原始实现中,使所有mount namespace中都可见的磁盘的唯一方法是在每个namespace中单独安装磁盘。在很多情况下,最好是执行一次单一的挂载操作,使磁盘在系统上的所有mount namespace(或可能是某些子集subtree)中都可见。
由于刚刚描述的问题,shared subtree功能被添加到Linux 2.6.15(2006年初,大约是mount namespace初始实现后的三年)。shared subtree的主要优点是允许在名称空间之间自动,受控地传播mount和unmount事件。这意味着,将光盘安装在一个mount namespace中可以在所有其他namespace中触发该磁盘的安装。
在shared subtree功能下,每个mount point都会标记一个“propagation type”[“传播类型”],该propagation type 确定在此mount point下创建和删除的mount point是否传播到其他mount point。有四种不同的”propagation type”:
这里有几点需要强调的
对等组是一组mount points,它们将挂载和卸载事件相互传播。当在创建新namespace的时候,传播类型为shared的mount point被复制,或者被使用作为bind mount的源(bind mount的详细内容见bind mount).或者将其用作绑定安装的源,则对等组会获取新成员。(对于绑定挂载,细节比我们在这里描述的更为复杂;细节可以在内核源文件documentation / filesystems / sharedsubtree.txt中找到),在这两种情况下,新的挂载点成为同一对等成员,作为现有的挂载点。相反,挂载点在卸载时不再是对等组的成员,无论是显式地还是隐式地,在挂载namespace最后一个成员进程终止或被移动到另一个namespace,都将导致被拆除。
通过一个例子来就讲解
假设shell运行在initial mount namespace中,我们首先创建出来一个private的root mountPoint,另外创建两个共享的mount points
#mount –make-private /
#mount –make-shared /dev/sda3 /x
#mount –make-shared /dev/sda5 /y
在另外一个终端中,使用unshare来创建一个新的mount namespaces
#unshare -m –propation unchanged sh
-m 表示创建一个新的mount namespaces
–propagation unchanged 表示切换到地一个terminial中,我们从/x mountpoint来创建bind mountPoint
#mkdir /z
#mount –bind /x /z
存在两个peer groups
1. 第一个peer groups包含x,x’(x’是创建第二个namespace的时候自动复制), z(从源mount point x中bind mount创建出来的)
2.第二个peer groups包含y,y’
注意bind mount z并没有复制到第二个namespace中欧你个,是因为父mount/被标注为private
通过/proc/PID/mountinfo检查传播类型以及peergroups
/ proc / pid / mountinfo文件显示有关进程pid所在的mount namespace中的mount points的一系列信息。驻留在同一个mount namespace中的所有进程将在该文件中看到相同的视图。通过此文件旨在提供有关挂载点的更多信息,而不是使用旧的,不可扩展的/ proc / pid / mounts文件。包含在此文件的每个记录中的是一组(可能为空)的所谓“可选字段”,它显示有关每个装载的传播类型和对等组(用于共享装载)的信息。
对于共享挂载,/ proc / pid / mountinfo中相应记录中的可选字段将包含格式为shared:n的标记。在这里,shared:n指示挂载与对等组共享传播事件。n是对等体组的标识,是该唯一标识对等体组的整数值。这些ID从1开始编号,并且当同行组因为其所有成员离开组而离开时可以被回收。所有属于同一对等组的成员的挂载点将显示/ proc / pid / mountinfo文件中具有相同n的共享:n标记。因此,例如,如果我们在上面的例子中讨论的第一个shell中列出/ proc / self / mountinfo的内容,我们会看到以下内容(使用一些sed过滤来修剪输出中的一些不相关的信息):
#cat /proc/self/mountinfo | sed 's/ - .*//'
61 0 8:2 / / rw,relatime
81 61 8:3 / /X rw,relatime shared:1
124 61 8:5 / /Y rw,relatime shared:2
228 61 8:3 / /Z rw,relatime shared:1
从这个输出中,我们首先看到根挂载点是私有的。这是由可选字段中没有任何标签指示的。我们还看到,挂载点/ x和/ z是同一对等组(共用ID为1)中的共享挂载点,这意味着挂载和卸载这两个挂载点中的任何一个都会传播到另一个挂载点。mount / y是不同对等组(id 2)中的共享挂载,根据定义,挂载不会将事件传播到对等组1中的挂载或从挂载中传播事件。
在第二个shell中运行命令
#cat /proc/self/mountinfo | sed 's/ - .*//'
147 146 8:2 / / rw,relatime
221 147 8:3 / /X rw,relatime shared:1
224 147 8:5 / /Y rw,relatime shared:2
再次,我们看到根挂载点是私有的。那么我们看到/ x是对等组1中的一个共享挂载,即与初始挂载名称空间中的挂载/ x和/ z相同的对等组。最后,我们看到/ y是对等组2中的共享挂载,即与初始挂载名称空间中的挂载/ y相同的对等组。最后需要注意的是,在第二个命名空间中复制的挂载点具有与初始命名空间中相应挂接的id不同的唯一id。
由于情况有点复杂,我们迄今为止避免讨论新的挂载点的默认传播类型。从内核的角度来看,创建新设备的默认设置如下:
如果装载点具有父级(即,它是非根装入点),并且父级的传播类型是ms_shared,则新装入的传播类型也是ms_shared。否则,新安装的传播类型为ms_private。
根据这些规则,根挂载将是ms_private,所有后代挂载默认也是ms_private。然而,ms_shared可以说是更好的默认值,因为它是更常用的传播类型。因此,systemd将所有安装点的传播类型设置为ms_shared。
因此,在大多数现代Linux发行版中,默认传播类型实际上是ms_shared。但这并不是关于这个主题的最后一句话,因为util-linux unshare实用程序也有某些话要说。当创建一个新的挂载名称空间时,unshare假定用户需要一个完全隔离的名称空间,并通过执行与以下命令(将根目录下的所有挂接以递归方式标记为私有)的等效命令来使所有挂载点保持私有状态:
#mount --make-rprivate /
为了防止这种情况,我们可以在创建新的名称空间时使用其他选项:
#unshare -m –propagation unchanged
Kubernetes 1.8 引入了feature MountPropagation, 允许pod中的container之间或者同一个node上的pod共享volumes. 如果该feature MountPropagation=disable,那么volume mounts的信息如上面所说的将不会在Pods间传播.通过设置kube-apiserver以及kubelet的启动参数–feature-gates中MountPropagation=true开启该feature. 之后设置Container.volumeMounts中mountPropagation字段来设置mount事件的传播.可以设置的值为:
如果features.MountPropagation disabled的情况下,volume的mount共享模式默认为为private
如果features.MountPropagation enable的情况下,volume的mount共享模式默认为rslave,在volumeMount中可以指定rslave,rshared模式
Notice:Bidirectional mount propagation传播是危险的。它可能会损坏主机操作系统,因此它只能在特权容器中使用。强烈建议熟悉Linux内核行为。另外,容器在Pod中创建的任何卷挂载必须在容器终止时销毁(卸载)。
通过对kubernetes中Pod的Container设置mountPropagation字段,表现在docker的container中可以通过docker inspect containerId来查看
{
"Type": "bind",
"Source": "/media",
"Destination": "/meida",
"Mode": "ro,rshared",
"RW": false,
"Propagation": "rshared"
},
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/3742e4a0-268a-11e8-98f1-fa163e88c3bb/volumes/kubernetes.io~secret/default-token-jcw4c",
"Destination": "/var/run/secrets/kubernetes.io/serviceaccount",
"Mode": "ro,Z,rslave",
"RW": false,
"Propagation": "rslave"
},
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/3742e4a0-268a-11e8-98f1-fa163e88c3bb/etc-hosts",
"Destination": "/etc/hosts",
"Mode": "Z",
"RW": true,
"Propagation": "rprivate"
},
参考文献
https://lwn.net/Articles/689856/
https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
http://blog.csdn.net/quqi99/article/details/43087001