mount propagation

特定のマウント処理を他のディレクトリやnamespaceで認識できるようにするかどうかを、制御する仕組みとしてshared subtreeが存在する。

例としてプロセスのカレントディレクトリや、chrootで変更した配下に対して、外部ディレクトリのマウント処理を反映したい場合などが挙げられる。 また特定のマウントポイント配下に更にマウントポイントを追加した場合に、マウント元にディレクトリには反映されるかどうか などがある。

これらをshared subtreeのpropagationという仕組みによって、mountに関するインベントの伝搬を制御することが可能となる。

Peer group

propagationではpeer groupという概念が存在しpeer group間でマウントイベントが伝搬される。 peer groupは複数のmount pointの集合であり、以下を契機にメンバが追加される。

  • 新しいnamespaceの作成
    • これによってマウントポイントが新しいnamepsaceに複製されるので、それぞれのマウントポイントは同一のpeer groupに所属する
  • bind mountのソースとして利用されたとき

メンバが削除されるトリガーは以下のものがある - unmountの実行 - namespaceの削除 - namespace内のプロセスがすべて終了した - namespace内のプロセスすべて該当namespace外に移動した

4つのpropagation type

マウントポイントはpropagation typeという属性を持っている。 これはマウントイベントをpeer group内に伝搬するかどうかを制御するために設定され4つ種類がある。

  • shared
  • private
  • slave
  • unvindable

そしてデフォルトでは、親のマウントポイントのpropagation typeを継承する。

それぞれをマウントポイントに設定した場合にどのような挙動になるのかを確認する。

shared

同じピアグループ内でマウントイベントは伝搬され共有される。 マウントポイント配下にマウントポイントが追加削除された際は、同じpeer group内のマウントポイント配下でも反映される。

オプションとしてmountコマンドに--makes-sharedを付加する。

下記の例ではまず、/A/Bにbind マウントした。

$ mkdir /A /B
$ touch /A/test

$ tree /A /B C
/A
└── test
/B

$ mount --bind --make-shared /A /B

$ tree /A /B
/A
└── test
/B
└── test

各マウントポイントの情報は/proc/{PID}/mountinfoで参照できる. 下記の例ではshared:1となっておりsharedはpropagation typeがsharedで、peer groupが1であることを表している。この数字が同じであれば同じpeer groupに所属する。

# /proc/self/mountinfo 実行結果を抜粋
85 40 8:1 /A /B rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

/Cを作成し、/B/mntにマウントする。すると/A配下でもmnt/test2を参照することがき、マウント情報が伝搬され反映されていることが確認できる。

$ mkdir /C /B/mnt
$ touch /C/test2

$ tree /A /B /C
/A
└── test
/B
├── mnt
└── test
/C
└── test2

$ mount --bind --make-shared /C /B/mnt/

$ tree /A /B /C
/A
├── mnt
│   └── test2
└── test
/B
├── mnt
│   └── test2
└── test
/C
└── test2

マウント情報にはコマンド実行した/Aをマウントポイントとした情報以外に、伝搬された情報である/A/mntも記載されており、いずれのpeer group番号は1となっている。

# /proc/self/mountinfo 実行結果を抜粋
85 40 8:1 /A /B rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
91 85 8:1 /C /B/mnt rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
92 40 8:1 /C /A/mnt rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

private

aharedとは対象的にピアグループ内にイベントは共有されない。

/A/Bにbind mountして作業前の初期状態とする。 マウンポイントをprivateとするためには、オプションとして--make-privateをmountコマンドに使用する。

$ tree /A /B /C
/A
└── test
/B
/C
└── test2

$ mount --bind --make-private /A /B
tree /A /B /C
/A
└── test
/B
└── test
/C
└── test2

マウント情報を確認するとprivateではpeer groupに関する情報は記載されていないことがわかる。

# /proc/self/mountinfo 実行結果を抜粋
85 40 8:1 /A /B rw,relatime - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

/C/B/mnt/にマウントしても、/A/mnt/test2は参照できず/Aには反映されていない。

$ mount --bind /C /B/mnt/

$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
│   └── test2
└── test
/C
└── test2

マウント情報は実行したコマンドと同様の2種類のエントリが存在する。

# /proc/self/mountinfo 実行結果を抜粋
85 40 8:1 /A /B rw,relatime - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
87 85 8:1 /C /B/mnt rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

逆に/A/mnt/Cをマウントしてみるとこれも同じ結果となり反映されない

$ tree /A /B /C
/A
├── mnt
└── test
/B
/C
└── test2

