LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第51回Linuxの非特権コンテナで利用するID mappedマウント(2)

前回は、ユーザ名前空間を使った一般ユーザ権限で起動するコンテナ(非特権コンテナ)から、コンテナのファイルシステムを使用する際の問題と、その問題を解決するためにID mappedマウントが利用できることを説明しました。そして、ID mappedマウントの簡単な動きを説明しました。

今回は、ひきつづきmount-idmappedコマンドを使いながら、実際のユースケースに近い動きを見ていきましょう。

コンテナから利用するID mappedマウント

まずは一般ユーザ権限で起動するコンテナから、ID mappedマウントを利用した際の動きを見てみましょう。

一般ユーザ権限で起動するコンテナから、ID mappedマウントを利用する際に確認する動きは図1のようになります。前回説明したID mappedマウントの動きに、さらにユーザ名前空間が使うマッピングが適用されますので、方向が逆の変換が2度行われることになります。実際の実行例を見て頭が混乱したときには、図1を見て頭の中を整理してください (^^)。

図1 ID mappedマウントと非特権コンテナ
ID mappedマウントと非特権コンテナ

それでは、実際に図1で紹介した動きを確認していきましょう。

コンテナイメージは、lxc-createコマンドを使って、root権限で作成します。

$ sudo lxc-create -t download c1 -- -d alpine -r 3.16 -a amd64
(コンテナの作成)
  :(略)
$ sudo ls -l /var/lib/lxc/c1/rootfs/
total 68
drwxr-xr-x  2 root root 4096 Dec 30 13:01 bin
drwxr-xr-x  3 root root 4096 Jan  3 06:05 dev
drwxr-xr-x 21 root root 4096 Jan  3 07:51 etc
drwxr-xr-x  2 root root 4096 Nov 11 18:03 home

上の例では、LXCの公式のAlpine Linuxイメージを使ってコンテナを作成しています。

lxc-createコマンドでコンテナイメージを作成すると、/var/lib/lxc/[コンテナ名]/rootfs以下にコンテナイメージが作成されます。この時点ではroot権限でコンテナイメージを作成しただけですので、UID/GID=0/0をベースとした、root権限でコンテナを起動するためのイメージが作成されました。つまり、root所有のファイルやディレクトリができているだけです。

このファイルシステムを、UID/GID=0をUID/GID=100000/100000にマッピングし、それ以降65536個のIDをマッピングしてマウントしましょう。/mntにマウントします。

$ sudo mount-idmapped --map-mount b:0:100000:65536 /var/lib/lxc/c1/rootfs /mnt
(ID mappedマウントで65536個のIDをマッピングしてマウント)
$ cat /proc/self/mountinfo | grep idmap
481 31 253:0 /var/lib/lxc/c1/rootfs /mnt rw,relatime,idmapped shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw
(ID mappedマウントされていることを確認)

上のように問題なくマウントが完了しました。mountinfoファイル内でもマウントされている様子が確認できます。ID mappedマウントは、mountinfoファイル内のエントリにidmappedという文字列が表示されます。

マウント元とマウント先の状況を確認しておきましょう。ファイルシステムのルート/と、所有グループがrootではない/etc/shadowの状況を確認します。

$ sudo ls -l /var/lib/lxc/c1/rootfs
(マウント元の確認)
total 68
drwxr-xr-x  2 root root 4096 Dec 30 13:01 bin
drwxr-xr-x  3 root root 4096 Jan  3 06:05 dev
drwxr-xr-x 21 root root 4096 Jan  3 07:51 etc
drwxr-xr-x  2 root root 4096 Nov 11 18:03 home
  :(略)
(マウント元を確認すると所有者はroot/root)
$ sudo ls -l /var/lib/lxc/c1/rootfs/etc/shadow
-rw-r----- 1 root shadow 422 Nov 11 18:03 /var/lib/lxc/c1/rootfs/etc/shadow
(所有グループがshadow)
$ sudo ls -l /mnt
(マウント先の確認)
total 68
drwxr-xr-x  2 100000 100000 4096 Dec 30 13:01 bin
drwxr-xr-x  3 100000 100000 4096 Jan  3 06:05 dev
drwxr-xr-x 21 100000 100000 4096 Jan  3 07:51 etc
drwxr-xr-x  2 100000 100000 4096 Nov 11 18:03 home
  :(略)
