TenForward

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

2022 年やりたいこと

技術ブログだけど、今年やりたいことといえば技術のことよりもそれ以外のことが思い浮かぶなあ。フォロワーさんが書いてるのをみて、一年の初めってのもあるし、歳もひとつとったしということで、書いてみるかなと思って書いてみました。

技術

これまで通りのことをやるくらいかな。昨年は忙しかったし、なんか仕事で色々疲れてあまり新しいことをやろうという気がおこらなかった。今年もあまりやる気が湧いてくる気もしないけど。

  • 連載記事を 2021 年よりはたくさん書きたい
  • 連載記事をまとめて技術書典に出すとか何か形にしたいな
  • linuxcontainers.org の翻訳はこれまで通り
  • コンテナの勉強会を最低 1 回はやりたい
    • けれども何もネタを思いつきません。何か面白そうなテーマが思い浮かんで、話してくれる人が見つかりますように
  • 昨年よりはもう少し新しいコンテナ関連の新技術を調べたい
  • Go とか Rust とか新しい言語やりたいな(昨年 Go はちょっと手をつけたけど結局放置…)
  • 仕事で昨年からまたセキュリティ関連やってるんだけど、まあその辺りをぼちぼちと…(あまり技術によってない感じなので(以下略))

趣味

  • 昨年よりはドラマ(韓国ドラマ)の視聴を増やしたい
  • 昨年よりはちゃんと音楽聴きたい
    • イベント復活したらいきたいですね。昨年は久々に King Crimson でコンサート行きました
  • 韓国語
    • Duolingo と NHKテレビ講座で進めて 10 分程度のヨントンに耐えられるようにしたい(それ以前にコミュニケーション能力に問題あるんだけど :-p)
    • ハングル検定 5 級くらい受けてみたいな
  • 本を読みたいな。買って放置してるやつ多い(歳と共に本を読む集中力がなくなってきた)
  • 久々にメガネを買いたいな(JFReyが好きで、これまで10本ほどヤフオクで買ってます)(2021-01-04追記)

運動

  • 昨年はワクチン接種後以外はほぼ毎日運動をしたので継続したい。内容は変わらずジムに行くか、5km ほどの決まったコースを走るか

まとめ

  • なんか「やる気に満ち溢れてる!」感がないなあ。実現しないんちゃう? :-p

『Infra Study 2nd #2「クラウドネイティブを支えるインフラ技術」』でお話してきました

6/11(金)に Infra Study 2nd #2 ってイベントで登壇しました。そのレポートを会社のテックブログで書きました。

blog.idcf.jp

まあ、参加してない方や、アーカイブ動画を見てない方は、このレポートを読むというよりは、もとのアーカイブ動画を見ましょう、って感じですね。

www.youtube.com

5.11 で追加された OverlayFS の非特権マウント(2)

前回は、OverlayFS の非特権マウントの基本的な部分について説明しました。

ところで OverlayFS はシンプルながらも、より進んだ使い方ができる機能やオプションが存在します。基本的な機能以上の機能を使う場合、OverlayFS は拡張ファイル属性を使います。

例えば連載第18回で説明した「opaque(不透明)ディレクトリ」機能や、以前のブログエントリーで説明したredirect_dir機能などです。

しかし拡張ファイル属性で、この trusted で始まる属性(trusted名前空間)を使う場合は特権が必要であり、当然 User Namespace 内の root ではこの属性を使用できません。このような場合、代わりに自由に定義して使える属性(名前空間)として user が存在します1

OverlayFS でも、この機能を使うように実装されており、trusted の代わりに user を使う場合は、マウントオプションとして userxattr を指定してマウントします。この場合、trusted.overlay.* ではなく user.overlay.* を使うようになります。

非特権の場合の拡張ファイル属性

この拡張ファイル属性の動きを連載で説明した「opaque(不透明)ディレクトリ」を使って説明しましょう。この機能を使うと lowerdir に指定した下層側のディレクトリの内容が見えなくなります。

