TenForward

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

LXDのシステムコールインターセプション機能を試す(マウント編)(2)

先のエントリーでは、コンテナ内から ext4 ファイルシステムのマウントができたのですが、実は security.syscalls.intercept.mount.shift という設定が効いていないようで、そこで時間切れでした(どこがうまく行ってないか書いてませんでしたが)。

tenforward.hatenablog.com

そこで Ubuntu 20.04 もリリースされたことですし、もう一度試してみましょう。

前回のおさらい

前回と同様に /dev/vdb というディスクが存在し、パーティションをひとつだけ作成し、ext4mkfs しています。テストのために事前にホスト側でマウントし、testfile というファイルをひとつだけ作ってあります。

$ ls -l /mnt/
total 16
drwx------ 2 root root 16384 Apr 29 07:38 lost+found
-rw-r--r-- 1 root root     0 Apr 29 07:56 testfile
karma@focal:~$ sudo umount /mnt

コンテナをひとつ作成して起動します。

$ lxc launch images:alpine/3.11 c1
Creating c1
Starting c1

バイス/dev/vdb1)をコンテナに割り当てます。

$ lxc config device add c1 vdb1 unix-block path=/dev/vdb1
Device vdb1 added to c1

システムコールインターセプションの設定を行います。shiftfs を有効にして、ext4 をマウントできるように許可します。

$ lxc config set c1 security.syscalls.intercept.mount true
$ lxc config set c1 security.syscalls.intercept.mount.shift true
$ lxc config set c1 security.syscalls.intercept.mount.allowed ext4
$ lxc restart c1
$ lxc config show c1
architecture: x86_64
config:
    : (snip)
  security.syscalls.intercept.mount: "true"
  security.syscalls.intercept.mount.allowed: ext4
  security.syscalls.intercept.mount.shift: "true"
    : (snip)
devices:
  vdb1:
    path: /dev/vdb1
    type: unix-block
    : (snip)

設定されているのが確認できましたので、マウントしてみます。

$ lxc exec c1 -- mount /dev/vdb1 /mnt
$ lxc exec c1 -- ls -lh /mnt
total 16K    
drwx------    2 nobody   nobody     16.0K Apr 29 07:38 lost+found
-rw-r--r--    1 nobody   nobody         0 Apr 29 07:56 testfile

ここまでが前回のおさらいです。

ShiftFS おさらい

マウントはできたのですが、ファイルシステム中のファイルの所有権がホストでは root:root だったのが、nobody:nobody となります。

これは User Namespace を使って、ホスト上のユーザーとコンテナ内のユーザーをマッピングしており、マッピングしている範囲外の id はコンテナ内では扱えず、その場合は id が /proc/sys/kernel/overflow{u,g}id の値になります。つまり nobody:nobody になるわけです。

$ cat /proc/sys/kernel/overflow{u,g}id
65534
65534

コンテナ内でイメージなどをマウントするには、ホスト上のユーザーが所有する uid/gid はコンテナ内で扱えませんので、このあたりを解決するのに ShiftFS というファイルシステムが開発されており、Ubuntu カーネルにはこの機能のパッチが適用されています。この辺りは

tenforward.hatenablog.com

tenforward.hatenablog.com

をどうぞ。

LXD で ShiftFS を使えるように設定

ここで、最初のコンテナに対して行った設定をもう一度見てみましょう。うまく働いてないなという security.syscalls.intercept.mount.shift はマウントの際に ShiftFS を使って uid/gid をコンテナ内の uid/gid に合わせる設定です。

しかし、先の例では nobody:nobody になってしまっていたので、この ShiftFS が効いていないことがわかります。

lxd の情報を確認してみると、

$ lxc info | grep shiftfs
    shiftfs: "false"

このように有効になっていないようです。

まずはホスト上で ShiftFS が有効になっているかを確認してみましょう。

$ lsmod | grep shiftfs
$

さすがにデフォルトでは shiftfs のカーネルモジュールはロードされないようです。このモジュールをロードして lxd を再起動してみましょう。lxd は snap パッケージで提供されているので、snap.lxd.daemon を再起動します。

$ sudo modprobe -v shiftfs
insmod /lib/modules/5.4.0-26-generic/kernel/fs/shiftfs.ko
$ lsmod | grep shiftfs
shiftfs                28672  0
$ sudo systemctl restart snap.lxd.daemon

これでどうでしょう?

$ lxc info | grep shiftfs
    shiftfs: "false"

おや?ダメですね…

調べてみると、snap の lxd の方に shiftfs が有効であると教えてあげないとダメなようです。

$ sudo snap set lxd shiftfs.enable=true
$ sudo systemctl reload snap.lxd.daemon

これでいけるかな?

$ lxc info | grep shiftfs
    shiftfs: "true"

有効になりました!! コンテナを再度作成してみましょう(再作成しなくてもよさそうな気がしましたが、lxd で ShiftFS が有効になったあとにコンテナを再作成しないとうまくいきませんでした)。

$ lxc launch images:alpine/3.11 c1
Creating c1
Starting c1
$ lxc config device add c1 vdb1 unix-block path=/dev/vdb1
Device vdb1 added to c1
$ lxc config set c1 security.syscalls.intercept.mount true
$ lxc config set c1 security.syscalls.intercept.mount.shift true
$ lxc config set c1 security.syscalls.intercept.mount.allowed ext4
$ lxc restart c1
$ lxc exec c1 -- mount /dev/vdb1 /mnt
$ lxc exec c1 -- ls -l /mnt
total 16
drwx------    2 root     root         16384 Apr 29 07:38 lost+found
-rw-r--r--    1 root     root             0 Apr 29 07:56 testfile

無事に root:root の所有になっており、ShiftFS が効いていることがわかります。ちなみにホスト側で見ると

$ ls -l /mnt
total 16
drwx------ 2 root root 16384 Apr 29 07:38 lost+found
-rw-r--r-- 1 root root     0 Apr 29 07:56 testfile

こちらの id も変わってません。

システムコンテナとして、少しでもVMやベアメタル上でOSを使うのに近づけようとする努力には本当に頭が下がりますね。

(追記)

snap set lxd shiftfs.enable=true を実行する前に作成したコンテナで ShiftFS を使うには、次のようにすると良いようです(私は未確認)。

$ lxc config set c1 security.privileged true ("c1" のとこはコンテナ名入れてね)
$ lxc restart c1
$ lxc config unset c1 security.privileged true
$ lxc restart c1