mount namespace和shared subtrees

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相结合.

Shared subtrees

一旦完成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”:

  • ms_shared:该mount point与其“peer group”[“对等组”](下面会详细介绍)成员的其他mount point共享mount和unmount事件。当在此mount point下添加或删除mount point时,此更改将传播到对等组,以便在每个对等挂载点下也会发生mount或unmount。传播也会以相反的方向发生,因此对等组mount point上的mount和unmount事件也会传播到此mount point。
  • ms_private:这是shared[共享]挂载点的反面。mount point不会将事件传播到任何对等点,也不会从任何对等点接收传播事件。
  • ms_slave:这个传播类型位于shared[共享]和private[私有]之间。一个share mount[从属装载]具有master -一个shared peer group[共享对等组],其成员将mount和umount事件传播到shave mount[从属装载]。然而,从属挂载不会将事件传播到master对等组。--只接受mount,umount事件
  • ms_unbindable:该mount point不可绑定。就像private mount point一样,此安装点不会将事件传播到对等点或从对等点接受事件。此外,此挂载点不能成为绑定挂载操作的源。

这里有几点需要强调的

  • 首先是传播类型是每挂载点设置。在命名空间内,某些挂载点可能被标记为共享,而其他挂载点则被标记为私有(或从属或不可绑定)。
  • 传播类型决定在挂载点下挂载和卸载事件的传播。例如,如果在共享挂载x下,我们创建一个子挂载y,该子挂载将传播到对等组中的其他挂载点。但是,x的传播类型对y下创建和删除的挂载点不起作用;是否传播y下的事件将取决于为y定义的传播类型。类似地,当卸载x本身时是否传播unmount事件将取决于x的父装载的传播类型。
  • “事件”这个词在这里被用作抽象术语,意思是“发生了什么事”。事件传播的概念并不意味着在挂载点之间传递某种消息。而是,它表示一个挂载点上的一些挂载或卸载操作触发了一个或多个其他挂载点的匹配操作。
  • mount可以既是主对等组的从属设备,也可以与其自己的一组对等设备共享事件 – 即所谓的从共享安装。在这种情况下,挂载可能会接收来自主设备的传播事件,然后这些事件会传播到其对等设备。

Peer groups[对等组]

对等组是一组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 <cmd>

kubernetes中Mount propagation

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事件的传播.可以设置的值为:

  • HostToContainer- 这个volume mount将接收所有后续mount到volume或其任何子目录的mount事件。这是MountPropagation=true之后的默认模式。换句话说,如果host上在该volume上执行了mount操作,Container将会看到mount事件的变化。该模式等同于上面描述的rslave mount propagation.
  • Bidirectional - 此volume mount的行为与HostToContainer mount相同。另外,容器创建的所有volume mount将被传播回host以及所有使用volume的pod的所有容器。该模式等同于上面描述的rshared mount propagation.

如果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

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页