opaqueディレクトリを使いたい場合、拡張ファイル属性 trusted.overlay.opaquey という値を入れます。非特権 OverlayFS の場合は、この代わりに user.overlay.opaque を使うわけです

この機能の動きを見るための環境を作成しましょう。

$ mkdir lower upper work overlay
$ mkdir {lower,upper}/opaquetest
$ touch lower/opaquetest/testfile_lower upper/opaquetest/testfile_upper

これまでの実行例と同様に lowerupperworkoverlay というディレクトリを作成し、lowerupper の両方のディレクトリに opaquetest というディレクトリを作成します(連載の例と同じです)。そして lowerupper 配下の opaquetest ディレクトリ内にそれぞれ testfile_lowertestfile_upper というファイルを置きます。

この環境で普通に OverlayFS マウントを行うと、次のように見えるはずです。

# tree overlay/
overlay/
└── opaquetest
    ├── testfile_lower
    └── testfile_upper

1 directory, 2 files

userxattr オプションを使わない場合

まずは userxattr オプションを指定せずに OverlayFS マウントを行い、動きを見てみましょう。当然、期待する動きはしないはずです。

# setfattr -n "user.overlay.opaque" -v "y" upper/opaquetest/ (拡張ファイル属性を設定する)
# getfattr -n "user.overlay.opaque"  upper/opaquetest/ (拡張ファイル属性が設定されたのを確認)
# file: upper/opaquetest/
user.overlay.opaque="y"

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
# tree overlay/ (拡張ファイル属性を設定したものの、特にOverlayFSの動きに変化はない)
overlay/
└── opaquetest
    ├── testfile_lower
    └── testfile_upper

1 directory, 2 files
# umount overlay

上記の例のように user.overlay.opaque 属性を設定しても、特に先ほどの普通に OverlayFS マウントを行ったときと動きに変化はありません。

userxattr オプションを追加してマウントした場合

それでは OverlayFS マウントを行う際に userxattr オプションを追加してみましょう。

# getfattr -n "user.overlay.opaque"  upper/opaquetest/ (拡張属性が設定されているのを確認)
# file: upper/opaquetest/
user.overlay.opaque="y"

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr overlay overlay (userxattrオプションを指定してマウント)
# tree lower upper (lower, upper以下のopaquetestディレクトリにはそれぞれファイルが存在する)
lower
└── opaquetest
    └── testfile_lower
upper
└── opaquetest
    └── testfile_upper

2 directories, 2 files
# tree overlay/ (overlayディレクトリを見ると上層側に置いたファイルしか見えない)
overlay/
└── opaquetest
    └── testfile_upper

1 directory, 1 file

このように上層側のファイルしか見えません。opaque ディレクトリの機能が働いていることがわかります。

非特権マウントの場合に使えない機能

以上のように、非特権マウントの場合は trusted.overlay の代わりに user.overlay を使って OverlayFS 独自の機能を実現します。

しかし非特権の場合、特権を持っているケースと同じように機能を使うと危険なケースがあります(特権の取得につながるとか)。このような機能については非特権の場合には使えないようになっています。

この機能のひとつが、以前ブログで紹介したredirect_dir機能 です。

次のように、この機能を使うためにオプションを指定してマウントしようとするとエラーになります。

$ unshare --user --map-root-user --mount
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr,redirect_dir=on overlay overlay
mount: /home/karma/tmp/overlay: wrong fs type, bad option, bad superblock on overlay, missing codepage or helper program, or other error.

当然ですが、redirect_dir=off とするとマウントできます。

# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,userxattr,redirect_dir=off overlay overlay
# grep overlay /proc/self/mountinfo 
119 109 0:62 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work,redirect_dir=off,index=off,metacopy=off

他にも非特権の場合、metacopy 機能が使えないようです(こちらの機能は私はまだ調べていないので詳細はまたの機会に)。


  1. ここで使っている名前空間は拡張ファイル属性の名前空間でコンテナで使う名前空間ではありません。

5.11 で追加された OverlayFS の非特権マウント(1)

5.11 カーネルで overlayfs に大きな変更があったようで、久々にカーネルの新しい機能を試してみました。

