[Linux] symlinkとbind mountの実戦比較

はじめに

主に自宅サーバ用途として、2000年頃から長く使い続けてきたFreeBSDやLinux系OS。
mount や symlink は最初に環境を構築するとほぼ使わなくなり、余り深く調べてこなかったのと、 ローカルサーバ内でアプリ開発をするようになり、それに伴い、使用頻度が増えてき為、改めて再学習の備忘録を含め、記事に違いをまとめてみました。

仕組みの違い

  • symlink道案内の札
    解決はユーザ空間のパス解決で行われ、権限は逸脱できない
    壊れやすい(先が消えると“ダングリング”)。

  • bind mountOSに作る第2の入口(トンネル)
    解決はカーネルのマウントテーブルで行われ、同じ実体を別ルートに露出できる。
    リブートで消える(fstabやsystemdで永続化が必要)。

symlink(ln -s SRC DST)

  • 「DST を開こうとすると、実際は SRC を見に行ってね」というパス置換
  • 別inodeを持つ“リンク用のファイル”。中身は文字列(参照先パス)。
  • 参照先が消えると壊れる(dangling symlink)。
  • 相対/絶対パスが選べる。相対は移動に強いが、chroot/jail内だと意図通り届かないことも。

bind mount(mount –bind SRC DST)

  • 既存ディレクトリ/ファイル別の場所へ再マウントして見せる。
  • 同じinode2つ目のマウントポイントから参照する感じ(“同じ実体に別玄関”)。
  • ディレクトリもファイルもOK(ファイルbindも可能)。
  • rbindでサブマウントも再帰的に露出できる。
  • 読み取り専用にしたいときは mount -o remount,bind,ro DST。

2) 権限・セキュリティの差

観点 symlink bind mount
権限の超越 できない(参照先の権限が最終決定) できない(最終的には参照先の権限)が、“どこを見せるか”をOS側で制御できる
Webサーバのポリシー ApacheのOptions FollowSymLinksやnginxのdisable_symlinksに引っかかることあり シンボリックリンク判定に引っかからない(ポリシー回避に有効)
chroot/コンテナ symlinkはroot外(/)を直接指せないことが多い 必要なサブツリーだけを安全に注入できる(Dockerの-vに近い)
パス逃げ(../../) symlink自体はパス。パス検証が甘いとTOCTOUの温床に bind mountは露出範囲をマウントで固定。アプリ側のパス検証が楽
SELinux/AppArmor symlinkはラベル/プロファイル次第 元のラベルを引き継ぐls -Zで確認、必要ならchcon

実務TIP:Webでsymlink禁止(既定/ポリシー)な環境は多い。
そのときbind mountで“同じ中身”を見せるのが定番回避策。


3) 運用・ツールの差

観点 symlink bind mount
永続性 永続(ファイルとして残る) 揮発(再起動で消える→/etc/fstabやsystemdで永続化)
バックアップ/同期 tar/rsyncデフォでlinkとして扱う(辿らない設定が多い) “本物のディレクトリ”に見えるので二重バックアップに注意(除外指定が必要)
調査コマンド readlink -f DST で参照先を見る findmnt DST / mountpoint DSTマウント状況を確認
無限ループ事故 ln -s . dstなどで発生しがち 自己配下へマウントすると地獄(避ける)
ファイル監視 監視対象は参照先のinode 同じinodeなので挙動は自然(inotify等はそのまま動く)

rsync注意:bind mount は“普通のディレクトリ”扱い
–one-file-system は同一FS内なので効きにくい。明示的に除外しよう。


4) 具体例:今回のユースケース

Web:/home/www/app から
データ/コード:/var/hoge/data を読みたい

# 失敗例になりがち:Webサーバのsymlinkポリシーに阻まれることがある
ln -s /var/hoge/data /home/www/shared/
  • ApacheのFollowSymLinks無効や、nginxのdisable_symlinksに引っかかることがある。

bind mount(推奨)

sudo mkdir -p /var/shared/data
sudo mount --bind /home/www/hoge /var/shared/data
# 永続化:/etc/fstab
echo "/home/www/hoge  /var/shared/data  none  bind  0 0" | sudo tee -a /etc/fstab
  • Webからは /var/shared/data を読むだけ
  • 権限は元側に合わせ、必要ならACLを足す:
sudo setfacl -R -m u:www-data:rx /home/www/hoge

只読で見せたい(事故防止)

sudo mount --bind /home/www/hoge /var/shared/data
sudo mount -o remount,bind,ro /var/shared/data
# fstab例:最後に ,ro を追加
/home/www/hoge  /var/shared/data  none  bind,ro  0 0

systemdでスマートに永続化(fstab嫌い派)

  • ユニット名はパスを-でつないだもの:/var/shared/datavar-shared-data /etc/systemd/system/var-shared-data
[Unit]
Description=Bind mount paypay to web shared

[Mount]
What=/home/www/hoge
Where=/var/shared/data
Type=none
Options=bind,ro

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now var-shared-data

5) 使い分け(現場基準)

  • Web配下に“別場所の中身”を自然に見せたいbind mount

    • symlinkポリシーに阻まれない
    • 只読化できる/範囲を限定できる
  • 設定ファイルの差し替え、軽い参照symlink

    • 例:/etc/nginx/sites-enabled/etc/nginx/sites-available
    • 例:アプリ内のテンプレート差し替え
  • コンテナ/隔離空間へ必要最小限を露出bind mount一択

    • chroot/Docker/LXC でのベストプラクティス

6) デバッグ・確認コマンド小箱

# symlinkの行き先
readlink -f /var/shared/data

# bind mountの確認
findmnt /var/shared/data
mountpoint /var/shared/data

# inodeが同じか確認(SRCとDSTで同じ番号なら“同じ実体”)
stat -c '%i %n' /home/www/hoge
stat -c '%i %n' /var/shared/data

7) ブログ向けタイトル案(おまけ)

  • symlinkとbind mountの実戦比較:Web配下に“別の中身”を安全に見せる最短手順」
  • シンボリックリンクでは通らない夜:nginx/Apacheで詰まったらbind mountで突破」
  • C言語のポインタみたいに:Linuxのbind mountを図解で理解する」