(マッピングの通りUID/GID=100000/100000に変換されている)
$ sudo ls -l /mnt/etc/shadow
-rw-r----- 1 100000 100042 422 Nov 11 18:03 /mnt/etc/shadow
(所有グループは100042)

ファイルシステムのルート/にあるディレクトリは、マウント元では所有権がUID/GID=0/0です。それが、マウント先ではUID/GID=100000/100000所有に変換されています。また、マウント元では所有グループがshadow(GID=42)であるファイルが、マウント先ではGID=100042になっており、複数のIDがマッピングを使って変換が行われていることがわかります。

ここでユーザ名前空間を作ってみましょう。ここではマッピングをまったく指定せずにユーザ名前空間を作成していますので、ユーザ名前空間作成後のシェルの実行ユーザはnobodyになります。それがわかるように、ここからはプロンプトにユーザ名を表示させています。

gihyo@ubuntu2204:~$ unshare --user (ユーザ名前空間を作成)
nobody@ubuntu2204:~$ ls -l /
total 2398280
lrwxrwxrwx   1 nobody nogroup          7 Aug  9 11:53 bin -> usr/bin
drwxr-xr-x   4 nobody nogroup       4096 Jan  7 13:18 boot
drwxr-xr-x   3 nobody nogroup       4096 Nov 23 14:49 data
drwxr-xr-x  20 nobody nogroup       4080 Jan  7 15:28 dev
drwxr-xr-x  91 nobody nogroup       4096 Jan  7 13:17 etc
drwxr-xr-x   3 nobody nogroup       4096 Nov 23 12:43 home
  :(略)
(まだマッピングを行っていないのでnobody/nogroup所有になっている)
nobody@ubuntu2204:~$ echo $$
1035

それでは、作成したユーザ名前空間にマッピングを指定していきましょう。ユーザ名前空間を作成したあとに実行しているシェルのPIDは1035です。このシェルに対するマッピングをnewuidmapnewgidmapコマンドで指定します。ここでコマンドを実行するために必要な設定を確認しておきましょう。上のユーザ名前空間を作成したシェルとは別のシェルを起動して確認します。