とは言っても、結果だけ言うとすぐに終わってしまうので、すごいことをやったように見せかけるために、復習したりして順に説明していきましょう。時間のない方は最後の方だけ見れば良いです。

OverlayFS とは

Union Filesystem の実装の1つで、ディレクトリを重ね合わせて1つのディレクトリツリーを構成できます。Docker なんかではおなじみの機能ですね。おなじみの機能とはいえ、実際に直接マウントして動きを見たことがない方も多いかと思います。そこでまずは動きを簡単に見てみましょう。

重ね合わせるということで、下層側ディレクトリ、上層側ディレクトリを重ね合わせて、マウントポイント以下に見せます。他にワーク用の workdir として指定するディレクトリが必要です。

次の例では

  • 下層用ディレクトリとして lower
  • 上昇用ディレクトリとして upper
  • workdirとして work
  • マウントポイントとして overlay

というディレクトリを準備しています。lowerupper の中にはディレクトリとファイルを作成しておき、マウント後に overlay 以下にそれらのファイル・ディレクトリが見えることが確認できます。

$ mkdir lower upper work overlay # overlayfs用のディレクトリの作成
$ mkdir lower/lowerdir upper/upperdir # 下層、上層それぞれにディレクトリ作成
$ touch lower/lowerdir/lowfile upper/upperdir/upfile # ディレクトリ内にファイル作成
$ sudo mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
$ find overlay/
overlay/
overlay/lowerdir
overlay/lowerdir/lowfile
overlay/upperdir
overlay/upperdir/upfile
$ grep overlay /proc/$$/mountinfo 
68 33 0:67 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work

詳しくは連載記事 gihyo.jp や、カーネル付属文書 www.kernel.org をご覧ください。

他に関連記事としてこんな記事も書いています。

tenforward.hatenablog.com

5.11 カーネルで行われた非特権マウントのための変更と FS_USERNS_MOUNT

User Namespace内は、Namespace内では特権ユーザー、Namespace外では一般ユーザーという UID/GIDマッピングができる機能です。Namespace 内では特権を持つユーザーであっても、実際は特権を持たないユーザーでの処理がされているため、当然ながら一般的には User Namespace 内ではマウント操作はできません。

User Namespace について詳しくは連載の次の記事をご覧ください。

gihyo.jp

しかし、一部のファイルシステムについては、従来から User Namespace 内でマウントできました。例えば、コンテナ内で /proc や tmpfs などをマウントする操作は普通に行われている操作ではないかと思います。

このような User Namespace 内でファイルシステムをマウントできる機能は、非常に簡単な定義を行うだけで使えます。このためのファイルシステムに定義する定数が include/linux/fs.h に定義されています。

