マウント名前空間と伝播の方式
一言でいうと
/proc/self/mountinfoは、「このプロセスが見るマウントツリー全体」を持っており、そこには、mountコマンドが見せてくれない伝播タイプ(propagation)まで書かれています。
なぜ必要なのか
コンテナを使い始めると、マウントが急に難しくなります。ホストで取り付けたボリュームがコンテナに見えなかったり、コンテナの中で取り付けたものがホストに漏れたり、コンテナを消したのにマウントが残っていたりします。これらの現象は、すべてマウント名前空間と伝播タイプで説明できます。
どう動くのか
mountinfoを読む
36 25 0:31 / /sys/fs/cgroup rw,nosuid,nodev,noexec shared:9 - cgroup2 cgroup2 rw
[1][2][3] [4] [5] [6] [7] [8] [9] [10] [11]
| フィールド | 意味 |
|---|---|
| 1 | マウントID |
| 2 | 親マウントID |
| 3 | major:minorのデバイス番号 |
| 4 | 元の中でのルート。バインドマウントなら、ここは/ではありません |
| 5 | マウントポイント |
| 6 | マウントオプション |
| 7 | 任意フィールド: 伝播タイプ(shared:、master:、propagate_from:、なければprivate) |
| 8 | 区切り文字- |
| 9–11 | ファイルシステムのタイプ、ソース、上位オプション |
フィールド4が、バインドマウントを見分けるカギです。同じデバイスのサブディレクトリを取り付けたなら、そこに/の代わりにそのサブパスが書かれます。
伝播タイプ4種類
| タイプ | mountinfoの表示 | 動作 |
|---|---|---|
| private | (表示なし) | このマウントの変化が、どこにも伝播しない |
| shared | shared:N |
双方向の伝播。ここでマウントすればペアにも見え、逆も同じ |
| slave | master:N |
単方向。マスターの変化は受け取るが、自分の変化は送らない |
| unbindable | unbindable |
バインドマウントの元になれない |
コンテナランタイムがボリュームを取り付けるときにrslaveやrprivateを使う理由が、ここにあります。コンテナの中で誤って取り付けたマウントがホストに漏れると困り、逆に、ホストが取り付けたものは見えなければならない場合が多いからです。
tmpfs
メモリ上のファイルシステムです。再起動すると消えます。
tmpfs /var/cache/app tmpfs size=256M,mode=1777,noexec,nosuid,nodev 0 0
size=を必ず指定します。ない場合の既定値は物理メモリの半分で、誰かが大きなファイルを書くと、システム全体がメモリ圧迫を受けます。- tmpfsはスワップに出ることがあります。本物のRAMディスクがほしいなら
ramfsですが、ramfsはサイズの制限がなく、さらに危険です。 mode=1777はスティッキービットです。/tmpのように、全員が書き込めるが、他人のファイルは消せないようにします。
systemdの.mountユニット
fstabの代わりに、ユニットファイルでもマウントを定義できます。ユニット名は、マウントポイントのパスをエスケープしたものでなければなりません。
# /etc/systemd/system/var-cache-app.mount
[Unit]
Description=Application cache (tmpfs)
[Mount]
What=tmpfs
Where=/var/cache/app
Type=tmpfs
Options=size=256M,mode=1777,noexec,nosuid,nodev
[Install]
WantedBy=local-fs.target
/var/cache/app → var-cache-app.mountです。先頭のスラッシュは除き、残りのスラッシュはハイフンに変えます。名前がパスと合わないと、systemdがユニットを拒否します。systemd-escape --path /var/cache/appで、正確な名前を得られます。
マウントが消えたり見えなかったりするとき
バインドマウントとtmpfsは単純に見えますが、伝播と名前空間のために、予想と違う動作をする場合があります。
コンテナの中では、あとから取り付けたマウントが見えません。ホストで/mnt/dataに新しいディスクをマウントしても、すでにそのパスをバインドしてあるコンテナには現れません。伝播タイプがprivateだと、以降のマウントが伝わらないからです。KubernetesでmountPropagation: HostToContainerを使う理由が、これです。
findmnt -o TARGET,SOURCE,PROPAGATION /mnt/data
tmpfsはメモリを使います。dfにはファイルシステムとして見えますが、実際にはRAMで、コンテナではそのメモリがcgroupの上限に含まれます。/tmpをtmpfsにして大きなファイルを書くと、OOMで死にます。サイズは必ず決めます。
tmpfs /tmp tmpfs size=512M,mode=1777,nosuid,nodev 0 0
空のディレクトリにマウントしないと、元の内容が隠れます。そのファイルは消えたのではなく、下に敷かれたまま容量を占めます。取り戻すには、マウントを外すか、別の場所にバインドして見ます。
mkdir /mnt/under && mount --bind / /mnt/under
ls /mnt/under/mnt/data # 가려진 원래 내용
umountできないときは、誰が使っているかを見ます。
lsof +f -- /mnt/data
fuser -vm /mnt/data
umount -l /mnt/data # 마지막 수단: 이름만 떼고 나중에 정리한다
-l(lazy)は、開いているファイルが閉じられるまで実際には取り付けられたままなので、ディスクを物理的に取り外す状況では安全ではありません。
バインドマウントに付けるオプションは、2回指定する必要があります。mount --bind -o roは、Linuxでは読み取り専用になりません。マウントしたあとに、もう一度指定する必要があります。
mount --bind /src /dst
mount -o remount,ro,bind /dst
現場での姿
コンテナを消したのにマウントが残ります。伝播タイプがsharedの状態でコンテナがマウントを作ると、ホストの名前空間に漏れ出します。コンテナが死んでも、そのマウントはホストに残ってディスクを握り続けます。findmntで探して、手動で片づける必要があります。
tmpfsのサイズを決めずにOOMが起きます。size=なしで作ったtmpfsにログがたまり、メモリを食いつくしました。ディスクではなくメモリだという事実を、忘れやすいです。
次のラボですること
/proc/self/mountinfoを直接パースしてマウントツリーと伝播タイプを表にし、バインドマウントとtmpfsのfstabの行を作成し、.mountユニットファイルを正確な名前で作成します。