gihyo@ubuntu2204:~$ id
uid=1000(gihyo) gid=1000(gihyo) groups=1000(gihyo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
(現在のユーザはUID/GID=1000/1000)
gihyo@ubuntu2204:~$ cat /etc/sub{u,g}id | grep gihyo
gihyo:100000:65536
gihyo:100000:65536
(gihyoユーザが使えるサブIDの設定)

現在はUID/GID=1000/1000のgihyoユーザです。このgihyoユーザが、サブIDとしてUID/GID=100000/100000からそれぞれ65536個のIDが使えるように、/etc/subuid/etc/subgidを設定しています。サブIDの話や、この後使用するnewuidmapnewgidmapコマンドについては第16回をご覧ください。

準備ができていることが確認できましたので、ホストでのUID/GID=100000/100000から65536個のIDが、ユーザ名前空間内のUID/GID=0/0から65536個にマッピングされるようにコマンドを実行します[1]

gihyo@ubuntu2204:~$ newuidmap 1035 0 100000 65536
(UIDのマッピング)
gihyo@ubuntu2204:~$ newgidmap 1035 0 100000 65536
(GIDのマッピング)
gihyo@ubuntu2204:~$ cat /proc/1035/{u,g}id_map
         0     100000      65536
         0     100000      65536
(ホストの100000から65536個のIDが名前空間内の0から65536個にマッピングされている)

/proc/uid_map/proc/gid_mapファイルを確認して、指定通りマッピングが行えていることが確認できました。

ここで、ユーザ名前空間内にいるシェルに戻って、ID mappedマウント元のファイルシステムの状態とマウント先の状態を確認してみましょう。

nobody@ubuntu2204:~$ ls -l /mnt
total 68
drwxr-xr-x  2 root root 4096 Dec 30 13:01 bin
drwxr-xr-x  3 root root 4096 Jan  3 06:05 dev
drwxr-xr-x 21 root root 4096 Jan  3 07:51 etc
drwxr-xr-x  2 root root 4096 Nov 11 18:03 home
  :(略)
nobody@ubuntu2204:~$ ls -l /mnt/etc/shadow
-rw-r----- 1 root shadow 422 Nov 11 18:03 /mnt/etc/shadow

複数のIDのマッピングを行ったシェルから、同じ複数のIDのマッピングによるID mappedマウントを行ったファイルシステムの状態を見ると、マッピング通りの変換が行われています。これでコンテナ内でも問題なくファイル操作が可能になり、コンテナが正常に動作します。

LXCコンテナから利用するID mappedマウント

先の例ではunsharenewuidmapnewgidmapコマンドを使って、ID mappedマウントを使ったコンテナの作成を順を追って説明しました。コンテナランタイムでもID mappedマウントされたファイルシステムを使ってコンテナを起動できることを、LXCを使って確認してみましょう。

あわせて、コンテナ内で行うファイル操作が、ID mappedマウント元であるホスト上のファイルの所有権にきちんと変換されているかも確認してみましょう。

コンテナは、先の例でID mappedマウントした/mnt以下のファイルシステムをそのまま使います。

$ sudo cat /proc/self/mountinfo | grep idmapped
(lxc-createで作成したコンテナイメージが/mntにID mappedマウントされていることを確認)
323 31 253:0 /var/lib/lxc/c1/rootfs /mnt rw,relatime,idmapped shared:1 - ext4 /dev/mapper/ubuntu--vg-ubuntu--lv rw

ここで、lxc-createが作成したLXCコンテナ用の設定ファイルを、ID mappedマウントした/mntをコンテナのルートファイルシステムとして使用するように書き換えます。そして、コンテナ内のUID/GID=0がUID/GID=100000に、範囲は65536となるようにマッピングを設定します。

lxc.rootfs.path = dir:/mnt (コンテナのルートファイルシステムを/mntにする)
lxc.idmap = u 0 100000 65536 (コンテナのUIDのマッピング)
lxc.idmap = g 0 100000 65536 (コンテナのGIDのマッピング)

LXCでも、先の例で確認した/etc/subuid/etc/subgidファイルでのサブIDの設定は必要です。ここではrootユーザでコンテナを起動するので、rootユーザでもサブIDの設定をしておきます。

$ cat /etc/sub{u,g}id | grep root
root:100000:65536
root:100000:65536

これで、rootユーザがUID=100000の一般ユーザ権限でコンテナを起動する準備ができましたので、起動してみましょう。

$ sudo lxc-start c1 (コンテナの起動)
$ sudo lxc-ls -f (コンテナの起動を確認)
NAME STATE   AUTOSTART GROUPS IPV4       IPV6 UNPRIVILEGED 
c1   RUNNING 0         -      10.0.3.237 -    true         

コンテナが起動しました。UNPRIVILEGEDtrueとなっており、一般ユーザ権限で起動していることも確認できました。

LXCで、ホスト上からコンテナ内でコマンドを実行するにはlxc-attachコマンドを使用します。指定したマッピングが行われていることをコンテナ内の/proc/1/uid_map/proc/1/gid_mapで確認しましょう。

$ sudo lxc-attach c1 -- cat /proc/1/{u,g}id_map
         0     100000      65536
         0     100000      65536
(コンテナ内のID=0が100000にマッピングされ、範囲は65536個であることを確認)

確認できました。

次にコンテナ内のルート/を確認してみましょう。

$ sudo lxc-attach c1 -- ls -l /
total 52
drwxr-xr-x    2 root     root          4096 Dec 30 13:01 bin
drwxr-xr-x    4 root     root           440 Jan  3 07:51 dev
drwxr-xr-x   21 root     root          4096 Jan  3 07:51 etc
drwxr-xr-x    2 root     root          4096 Nov 11 18:03 home
  :(略)

ホストの/mnt以下ではUID/GID=100000/100000という所有権になっていたディレクトリが、コンテナ内では変換されてUID/GID=0/0になっていることが確認できました。

コンテナ内でファイルを作成してみましょう。

$ sudo lxc-attach c1 -- touch /root/root-file
(コンテナ内でファイルを作成)
$ sudo lxc-attach c1 -- ls -l /root/root-file
-rw-r--r--    1 root     root             0 Jan  3 06:22 /root/root-file
(root所有のファイルができた)

コンテナ内でrootユーザでファイルを作成しました。これをホスト上のID mappedマウントした/mntと、マウント元の/var/lib/lxc/c1/rootfs以下で確認してみましょう。

$ sudo ls -l /mnt/root/root-file
-rw-r--r-- 1 100000 100000 0 Jan  3 07:54 /mnt/root/root-file
(ID mappedマウント先ではUID:100000ユーザの所有になっている)
$ sudo ls -l /var/lib/lxc/c1/rootfs/root/root-file
-rw-r--r-- 1 root root 0 Jan  3 07:54 /var/lib/lxc/c1/rootfs/root/root-file
(ID mappedマウント元ではroot所有になっている)

ホストのファイルシステム上でファイルの所有権を確認すると、ID mappedマウントした/mnt以下ではUID=100000であるユーザの所有に、ID mappedマウント元の/var/lib/lxc/c1/rootfsでは、ID mappedマウントにより変換が行われ、root所有になっています。

root所有以外であるファイルについても動きを確認しておきましょう。ここではUID/GID=35/35所有であるファイルを作成してみます。35は、Alpine Linuxではgamesユーザ・グループのIDです。

$ sudo lxc-attach c1 -- touch /tmp/testfile
(コンテナ内でファイルを作成する)
$ sudo lxc-attach c1 -- chown games:games /tmp/testfile
(作成したファイルの所有権をgames:gamesにする)
$ sudo lxc-attach c1 -- ls -l /tmp/testfile
-rw-r--r--    1 games    games            0 Jan  7 15:30 /tmp/testfile
$ ls -l /mnt/tmp/testfile 
-rw-r--r-- 1 100035 100035 0 Jan  7 15:30 /mnt/tmp/testfile
(ID mappedマウント先ではコンテナのマッピングに従いUID/GID=100035/100035所有になっている)
$ sudo ls -l /var/lib/lxc/c1/rootfs/tmp/testfile
-rw-r--r-- 1 35 35 0 Jan  7 15:30 /var/lib/lxc/c1/rootfs/tmp/testfile
(ID mappedマウント元ではID mappedマウントのマッピングに従いUID/GID=35所有になっている)

コンテナ内でUID/GID=35/35games所有となっているファイルの所有権が、ID mappedマウント先の/mnt/tmp/testfileではUID/GID=10035/10035所有となっており、ID mappedマウント元の/var/lib/lxc/c1/rootfs/tmp/testfileではマッピングに従って変換され、UID/GID=35所有になっていることが確認できます[2]

コンテナ内でのファイル操作が、ホスト上のID mappedマウント元では、マッピング通りに元のUID/GIDに変換されていることが確認できました。

複数のコンテナから同じイメージをマウント

コンテナからID mappedマウントを利用する際の動きを見たところで、コンテナでID mappedマウントを使用する際のメリットとなる使い方を紹介しておきましょう。

ここまででは、1つのコンテナからID mappedマウントを使ってコンテナからファイルシステムをマウントしてきました。この場合はchownしなくて良いという点がID mappedマウントを使用するメリットでした。

ここで、さらに複数のコンテナから単一のコンテナイメージや領域をマウントして利用することを考えてみましょう。このような場合では、ID mappedマウントが持つ優位性がさらに高まります。

複数のコンテナから同じ領域を同時に利用しても問題ないケースで、同時に複数のコンテナから同じ領域をマウントする場合、そもそもchownでは対応できません。ID mappedマウントを使うことで対応できるようになります。

また、同時にマウントはしないけれど、複数のコンテナから1つの領域をマウントして使用する場合、何度もchownを繰り返さなくて済むことはメリットになるでしょう。

ID mappedマウントのその他のユースケース

ここまでは、一般ユーザ権限で起動するコンテナからID mappedマウントを利用する際の動きを見てきました。ところで、ID mappedマウントのユースケースはここまで紹介したようなコンテナからの利用以外にもあります。いくつか紹介していきましょう。

UID/GID=0のマッピングなしでのID mappedマウント

root所有であるファイルに対する権限がない状態で、コンテナを利用しても問題がないようなケースがあるとしましょう。このような場合、root所有であるファイルに対してrootユーザが変更を加えられないように、UID/GIDが0に対するマッピングがない状態でID mappedマウントを行えます。

例えば、UID/GIDが1から65535個のIDをマッピングを行ってみましょう。現在のルートディレクトリ//mntにID mappedマウントします。

$ sudo mount-idmapped --map-mount b:1:1:65535 / /mnt/
(ID=1から65535個のIDをマッピングしてマウント)
$ ls -l /mnt
total 2398296
lrwxrwxrwx  1 nobody nogroup          7 Aug  9 11:53 bin -> usr/bin
drwxr-xr-x  2 nobody nogroup       4096 Nov 23 12:33 boot
drwxr-xr-x  3 nobody nogroup       4096 Nov 23 14:49 data
drwxr-xr-x  4 nobody nogroup       4096 Aug  9 11:56 dev
drwxr-xr-x 91 nobody nogroup       4096 Jan  7 13:17 etc
drwxr-xr-x  3 nobody nogroup       4096 Nov 23 12:43 home
(もともとroot所有だったファイルはマッピングがないためnobody/nogroup所有)
$ ls -l /mnt/home
total 4
drwxr-x--- 6 gihyo gihyo 4096 Jan 10 13:15 gihyo
$ ls -l /mnt/etc/shadow
-rw-r----- 1 nobody shadow 1026 Nov 23 13:12 /mnt/etc/shadow
(ID=0以外のマッピングは行われている)

このようにUID/GIDが0に対するマッピングがないため、root所有だったファイルやディレクトリはnobody/nogroup所有となっています。それ以外のIDに対するマッピングは存在するため、変換が行われて本来のユーザ・グループの所有になっています。

この状態でファイル操作を行ってみましょう。

$ sudo touch /mnt/i-am-root
touch: cannot touch '/mnt/i-am-root': Value too large for defined data type
(root権限でファイルを作成しようとするとエラーになる)
$ touch /mnt/home/ubuntu/i-am-ubuntu
$ ls -l /mnt/home/ubuntu/i-am-ubuntu
-rw-rw-r-- 1 ubuntu ubuntu 0 Oct 18 13:13 /mnt/home/ubuntu/i-am-ubuntu

rootユーザでファイル操作を行った場合だけエラーになります。

実際にこのような設定を行ったコンテナを起動するようなケースがあるのかは筆者はよくわかりませんが、このようなこともできるという例でした。

VFATファイルシステムのマウント

VFATのような、所有権という概念がないファイルシステムをLinux上で使いたい場合に、ID mappedマウントが使えます。さらに、マウントしたVFATの領域を、システム上の複数のユーザに見せたいような場合にも、ID mappedマウントが利用できます。

まずは、VFATのファイルシステムを/mntにマウントします。そして、ファイルを作成しておきます。

$ sudo mount -t vfat /dev/sda /mnt
(/dev/sdaに存在するVFATのファイルシステムを/mntにマウント)
$ grep fat /proc/self/mountinfo 
488 31 8:1 / /mnt rw,relatime shared:194 - vfat /dev/sda1 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
(vfatでマウントされている)
$ ls -ld /mnt
drwxr-xr-x 2 root root 4096 Jan  1  1970 /mnt
$ sudo touch /mnt/testfile
(VFAT領域上にファイルを作成)
$ ls -l /mnt/testfile 
-rwxr-xr-x 1 root root 0 Jan 10 13:36 /mnt/testfile

ここではroot権限で操作していますので、/mntディレクトリも/mnt以下に作成したファイルもroot所有です。

この/mntをID mappedマウントして、一般ユーザであるgihyoユーザ(UID/GID=1000/1000)ubuntuユーザ(UID/GID=1001/1001)に見せてみましょう。それぞれのホームディレクトリ内にマウント用にmntディレクトリを作成してあります。

$ sudo ls -ld /home/{gihyo,ubuntu}/mnt
drwxrwxr-x 2 gihyo  gihyo  4096 Jan 10 13:40 /home/gihyo/mnt
drwxr-xr-x 2 ubuntu ubuntu 4096 Jan 10 13:40 /home/ubuntu/mnt

それぞれのホームディレクトリ以下のmntディレクトリに、/mntにマウントしたVFATのファイルシステムをID mappedマウントしましょう。

$ sudo mount-idmapped --map-mount b:0:1000:1 /mnt /home/gihyo/mnt
$ sudo mount-idmapped --map-mount b:0:1001:1 /mnt /home/ubuntu/mnt
(それぞれのホームディレクトリ以下にID mappedマウント)
$ sudo ls -l /home/{gihyo,ubuntu}/mnt
/home/gihyo/mnt:
total 0
-rwxr-xr-x 1 gihyo gihyo 0 Jan 10 13:36 testfile

/home/ubuntu/mnt:
total 0
-rwxr-xr-x 1 ubuntu ubuntu 0 Jan 10 13:36 testfile
(それぞれのユーザ権限で操作できる状態でマウントされている)

ここでは所有権という概念がないファイルシステムを、ユーザ権限がある状態でマウントする例としてVFATを使いました。

この他にも、システム上にマウントされたネットワークファイルシステムを複数のユーザに見せるようなケースにID mappedマウントが使用できるでしょう。

systemd-homed

次に、実際にID mappedマウントを内部で採用しているシステムを紹介しましょう。みなさんがお使いのLinuxシステムで、重要な役割を果たしているsystemdの一部であるsystemd-homedです。

systemd 250で、systemd-nspawnとsystemd-homedに、ID mappedマウントが採用されました。

筆者がメンテナを務めるPlamo Linuxでは、まだsystemdを採用していませんので、筆者はsystemdには詳しくありません。しかし、ID mappedマウントが採用されているということで、Fedoraをインストールし、systemd-homedを試してみました。

ここでの実行例は、systemd 251を採用しているFedora 37を使用しています。

$ cat /etc/redhat-release 
Fedora release 37 (Thirty Seven)
$ uname -r
6.0.16-300.fc37.x86_64
$ rpm -q systemd
systemd-251.10-588.fc37.x86_64

systemd-homedは、特定のシステム上で設定されているユーザ管理に依存しないポータブルなアカウントを提供するためのサービスです。通常、Linuxでのユーザ管理はuseraddなどのコマンドや、/etc/passwd/etc/groupなどのファイルを用い、ホームディレクトリは/home以下に存在し、/etc/passwdなどで管理されているIDを用いて管理されています。

これをすべてホームディレクトリ上で管理をして、どこからそのホームディレクトリを参照しても問題なく利用できるようにします。つまりホームディレクトリに対するポータビリティを実現しているわけです。それだけでなく、ホームディレクトリを暗号化してセキュリティも確保できるようです[3]

このsystemd-homedが、ホームディレクトリに対するポータビリティを確保するために、ID mappedマウントを利用しています。つまり、特定のユーザに対して異なるIDが使用されているシステム間でも、問題なくホームディレクトリが利用できます。ユーザがログインしてホームディレクトリを使用するときに、適切なIDへのマッピングが行われます。マッピング元のファイルシステムはnobody所有になります。

それでは、systemd-homedがID mappedマウントを使用している様子を見ていきましょう。

まずはFedora 37でsystemd-homedが使えるように設定します[4]。システムのSELinuxの設定をpermissiveに設定して起動しています。

$ sudo authselect select sssd with-systemd-homed
(sssdでsystemd-homedを利用するように設定する)
  :(略)
$ sudo systemctl enable --now systemd-homed.service
(systemd-homed.serviceを有効化し、起動する)

これで、systemd-homedが管理するアカウントでの認証と利用ができるようになります。

さて、これでsystemd-homedを使う準備ができましたので、実際に使っていきましょう。

まず、ホームディレクトリが置かれる/homeは、動きを追いやすいようにBtrfsで作成しています。ここで/homeをBtrfsで作成しておくと、systemd-homedが管理するユーザを作成する際に、ストレージ形式としてsubvolumeが指定できます。その場合、ホームディレクトリ用の領域がBtrfsのサブボリュームで作成されます。

$ grep '/home' /proc/self/mountinfo
91 89 0:34 / /home rw,relatime shared:46 - btrfs /dev/vdb rw,seclabel,space_cache=v2,subvolid=5,subvol=/
(/homeはBtrfsで作成されている)

それではユーザを作成し、ホームディレクトリがどのように作られるかを調べていきましょう。systemd-homedで管理するユーザを作成するには、homectl createコマンドを使用します。

$ sudo homectl create --storage=subvolume gihyo
(ストレージ形式としてsubvolumeを指定してgihyoユーザを作成)

これで/home直下にgihyo.homedirというディレクトリが作られ、ユーザーのホームディレクトリが作られます。

$ ls -ld /home/gihyo*
drwx------. 1 nobody nobody 108 Jan  7 16:04 /home/gihyo.homedir
$ sudo ls -al /home/gihyo.homedir/
total 36
drwx------. 1 nobody nobody  82 Jan  7 16:01 .
drwxr-xr-x. 1 root   root    36 Jan  7 16:01 ..
-rw-r--r--. 1 nobody nobody  18 Jan  2 20:43 .bash_logout
-rw-r--r--. 1 nobody nobody 141 Jan  2 20:43 .bash_profile
-rw-r--r--. 1 nobody nobody 492 Jan  2 20:43 .bashrc
-rw-------. 1 nobody nobody 644 Jan  7 16:01 .identity

gihyoというユーザを作りましたが、ホームディレクトリ以下はすべてnobody/nobody権限でファイルが作成されています。

この状態でコンソールからログインしてみます[5]。ログイン後に/homeを確認してみましょう。

$ ls -ld /home/gihyo*
drwx------. 1 gihyo  gihyo  108 Jan  7 16:04 /home/gihyo
drwx------. 1 nobody nobody 108 Jan  7 16:04 /home/gihyo.homedir
(/home以下はユーザ作成と同時に作られたgihyo.homedirとgihyoディレクトリが作られている)
$ sudo ls -la /home/gihyo
total 40
drwx------. 1 gihyo gihyo 108 Jan  7 16:04 .
drwxr-xr-x. 1 root  root   46 Jan  7 16:03 ..
-rw-------. 1 gihyo gihyo  12 Jan  7 16:04 .bash_history
-rw-r--r--. 1 gihyo gihyo  18 Jan  2 20:43 .bash_logout
-rw-r--r--. 1 gihyo gihyo 141 Jan  2 20:43 .bash_profile
-rw-r--r--. 1 gihyo gihyo 492 Jan  2 20:43 .bashrc
-rw-------. 1 gihyo gihyo 644 Jan  7 16:01 .identity

すると、さきほどは存在していなかった/home/gihyoディレクトリが作成され、所有権はgihyoユーザ・グループ所有になっています。そして/home/gihyo以下には、nobody権限で/home/gihyo.homedir以下に作られていた設定ファイル群が、gihyoが所有権の状態で存在しています。

これがID mappedマウントされた結果なのかを確認します。

$ cat /proc/self/mountinfo | grep idmapped
810 87 0:34 /gihyo.homedir /home/gihyo rw,nosuid,nodev,relatime,idmapped shared:395 - btrfs /dev/vdb rw,seclabel,space_cache=v2,subvolid=257,subvol=/gihyo.homedir

gihyo.homedir/home/gihyoにID mappedマウントされていることが確認できます。/home/gihyoディレクトリ自身の所有権がgihyo所有になっていた理由は、ID mappedマウントされているからです。

ユーザがログアウトした後は、/home/gihyoディレクトリ自体は残ったままで、ID mappedマウントはアンマウントされます。このとき/home/gihyoディレクトリの所有権はrootになっており、再度gihyoユーザがログインすると、再びID mappedマウントされ、先の例と同様の状態になります。

以上のように、systemd-homedが使えるようになった後は、あっけないほどあっさりとID mappedマウントされ、ホームディレクトリが使えるようになりました。

ID mappedマウントが使えるファイルシステム

ここまで長々とID mappedマウントの機能やユースケースを紹介してきました。ここまででは、ID mappedマウント元のファイルシステムには何を使っているかは触れていませんでしたので、簡単に説明しておきましょう。

ID mappedマウントは、ID mappedマウントに対応しているファイルシステムでのみマウントできます。

執筆時点の6.1カーネルでは、Btrfs, EROFS, ext4, F2FS, VFAT, NTFS3, XFSが対応しています。これ以外のファイルシステムをマウント元として、ID mappedマウントは行えません。

ID mappedマウントができるファイルシステムは、カーネルの各ファイルシステムで定義されているstruct file_system_type内のfs_flagsFS_ALLOW_IDMAPが設定されています。

例えば、5.15カーネルでID mappedマウントに対応したBtrfsでは次のように定義されています。

fs/btrfs/super.c
static struct file_system_type btrfs_root_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "btrfs",
	.mount		= btrfs_mount_root,
	.kill_sb	= btrfs_kill_super,
	.fs_flags	= FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | FS_ALLOW_IDMAP,
};

まとめ

前回と今回の2回で、ID mappedマウントについて説明しました。

ID mappedマウントは、5.12カーネルで機能が追加されたあと、対応するファイルシステムを拡大するだけでなく、5.17カーネルで、ID mappedマウントを行っているマウントをマウント元としてID mappedマウントできるようになりました。つまり再帰的にID mappedマウントができるようになっています。

ID mappedマウント機能の実装により、ユーザ名前空間を使ったコンテナの可能性が広がり、より安全にコンテナを起動する環境が整ったように思います。コンテナからの利用だけでなく、ファイルシステムをマウントして利用する際の自由度も高まりました。

参考文献

今回の記事で、systemd-homedの動きを確認するために参考にしたページは次のページです。

おすすめ記事

記事・ニュース一覧