struct file_system_type {
        const char *name;
        int fs_flags;
#define FS_REQUIRES_DEV         1 
#define FS_BINARY_MOUNTDATA     2
#define FS_HAS_SUBTYPE          4
#define FS_USERNS_MOUNT         8       /* Can be mounted by userns root */
#define FS_DISALLOW_NOTIFY_PERM 16      /* Disable fanotify permission events */
  : (snip)

この FS_USERNS_MOUNT というのがそれで、ファイルシステムを実装する際にこの値を fs_flags に設定すると、コメントにあるように User Namespace 内の root が、そのファイルシステムをマウントできるわけです。

実は LXC 方面で使っていたため、Ubuntuカーネルにはこれまでも User Namespace 内で overlayfs をマウントするパッチが適用されていました(筆者がメンテナをつとめる Plamo Linux でも一時期適用されていたはずです)。

今回(5.11 カーネル)の OverlayFS の非特権マウントのパッチも非常に単純で、次のようなパッチです。これまで Ubuntu カーネルに適用されていたパッチも同じものです。

--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -2096,6 +2096,7 @@ static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags,
 static struct file_system_type ovl_fs_type = {
    .owner      = THIS_MODULE,
    .name       = "overlay",
+   .fs_flags   = FS_USERNS_MOUNT,
    .mount      = ovl_mount,
    .kill_sb    = kill_anon_super,
 };

ovl: unprivieged mounts

今回の OverlayFS に対するパッチは 10 個ほどのパッチとなっていますが、「非特権マウント」のために必要な変更は上記の変更だけです。他はより安全に処理を行うための修正のようで、今回だけでなく 5.8 でも変更が行われていたようです。

5.11 カーネルでの非特権 OverlayFS マウント

それでは先の例と同じディレクトリ、ファイルを使って非特権 OverlayFS を試してみましょう。使用するカーネルは 5.11.5 です。

$ uname -r
5.11.5-plamo64

「非特権」と言っても、先に説明したとおり「User Namespace 内の root がマウントできる」ということですので、unshare コマンドで User Namespace を作成して試します。(いずれにせよ mount コマンドは root でないと実行が失敗するようになってます)

ただ、ここで User Namespace だけを作ってもマウントは失敗します。

$ unshare --user --map-root-user
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
mount: /home/karma/tmp/overlay: permission denied. (失敗した)

これは Mount Namespace も元の Namespace とも独立している必要があるためです。

そこで次の例では unshare コマンドに --mount も指定して User/Mount Namespace を作成してみましょう。--map-root-user は unshare を実行するユーザーと User Namespace 内の root をマッピングするオプションです。次の例だと元の Namespace の UID: 1000 のユーザーと作成する Namespace 内の UID:0 をマッピングするということです。

$ id -u (現在のユーザーは UID:1000)
1000
$ unshare --user --map-root-user --mount (User Namespace と Mount Namespace を作成する)
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
# grep overlay /proc/self/mountinfo (マウント情報を確認する)
119 109 0:62 / /home/karma/tmp/overlay rw,relatime - overlay overlay rw,lowerdir=lower,upperdir=upper,workdir=work,index=off,metacopy=off
# find overlay/ (重ね合わせた状態でマウントできている)
overlay/
overlay/lowerdir
overlay/lowerdir/lowfile
overlay/upperdir
overlay/upperdir/upfile

マウントが成功しましたね。所有権も見ておきましょう。

# ls -l overlay/
合計 0
drwxr-xr-x 1 root root 14  314日  21:07 lowerdir/
drwxr-xr-x 1 root root 12  314日  21:07 upperdir/
# ls -l overlay/*
overlay/lowerdir:
合計 0
-rw-r--r-- 1 root root 0  314日  21:07 lowfile

overlay/upperdir:
合計 0
-rw-r--r-- 1 root root 0  314日  21:07 upfile

これらのファイルは元の Namespace のユーザー(UID: 1000)権限で作成しましたので、ちゃんと User Namespace 内でマウントしてもマッピング先のユーザー(UID: 0=root)の所有権になっています。

5.11 より前のカーネルでの実行例

一応、比較のために 5.11 より前のバージョンのカーネルで非特権マウントができないことも確認しておきましょう。ちょっと古いのですが、手元にあった 5.2 カーネルの環境で試してみました。

$ uname -r
5.2.1-plamo64
$ id -u
1000
$ unshare --mount --user --map-root-user
# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
mount: /home/karma/tmp/overlay: permission denied.

同様に実行してみました。失敗しましたね。

(つづく)

マウントプロパゲーション(8)〜 Namespaceとプロパゲーション(2)〜

これまでの続きです。まだまだ続きます。

これまでと同様に完全に私個人が理解するための資料です(man 7 mount_namespaceみれば書いてますし)。間違いの指摘は大歓迎です。

前回と同様に引き続きマウントプロパゲーションとMount Namespaceの関係も見ていきます。今回はslaveです。

shell_1# mount -t tmpfs tmpfs /mnt_shrd/
shell_1# mount -t tmpfs tmpfs /mnt_mstr/
shell_1# mount --make-shared /mnt_shrd/ (sharedに設定)
shell_1# mount --make-shared /mnt_mstr/ (sharedに設定)
shell_1# grep /mnt /proc/self/mountinfo (sharedになっていることを確認)
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw

tmpfsをふたつマウントし、両方ともsharedに設定しました。mountinfoファイルを確認するとsharedとなっているのがわかります。

ここで別のシェル(shell_2)を起動し、新しくMount Namespaceを作成してみましょう。

shell_2# unshare --mount --propagation unchanged /bin/bash (Mount Namespaceを作成)
shell_2# grep /mnt /proc/self/mountinfo (sharedであることを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw

--propagation unchangedを指定していますので、さきほど行ったマウントはいずれもsharedで元のNamespaceと変わりません。ここでこのうちのひとつのマウントをslaveに設定してみましょう。

shell_2# mount --make-slave /mnt_mstr/ (slaveに設定)
shell_2# grep /mnt /proc/self/mountinfo (設定した方のマウントがslaveになったことを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw

ここでは/mnt_mstrの方をslaveに設定しましたので、オプショナルフィールドはmaster:331となっており、もとのMount Namespaceでshared:331となっていたマウントのslaveになっていることがわかります。

この新しいMount Namespace内で、このふたつのマウントのサブマウントを作成し、mountinfoファイルを確認してみましょう。

shell_2# mkdir /mnt_shrd/a
shell_2# mkdir /mnt_mstr/b
shell_2# mount -t tmpfs tmpfs /mnt_shrd/a
shell_2# mount -t tmpfs tmpfs /mnt_mstr/b
shell_2# grep /mnt /proc/self/mountinfo (mountinfoを確認)
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw
679 677 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
694 678 0:57 / /mnt_mstr/b rw,relatime - tmpfs tmpfs rw

もともとマウントされていた/mnt_shrd/mnt_mstrは変わりません。そして新たに行ったサブマウントは、

