TenForward

技術ブログ。はてなダイアリーから移転しました

unshare コマンドがマウントプロパゲーションをまともに扱うような変更を入れていた

全国10万人の unshare でコンテナを作るのが好きなコンテナマニアの方々が systemd のおかげで余計な処理をさせられてたのですが、いつの間にか unshare(1) コマンド側であるべき姿に戻す処理が追加されていたというお話です。

結論

回りくどい話が嫌いな方も多いかと思うので、最初に結論を簡潔にまとめときます。ここだけ読めば結論はわかります。

前提として、

  • kernel デフォルトではマウントプロパゲーションは private
  • 8 年ほど前に systemd で起動時に shared にするように処理が追加されてる
  • このため unshare --mount で新たに Mount Namespace を作って、その中でマウント処理を行ってもホスト側でもそのマウントが見えるようになっていた

というような状態でした。ところが unshare --mount を実行したら当然 Namespace 内のマウントは他から見えないはずと思っていたユーザーが多く、直感的でないため、

  • unshare コマンドのデフォルトで Mount Namespace を作る際に private を指定するような変更が加わっていた

ということです。つまりホスト側で systemd がマウントプロパゲーションを shared に設定しても、unshare --mount して Namespace を作ったら、ちゃんと private になって他の Namespace からはマウントは見えないよ、となっています。

回りくどい話が嫌いな人はここまで読めば大丈夫です。

詳細

もう 8 年とか前の話なのですが、systemd が加えたこの変更、

github.com

kernel ではマウント時のプロパゲーションのデフォルトは private のはずですが、なぜかいつしか shared でマウントされるようになっててハマった人がいるのは、この変更のせいでした。

これは /proc/self/mountinfo なんかを見るとわかります。

$ grep '/ / ' /proc/self/mountinfo 
30 1 252:2 / / rw,relatime shared:1 - ext4 /dev/vda2 rw

sharedの文字が見えますね。ちなみにこのような制御を行わないシンプルな sysvinit を採用している Plamo Linux だと private でマウントされています。

$ grep '/ / ' /proc/self/mountinfo
22 1 259:6 / / rw,relatime - ext4 /dev/nvme0n1p6 rw

この変更のせいで、unshare --mount と新たに Mount Namespace を作って、Namespace 内でマウントしたにも関わらず、別の Namespace(unshareする前の Namespace)でマウントが共有されてしまっていてハマったりしたものです。例えば、

kernhack.hatenablog.com

私の連載でも後日補足を入れる羽目になってます。

gihyo.jp


で、まあこの辺りを色々あって見直していたら、イマドキは systemd 環境で Mount Namespace を新たに作ってもマウントが共有されなくなっています。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:    20.04
Codename:   focal
$ grep vda2 /proc/self/mountinfo 
30 1 252:2 / / rw,relatime shared:1 - ext4 /dev/vda2 rw (←確かにshared)
$ sudo unshare --mount /bin/bash
# grep vda2 /proc/self/mountinfo 
437 436 252:2 / / rw,relatime - ext4 /dev/vda2 rw (←sharedの文字が消えてる!)

このように private になってる感じです。試しにこの Namespace でマウントしてみると、

# losetup /dev/loop6 test.img 
# mount /dev/loop6 /mnt
#  grep loop6 /proc/self/mountinfo 
478 437 7:6 / /mnt rw,relatime - ext4 /dev/loop6 rw

マウントされていますが、ホスト環境で見ると、

$ grep loop6 /proc/self/mountinfo
$

と見えません。つまり private になってるってことですね。

誰が変わってしまったんだろう?と調べてみると、コレでした!util-linux のコミットです。

github.com

After "unshare --mount" users assume that mount operations within the new namespaces are unshared (invisible for the rest of the system).
Unfortunately, this is not true and the behavior depends on the current mount propagation setting. The kernel default is "private", but for example systemd based distros use "shared". The solution is to use (for example) "mount --make-private" after unshare(1).
I have been requested many times to provide less fragile and more unified unshared mount setting by default to make things user friendly.
The patch forces unshare(1) to explicitly use MS_REC|MS_PRIVATE for all tree by default.

超訳

"unshare --mount" なんてマニアックなコマンドを使うヤツは当然新しい Namespace ではマウントは共有されないと思ってるよね。
不幸なことにそんなことはなくて、今どんなマウントプロパゲーションの設定になってるかによるんだよね。カーネルのデフォルトが "private" なのに、クソsystemdのヤツが "shared" に設定してる。このクソ設定に対抗するには unshare のあとに "mount --make-private" って実行するしかないよね。
オレは何度もデフォルトでこんなマニアックなユーザーがわかりやすいようにちゃんとした共有されないマウント設定をすべきって頼まれてきた。
このパッチでソレに応えて unshare では明確に "MS_REC|MS_PRIVATE" を使うようにしたぜ。

man unshare にもその辺りの記載があります。

unshare since util-linux version 2.27 automatically sets propagation to private in a new mount namespace to make sure that the new namespace is really unshared. It's possible to disable this feature with option --propagation unchanged. Note that private is the kernel default.

ここにあるように --propagation unchanged を指定して unshare を実行すると、

$ sudo unshare --mount --propagation unchanged /bin/bash
# grep vda2 /proc/self/mountinfo 
438 436 252:2 / / rw,relatime shared:1 - ext4 /dev/vda2 rw

確かにホスト環境と同じように shared でマウントされたままです。この状態で Namespace 内でマウントすると、

# mount /dev/loop6 /mnt
# grep loop6 /proc/self/mountinfo 
479 438 7:6 / /mnt rw,relatime shared:350 - ext4 /dev/loop6 rw

shared です。ホスト側でも

$ grep loop6 /proc/self/mountinfo 
480 30 7:6 / /mnt rw,relatime shared:350 - ext4 /dev/loop6 rw

とマウントが見えます。

(関連する Issue)

github.com