ファイルシステムのマウントと名前空間の伝播
コンテナでホストのデバイスだけ見せて他は隔離する、その匙加減はマウント伝播で決まります。shared/slave/private/unbindableの4種別とbind/rbindの原理を、マウントツリー構造から正確に解き明かします。
- 1.マウントは「マウントポイント」のツリーとしてカーネル内に保持され、各マウントは peer グループへのイベント波及を決める shared/slave/private/unbindable の伝播種別を持つ。
- 2.shared は peer 間で mount/umount を双方向に伝播、slave はマスタからの片方向受信、private は無伝播、unbindable は private かつ bind 不可。bind は既存サブツリーを別地点へ写し、rbind は配下のマウントも再帰的に複製する。
- 3.コンテナランタイムは起動時にルートを再帰 private 化してホストの変更漏れを断ち、必要な箇所だけ slave/shared に調整して片方向共有や動的マウント反映を実現する。
マウントは「ポイント」のツリーである
mount というと「デバイスをディレクトリに繋ぐ」操作だと捉えがちですが、カーネル内部の実体は マウントポイントのツリー です。ある ファイルシステム のルートを、既存ツリー上の任意のディレクトリ(マウントポイント)に接ぎ木すると、その地点から下の名前解決が新しいファイルシステムへ切り替わります。マウントは入れ子にでき、あるマウントの上にさらに別のマウントを重ねられます。結果として、プロセスが見るファイル空間は 複数のマウントが連結した1本のツリー になります。
カーネルは各マウントを struct mount で表し、これは「どのスーパーブロック(FS実体)を、親マウントのどの地点(マウントポイントの dentry)に繋いだか」を保持します。重要なのは、同じスーパーブロックを複数の地点にマウントできる ことと、マウントは mount 名前空間 ごとに別インスタンスとして保持される ことです。/proc/self/mountinfo を読むと、各マウントの ID・親 ID・マウント元・伝播種別まで一覧できます。
「マウントポイント」はツリー上の接続地点(ディレクトリ)で、「マウント」はそこに繋がれたファイルシステムの実体側です。1つのディレクトリに複数回マウントを重ねると、見えるのは最後(最上位)のマウントだけで、下のマウントはスタックのように隠れます。umount するとひとつ前が再び現れます。
共有サブツリー:伝播の4種別
マウント名前空間を CLONE_NEWNS で複製すると、親のマウントツリーが コピー されます。ここで問題になるのが、「片方の名前空間で新しくマウントしたとき、それをもう片方にも反映するか」です。これを制御するのが マウント伝播(mount propagation)、別名 共有サブツリー(shared subtrees) の仕組みです。各マウントは次の4種別のいずれかを持ちます。
| 伝播種別 | mount/umount イベントの波及 | 典型用途 |
|---|---|---|
| shared | 同じ peer グループ内の全メンバへ双方向に伝播する | ホスト全体で揃えたいマウント、リムーバブルメディアの自動反映 |
| slave (依存) | マスタからは受信するが、自分の変更はマスタへ返さない(片方向) | ホストのデバイス追加は見せたいが、コンテナ内の変更は外へ漏らさない |
| private | 送受信とも一切しない。完全に独立 | コンテナの隔離マウント、変更を閉じ込めたい領域 |
| unbindable | private かつ、このマウントを bind マウントの元にできない | 再帰 bind 時に複製してほしくない地点(例: コンテナ用テンプレート) |
ここで言う peer グループ とは、互いに shared な関係で結ばれたマウントの集合です。同じ peer グループに属するマウントの1つで mount/umount が起きると、そのイベントが他の全メンバの対応する地点へ複製されます。slave は peer グループに対して 片方向の従属 であり、マスタ側 peer グループのイベントは受け取るが、自分の側で起きたイベントはマスタへ送り返しません。
伝播種別は「ファイルシステム」ではなく「個々のマウント」の属性です。同じデバイスでも、マウントした地点ごとに別の種別を持てます。さらに名前空間を複製した瞬間に、コピー先のマウントがどの種別になるかは、コピー元が shared かどうかで変わります。shared なマウントは複製先でも同じ peer グループに入り、private なら複製先と縁が切れます。
なぜ4種別が必要か:双方向と片方向の使い分け
伝播種別の存在意義は、情報の流れ方向を地点ごとに設計する ことにあります。具体例で考えます。ホストに USB メモリを挿すと、/media/usb に自動マウントされるとします。
- この地点が shared なら、ホストの mount イベントが peer グループを通じて全コンテナへ伝播し、各コンテナの対応地点にも USB が現れます。逆にコンテナ内で何かをマウントすると、それがホストや他コンテナへ漏れます。これは通常望ましくありません。
- そこで多くのランタイムは、ホスト側を shared、コンテナ側を slave にします。すると「ホスト → コンテナ」の片方向だけが通り、ホストの USB はコンテナに見えるが、コンテナ内のマウントはホストへ波及しません。これが「デバイスは見せたいが隔離はしたい」要求の答えです。
- 完全に切り離したい領域は private にします。
/procを私的にマウントし直したコンテナの内部マウントが、決してホストへ漏れないのはこの種別のおかげです。
slave は「マスタの更新は追従するが、自分の更新は閉じ込める」という非対称な共有です。コンテナでホスト由来のマウント(デバイス、設定ボリュームなど)を最新に保ちつつ、コンテナが余計なマウントを生やしてもホストを汚さない、という現代コンテナの定石を支えています。なお slave かつ shared(slave で受けつつ自分の子 peer グループへは shared)という合成状態も取れます。
bind マウントと再帰 bind
伝播と並ぶもう一つの基本が bind マウント です。これは「既にツリー上に存在するサブツリーを、別の地点にもう一度見せる」操作です。新しいファイルシステムを繋ぐのではなく、同じファイル群への2つ目の窓 を作ります。
# /data の中身を /srv/app/data にも見えるようにする
mount --bind /data /srv/app/data
ここで重要な区別が bind と rbind(再帰 bind) です。素の --bind は 指定した1つのマウントだけ を写します。そのサブツリーの内側にさらに別のマウント(入れ子マウント)があっても、それらは写りません。一方 --rbind は 配下のマウントツリー全体を再帰的に複製 します。
mount --bind /a /b # /a 直下のFSだけ複製。/a/x にあった別マウントは /b/x に現れない
mount --rbind /a /b # /a 配下の入れ子マウントもすべて /b 配下に再現される
bind マウントは権限属性を後付けで変えるためにも使われます。mount --bind /x /x の後に mount -o remount,bind,ro /x とすると、同じ実体を読み取り専用の窓として固定できます。コンテナで「このディレクトリだけ ro で渡す」ときの典型手段です。元の窓は書き込み可能なまま、別地点だけ ro にできるのは、属性がマウント単位だからです。
伝播と bind は直交する概念ですが、組み合わさって効きます。rbind で複製したとき、複製先の各マウントの伝播種別は 元マウントの種別と、複製操作の文脈 で決まります。元が shared なら複製先も同じ peer グループに加わり双方向に繋がります。これが意図しない漏れの原因になりやすいため、コンテナ構築では rbind の前後で種別を明示制御するのが鉄則です。unbindable は、こうした再帰 bind の際に「この地点は複製対象から外す」ことを宣言し、不要なマウントの増殖を防ぎます。
コンテナでの活用:再帰 private 化から slave 調整へ
ここまでの部品が、コンテナの安全なファイルシステム隔離を成立させます。名前空間と cgroups によるコンテナ化で、マウント名前空間がまず分離されますが、複製直後はホストと shared な関係が残っていることがあります。そこでランタイムは典型的に次の順序で組み立てます。
1. unshare(CLONE_NEWNS) # mount 名前空間を分離(ツリーはまだ親と共有関係を持ちうる)
2. mount --make-rprivate / # ルート配下を再帰 private 化 → ホストへの漏れを全断
3. 必要なホスト資源を rbind で持ち込み、個別に slave/ro へ調整
4. pivot_root(2) で新ルートへ切替、旧ルートを umount して参照を断つ
ステップ2の 再帰 private 化 が安全の要です。これを怠ると、コンテナ内のマウント操作がホストの peer グループへ伝播し、ホストや兄弟コンテナのマウントツリーを汚染し得ます。逆に、ホストのデバイス更新を見せたい特定地点だけを後から slave へ戻すことで、「全体は隔離・一部だけ片方向追従」という細かな制御が実現します。ルート切り替えに chroot ではなく pivot_root(2) を使うのは、旧ルートを退避してから umount でき、旧ルートへの参照を完全に断てる ためです(脱出経路を残さない)。
ボリュームを mount --make-rshared のままコンテナへ rbind で渡すと、コンテナ内でその配下にマウントした内容がホストや他コンテナへ伝播します。マウント伝播を悪用すると、本来見えないはずのホストパスへマウントを波及させる攻撃面になり得ます。共有が本当に必要な地点だけを shared にし、原則は private、片方向で十分なら slave、というのが安全な既定です。
このマウントツリーの隔離は、各プロセスが自分専用のファイル空間を持つことを意味し、ファイルディスクリプタ を通じた実際の I/O 経路とは独立に「見え方」だけを変えます。読み取り共有・書き込み分離を担う OverlayFS が「中身のレイヤ」を、マウント伝播が「ツリーのつながり方」を受け持つ、と整理すると役割分担が明確になります。
頻出の対比は伝播4種別の方向性です。shared=双方向 / slave=マスタから片方向受信 / private=無伝播 / unbindable=private かつ bind 元になれない。bind と rbind の差は「配下の入れ子マウントを再帰複製するか否か」。コンテナの定石は「(1) 名前空間分離 → (2) make-rprivate でルートを再帰 private 化 → (3) 必要箇所だけ slave/ro 調整 → (4) pivot_root」。「なぜ make-rprivate が必要か=マウント変更のホスト漏れを断つため」を一言で言えれば十分です。
まとめ
マウントの実体は マウントポイントのツリー で、各マウントは mount 名前空間 ごとに別インスタンスとして保持される。名前空間を複製したときのイベント波及を決めるのが マウント伝播 で、shared(双方向)/ slave(片方向受信)/ private(無伝播)/ unbindable(private かつ bind 不可) の4種別を地点ごとに設計する。bind は既存サブツリーへの2つ目の窓を作り、rbind は配下の入れ子マウントまで再帰複製する。コンテナランタイムは「ルートを再帰 private 化して漏れを断ち、必要な箇所だけ slave/ro に調整し、pivot_root で旧ルートを切り離す」流れで安全な隔離を組む。中身のレイヤ共有を担う OverlayFS と、ツリーのつながり方を制御するマウント伝播の両輪で、現代コンテナのファイルシステムは成り立っている。
OS Article
ファイルシステムのマウントと名前空間の伝播を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
マウント
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
shared は peer 間で mount/umount を双方向に伝播、slave はマスタからの片方向受信、private は無伝播、unbindable は private かつ bind 不可。bind は既存サブツリーを別地点へ写し、rbind は配下のマウントも再帰的に複製する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「マウント / 名前空間」に近いか確認する。
- 強みである「マウントは「マウントポイント」のツリーとしてカーネル内に保持され、各マウントは peer グループへのイベント波及を決める shared/slave/private/unbindable の伝播種別を持つ。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。