  • /mnt_shrd/aは新たなsharedマウントでIDが338(親のマウントからsharedを継承)
  • /mnt_mstr/bはprivateマウント

となっていることがわかります。

最初のシェル(shell_1)に戻ってmountinfoファイルを確認してみると、

shell_1# grep /mnt /proc/self/mountinfo 
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw
680 590 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw

別のシェル(shell_2)で作成したMount Namespace内で行ったsharedマウントは、もとのMount Namespaceにも伝播しています。いっぽうでslaveに設定したほうのマウントは伝播していません。

ここで最初のシェル(shell_1)の方で新たにマウントを行ってみましょう。

shell_1# mkdir /mnt_shrd/c
shell_1# mkdir /mnt_mstr/d
shell_1# mount -t tmpfs tmpfs /mnt_shrd/c
shell_1# mount -t tmpfs tmpfs /mnt_mstr/d
shell_1# grep /mnt /proc/self/mountinfo 
590 31 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
604 31 0:55 / /mnt_mstr rw,relatime shared:331 - tmpfs tmpfs rw
680 590 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
695 590 0:58 / /mnt_shrd/c rw,relatime shared:345 - tmpfs tmpfs rw (←新たに行われたマウント)
747 604 0:59 / /mnt_mstr/d rw,relatime shared:352 - tmpfs tmpfs rw (←新たに行われたマウント)

あらたにIDが345352のsharedマウントが行われています。これを別のシェル(shell_2)のMount Namespace内で確認してみましょう。

shell_2# grep /mnt /proc/self/mountinfo 
677 636 0:52 / /mnt_shrd rw,relatime shared:324 - tmpfs tmpfs rw
678 636 0:55 / /mnt_mstr rw,relatime master:331 - tmpfs tmpfs rw
679 677 0:56 / /mnt_shrd/a rw,relatime shared:338 - tmpfs tmpfs rw
694 678 0:57 / /mnt_mstr/b rw,relatime - tmpfs tmpfs rw
696 677 0:58 / /mnt_shrd/c rw,relatime shared:345 - tmpfs tmpfs rw
748 678 0:59 / /mnt_mstr/d rw,relatime master:352 - tmpfs tmpfs rw

最初のシェル(shell_1)で行われたID 345352のマウントは両方とも伝播しており、slaveマウントのサブマウントのほうはmasterとなっていますので、ID 352に対するslaveマウントであることがわかります。