mappedマウントとケーパビリティまとめ2014年に始めたこの連載もついに50回になりました。この連載は当初12回の予定で、LXCの機能を中心に紹介するつもりでした。書いているうちに予定の回数には収まらないほど書きたいことが出てきて、結局予定していた内容を書き終えたのは第26回でした。内容についてはLXCの機能について書いたあとは、Linuxカーネルに実装されるコンテナ関連機能の紹介が中心になりました。ここ数年はLinuxカーネルに実装されるコンテナ関連の大きな機能の追加が少なくなったことと、それ以上に筆者が新しい機能を勉強する時間が少なくなり、記事として書ける内容を取得するスピードが減速したので、新しい記事を書く頻度は減りました。このようなマイペースな連載に長くお付き合いいただきありがとうございます。スピードが減速したとはいえ、まだ書きたいネタがなくなったわけではないので引き続きお付き合いいただければと思います。今回は、ここ数年いろいろな勉強会で登壇するたびに毎回簡単に紹介をしていた機能について、少し詳細に紹介したいと思います。Calendarに今年もこの記事で参加します。2022の23日目の記事です。コンテナを安全に使う機能として、第16回でユーザ名前空間を紹介しました。ユーザ名前空間を使うことで、一般ユーザ権限でコンテナが起動しても、コンテナ内ではroot権限を持つことができ、コンテナが安全に起動できます。この機能を使った一般ユーザ権限で起動するコンテナを使うために、これまでにいろいろな機能が追加されてきました。notifyといった機能です。このような機能追加で、ユーザ名前空間を使ったコンテナでも実際のホスト環境に近い機能が使えるようになってきました。mappedマウントという機能を紹介したいと思います。22.04上で実行しています。今回の実行例では、UID/GIDが1000であるgihyoユーザ、1001であるubuntuユーザ、1002であるu1002ユーザを用いており、それぞれシェルのプロンプトにユーザを表示し、誰が実行しているのかがわかるようにしています。一般ユーザ権限で起動するコンテナで使用するコンテナファイルシステム一般的にコンテナイメージとして配布されているコンテナのファイルシステムは、普通にOSをインストールした際と同様のファイルの所有権が設定されています。ほとんどのファイルがUID/GIDが0(root所有)で設定されているはずです。それ以外の所有者・グループが設定されているファイルについても、rootのUID/GIDである0をベースとしているはずです。一般ユーザ権限でコンテナを起動するためには、ホストから見たコンテナのファイルシステムでは、その一般ユーザ権限に対する権限が必要になります。ファイルの所有権がUID/GIDが0ベースのファイルシステムをそのまま使うと、コンテナから利用する際に権限がなく、コンテナから正常に利用できないことがほとんどでしょう。次のようにmmdebstrapコマンドを使ってコンテナイメージを作成したとします。http://jp.archive.ubuntu.com/ubuntuこのように作ったコンテナのルートファイルシステムは、次のようにほとんどのディレクトリやファイルはroot所有です。:(略)ここでunshareコマンドを使い、ユーザ名前空間を使ってコンテナを作ります。:(略)上の例では、現在のユーザをコンテナ内のrootにマッピングしています(--map-root-user⁠)⁠。元の名前空間のUIDが1000のユーザとコンテナ内のUIDが0のユーザだけをマッピングしましたので、その他のマッピングがないため、すべてのディレクトリがnobody:nogroup所有のように見えています[1]。ここがなぜnobody:nogroupになるのかは第16回をご覧ください。この状態では、コンテナ内でのファイル操作ができません。この問題を解決するには、ファイルやディレクトリの所有権をコンテナ内のrootにマッピングされるユーザが所有するように変更しなければいけません。この方法としてすぐに思い浮かぶのはchownコマンドやchown(2)システムコールでしょう。次のようにコンテナのルートファイルシステム以下の所有権をchownコマンドを使って変更します。/data/jammy/すると、次のようにコンテナ内でもroot所有のディレクトリに見えますので、コンテナ内での操作ができるようになったようにみえます。:(略)ところが待ってください。そんなことはありませんよね。/data/jammy/var/cache/apt/archives/partialchown前のファイルシステムを見てみると、mmdebstrapで作った最小限のファイルシステムでもこのようにroot以外が所有するディレクトリが見つかります。実際に使用する場合は、もっとさまざまな所有権を持ったファイルやディレクトリが存在するはずです。chownを使う場合、コンテナを正常に運用するには、ファイルシステム内のすべてのファイルやディレクトリの所有権をひとつずつ調べながら、マッピングに従って変換する操作が必要になります。これは非常にコストがかかる操作であることはご理解いただけると思います。shiftfsの提案とカーネルのマウントAPIの変更このような問題を解決するために、2017年ごろにshiftfsというファイルシステムが提案されました。shiftfsについては第47回でも少し紹介しています。mappedマウントと同じことを実現する機能でした。このshiftfsはマウント操作を2度行うという、通常のファイルシステムをマウントする操作とは異なる操作を行っていました。このような複雑なマウントを行っていたからか、shiftfs自体がカーネルにマージされることはありませんでした。カーネルが持つマウント関連のAPIも、さまざまな複雑なマウントが必要とされる最近のユースケースをすべてmount(2)システムコールで対応していたためいろいろと問題がありました。そのため、マウント関連のAPIが5.2カーネルで新しくなり、さまざまなユースケースに対応できるようになりました[2]。mappedマウントがマージされました。新しいマウントAPIについてはLWN.netの記事が詳しいです。mappedマウントは、先に紹介したshiftfsと同様の動きをします。ここまでで述べたように、一般ユーザ権限でコンテナを起動するには、コンテナを起動するユーザの権限でファイルシステムを操作できる必要があります。そこで、図1のようにマウント時に一般ユーザ権限でコンテナを起動する際に使用するユーザ名前空間のマッピングを使用し、そのマッピングを使ってID情報を変換してマウントします。mappedマウントの動きコンテナイメージの場合、リポジトリなどから取得して、すでにホストのファイルシステム上にコンテナイメージが存在する状態でマッピングを行いたいケースがほとんどでしょう。mappedマウントを使い、マッピングを使って所有権を変更して、バインドマウントのように別の場所にマウントし、そのマウントをコンテナのファイルシステムとして使用します。mappedマウントは、カーネルでは実装されました。util-linuxに含まれるmountコマンドでは利用できるようにはなっていません。mappedマウントを使って実装された機能を通して使うことになります。mappedマウントを使用します。util-linuxでもすでにプルリクエストが出ており、マージ予定になっていますので、近いうちにmountコマンドを通して使えるようになるでしょう。Brauner氏によるmount-idmappedコマンドを使うのが簡単です。mappedマウントを紹介していきます。mount-idmappedコマンドのビルドと利用mount-idmappedコマンドをビルドするのは非常に簡単です。コンパイラをインストールした環境で次のように実行します。(パスが通った場所にコマンドをコピー)できあがったmount-idmappedコマンドは、上の例のようにパスが通った場所にコピーしましょう。mount-idmappedコマンドは、オプションとしてIDのマッピング情報を与えます。通常、mountコマンドでバインドマウントを行う際のようにマウント元とマウント先のディレクトリ名を渡します。マッピングは--map-mountオプションで指定します。このオプションで指定する値は<type>:<id-from>:<id-to>:<id-range>です。このオプションは複数回指定できます。UIDとGIDを両方とも一度にマッピングするように指定します。"u"はUIDのマッピングだけを指定します。"g"はGIDのマッピングだけを指定します。マッピングの範囲たとえば次のようにマッピングとマウント元、マウント先のディレクトリを指定します。/path/to/dest上記の例では次のようになります。<type>として"b"を指定しているのでUID/GIDの両方を同時にマッピング指定する<id-from>として"1000"を指定しており、<id-to>として"1001"を指定しているので、UID/GID=1000を新たに作成するユーザ名前空間でUID/GID=1001にマッピングする<id-range>として"1"を指定しているので、UID/GID=1000から1つだけIDをマッピングする、つまりUID/GID=1000→UID/GID=1001だけをマッピングする/path/to/srcディレクトリを/path/to/destディレクトリにマウントする。このマウントはバインドマウントしたときと同じようにマウントされますつまりマウント元のUID/GIDが1000であるオブジェクトは、新たにマウントした先ではUID/GIDが1001であるオブジェクトに見えるということです。ファイルにひもづくUIDとGIDの情報だけが変換されたバインドマウントと言ったところでしょう。これ以下のmount-idmappedコマンドを使った実行例では、unshareコマンドなどでユーザ名前空間を作成していないように見えるかもしれません。mappedマウント時にマッピングを行わなければいけないため、mount-idmappedコマンド内でマッピングを行うためのユーザ名前空間を作成しています。mappedマウントを使ってみましょう。次のように、システム上にはUID/GIDが1001であるユーザubuntuと、1002であるユーザu1002が存在しています。次の実行例では、コマンドはUID/GIDが1002であるu1002ユーザで実行しています。groups=1001(ubuntu),4(adm),27(sudo)UIDが1001であるubuntuユーザのホームディレクトリを、UIDを1002にIDマッピングして、/mntにマウントしてみます。ubuntuユーザのホームディレクトリである/home/ubuntuにはubuntuユーザの権限でubuntu-fileというファイルをひとつ作ってあります。mappedマウントを実行してみましょう。/mntこれで/home/ubuntuがIDマッピングされて/mntにマウントされているはずです。/mntディレクトリの所有権を確認し、/mnt配下にファイルをu1002ユーザ(UID/GID=1002)権限で作成してみましょう。マウントした/mntはu1002ユーザ権限になっており、ファイルも特に問題なく作成できました。ubuntu-file先の例で、マッピング元の/home/ubuntuではUID/GIDが1001であるubuntuユーザ所有になっていたubuntu-fileという名前のファイルが、マウント先ではきちんとマッピングされて所有権がu1002ユーザになっています。マウント後に作成したファイルも同様にu1002ユーザ所有となっています。mappedマウントされたディレクトリである/mntで作成したu1002-fileを、マウント元の/home/ubuntuで確認しておきましょう。ubuntu-file/mnt上でu1002ユーザの権限で作成したu1002-fileは、元の/home/ubuntuで確認すると、きちんとUID:1001のubuntuユーザ所有になっています。mappedマウントとACLここまではLinuxが持つ基本的なアクセス制御だけを見てきました。mappedマウントのいろいろな動きを見てみましょう。Linuxではchmodコマンドを使い、基本的なユーザ、グループ、その他のユーザに対する読み取り、書き込み、実行の権限を設定できます。細かな制御を行うためにACL(アクセス制御リスト)という機能があります。mappedマウントを行った場合、ACLがどのように設定されるのかも確認しておきましょう。mappedマウントされた側のファイルに対してACLを設定します。-lで確認すると、ACLが設定された印である+が表示されています。ACLを確認するコマンドであるgetfaclコマンドで確認すると、setfaclコマンドで指定したu1002に対するACLが設定されています。ここまでは普通にACLを利用する場合の動きとしては変わらないと思います。mappedマウントの元になっているディレクトリ(/home/ubuntu)の方を確認してみましょう。こちらはUID/GIDが1001のubuntuユーザ所有でした。mappedマウントされたディレクトリで見た場合はu1002ユーザに対するACLが設定されていました。マウント元のディレクトリを見ると、元の所有権どおりubuntuユーザに対するACLが設定されています。一般的なLinuxにおけるファイルのアクセス権で設定されるユーザ、グループだけでなく、ACLについてもきちんとマッピングが適用されてデータが保存されるということです。mappedマウントとケーパビリティACLの次はケーパビリティを見ておきましょう。Linuxカーネルのケーパビリティ機能については、第42回〜第44回で紹介しました。mappedマウントと関係してくるのは、ファイルに設定するファイルケーパビリティです。このうち、第44回で紹介したユーザ名前空間内でのファイルケーパビリティ機能を試してみましょう。このユーザ名前空間内で設定するファイルケーパビリティは、簡単に言うとユーザ名前空間内でファイルケーパビリティを設定すると、ユーザ名前空間内のrootとマッピングされている親のユーザ名前空間のUIDが記録される機能です。そのUIDがrootにマッピングされたユーザ名前空間内でのみ、有効なファイルケーパビリティとして働きます。詳細は第44回を参照してください。このユーザ名前空間内でのみ有効なファイルケーパビリティを設定するにはsetcapコマンドに-nオプションを使用して設定します。今回の例では、-nオプションにu1002ユーザのUID1002を指定します。mappedマウントしたディレクトリと、元のディレクトリでケーパビリティがどのように設定されているように見えるかを確認してみましょう。mappedマウントしたディレクトリ/mntではrootid=1002と表示されており、マウント元の/home/ubuntuではrootid=1001となっており、ファイルケーパビリティについても、きちんと変換されていることがわかります。mappedマウントが必要とされる理由と、機能の基本的な動きを紹介しました。長くなってしまいましたので、続きは次回にしたいと思います。次回は、コンテナのファイルシステムをマウントするところで使用する際の動きや、それ以外のユースケースについて紹介したいと思います。Ltd.ページ内容の全部あるいは一部を無断で利用することを禁止します⁠。個別にライセンスが設定されている記事等はそのライセンスに従います。