$ mount --bind --make-private /A /B
$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
└── test
/C
└── test2
$ mount --bind /C /A/mnt/
$ tree /A /B /C
/A
├── mnt
│   └── test2
└── test
/B
├── mnt
└── test
/C
└── test2
85 40 8:1 /A /B rw,relatime - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
87 40 8:1 /C /A/mnt rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

slave

イベントの伝播は一方通行となる。同じpeer group内でマスタとなるマウントポイントが変更された場合に、スレーブ側のマウントポイントも変更される。 しかしその逆は発生しない。

マスタ側のディスクドライブマウントなどをスレーブ側で利用したい場合で、かつ逆にスレーブ側の変更で影響を与えたくない場合に利用する。

bindコマンドに--make-slaveオプションを利用して/A/Bにマウントする。

$ tree /A /B /C
/A
└── test
/B
/C
└── test2


$ mount --bind --make-slave /A /B
tree /A /B /C
/A
└── test
/B
└── test
/C
└── test2

マウント情報にはmaster:1

# /proc/self/mountinfo 実行結果を抜粋
85 40 8:1 /A /B rw,relatime master:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

/C/B/mntにsharedマウントすると、/A配下には反映されていない。

$ mkdir /B/mnt
$ mount --bind /C /B/mnt

$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
│   └── test2
└── test
/C
└── test2
85 40 8:1 /A /B rw,relatime master:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
87 85 8:1 /C /B/mnt rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

一旦/B/mntをunmountして元の状態に戻す。

$ umount /B/mnt/
$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
└── test
/C
└── test2

そして今度は逆に/A配下にマウントする。 この場合は/Aがmasterとなっているので、マウント情報は/Bに伝搬され/B/mnt/test2の存在が確認できる。

$ mount --bind /C /A/mnt/
$ tree /A /B /C
/A
├── mnt
│   └── test2
└── test
/B
├── mnt
│   └── test2
└── test
/C
└── test2

unvindable

privateと同様にマウントイベントの伝播はしないが、その挙動に加えてbind mountのソースとして利用することができなくなるという制約が追加される。

mountコマンドの実行時には--make-unbindableを付与する。ここでは/A/Bにマウントする。

$ tree /A /B /C
/A
└── test
/B
/C
└── test2

$ mount --bind --make-unbindable /A /B

$ tree /A /B /C
/A
└── test
/B
└── test
/C
└── test2

マウント情報にはunbindableであることが記されている。

85 40 8:1 /A /B rw,relatime unbindable - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

当然privateと同様の挙動のため、/C/B/mntにマウントしても/A/mntでは参照することはできない。

$ mkdir /B/mnt

$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
└── test
/C
└── test2

さらに/B/C/mntをマウントしようとしてもbind mountのソースとして利用できないため失敗する。

$ mkdir /C/mnt
$ tree /A /B /C
/A
├── mnt
└── test
/B
├── mnt
└── test
/C
├── mnt
└── test2

$ mount --bind /B /C/mnt/
$ mount: wrong fs type, bad option, bad superblock on /B,
       missing codepage or helper program, or other error

       In some cases useful info is found in syslog - try
       dmesg | tail or so.

unvindableは再起にマウントした際にマウントポイントが指数関数的に増加するのを防ぐ目的で利用されるようだ。

/A配下に2つのディレクトリを用意した。まずは/Aを配下のdir1にマウントする。

$ tree /A
/A
├── dir1
└── dir2

$ mount --rbind /A /A/dir1
/A
├── dir1
│   ├── dir1
│   └── dir2
└── dir2

そして次は/A/A/dir2にマウントする

$ mount --rbind /A /A/dir2
$ tree /A
/A
├── dir1
│   ├── dir1
│   └── dir2
│       ├── dir1
│       │   ├── dir1
│       │   └── dir2
│       └── dir2
└── dir2
    ├── dir1
    │   ├── dir1
    │   └── dir2
    └── dir2

マウントコマンドを2回実行したがこのときのmountinfoはこのようになっている。 手動実行分以外に再帰的なマウントポイントが増えていることがわかる。同時にこれを繰り返せばマウントポイントの量が指数関数的に増加するのも納得がいく。

そのために途中のマウントポイントをバインドマウント不可とすることで、爆発的な増加を防止する仕組みがunbindableというわけだ。

85 40 8:1 /A /A/dir1 rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
87 40 8:1 /A /A/dir2 rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
88 87 8:1 /A /A/dir2/dir1 rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
89 85 8:1 /A /A/dir1/dir2 rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota
90 89 8:1 /A /A/dir1/dir2/dir1 rw,relatime shared:1 - xfs /dev/sda1 rw,seclabel,attr2,inode64,noquota

参照