OverlayFSは、複数のディレクトリーを重ね合わせて 1 つに見せてマウントするファイルシステムです。みなさん、コンテナを使っているならば、ほぼその裏側では OverlayFS が動いていることでしょう。
LXC/Incus 推しの私の環境では OverlayFS が動くことはほとんどないですが。:-p
重ね合わせには「下層側」ディレクトリーと「上層側」ディレクトリーを指定し、それを重ねます。下層側はいくつでも指定できます。このあたりの詳細は、ちょっと古いのですが、私の連載「LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術」の第 18 回をご覧ください。
この記事は少し古いので、無駄な部分(今と異なる部分)が多いので、もっと効率的に勉強したい!という方は、おや、ちょうど良い本が…😂
技術書典 16 にて、"Linux Container Book" 第 4 弾リリースです。中身は大体 OverlayFS の話です!! ぜひお買い求めください!!
OverlayFS における変更の記録
OverlayFS のファイルやディレクトリーなどのオブジェクトに変更を加えると、通常は上層側に記録されます。下層側にしか存在しないオブジェクトの場合でも、上層側に変更が加えられます。
その際、下層側にしかないオブジェクトは上層側に作成されたり、コピーされたりします。
詳しい動きは連載か本でご覧ください。連載でも十分にこのあたりは理解できます。
下層側に存在するディレクトリーのリネーム(移動)
さて、下層側にだけ存在するディレクトリーをリネームする場合、OverlayFS は次の 2 つの方法で処理できます。これはカーネル付属文書に書かれています。
- return EXDEV error: this error is returned by rename(2) when trying to move a file or directory across filesystem boundaries. Hence applications are usually prepared to handle this error (mv(1) for example recursively copies the directory tree). This is the default behavior.
- If the “redirect_dir” feature is enabled, then the directory will be copied up (but not the contents). Then the “trusted.overlay.redirect” extended attribute is set to the path of the original location from the root of the overlay. Finally the directory is moved to the new location.
1 がデフォルトの動きです。下層側に存在するディレクトリーをリネームすると、EXDEV
というエラーが返ります。これは、ファイルシステムでリネームする場合にも一般的に返るエラーなんだと思います(しらんけど)。
一般的な ext4 や XFS といったファイルシステムがマウントされているシステムで、リネーム元とリネーム先が同じファイルシステムに存在しない場合、メタデータを変更して済ますわけにはいきませんので、実際のオブジェクトの作成やコピーなどが必要になります。
このエラーが返ったときは、呼び出し元のコマンドがよしなに処理します。つまり mv
コマンドの場合でも、mv
コマンドが EXDEV
を受け取って必要な処理を行います。
OverlayFS は、既存のコマンドでも使われているこの仕組みをうまく利用しています。
OverlayFS において、下層側のオブジェクトに変更が加わると、下層側→上層側間のオブジェクトのコピーが発生します。コピーしたあとに、上層側で変更を加えます。
ディレクトリーをリネームした場合も、EXDEV
が返ると、mv
コマンド側で必要な処理がされますので、そのあたりを追ってみました。
- 下層側にあるディレクトリーを
mv
でリネーム - OverlayFS が
EXDEV
を返す mv
が上層にリネーム先のディレクトリーを作成するmv
がリネーム元のディレクトリーに存在するファイルをオープンmv
がリネーム先のディレクトリーに 4 と同じ名前でファイル作成mv
がリネーム元のディレクトリー内ファイルのデータを読み、リネーム先のファイルに書き込みmv
が下層側のリネーム元のオブジェクトの削除を行う- OverlayFS が削除に対応して whiteout を作成
OverlayFS としては、EXDEV
を返すだけで、あとは呼び出し元のコマンド mv
が必要な処理を全部やってくれて、OverlayFS はそれに応じた処理を淡々とこなすだけで、ちゃんと OverlayFS として矛盾のない動きが得られています。しかも、ユーザー側のプログラム(ここでは mv
)の変更は不要です。これはなかなか良くできてますね。
実際の内部的な処理を追う
先に紹介した動きを実際に確認していきましょう。
まずは、OverlayFS 環境の準備です。
$ mkdir lower upper overlay work $ mkdir -p lower/lowerdir upper/upperdir $ touch lower/lowerdir/testfile
今回は上層側のオブジェクトは不要ですが、一応準備してあります。これでマウントします。
$ sudo mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work,redirect_dir=off overlay overlay $ tree . ├── lower │ └── lowerdir │ └── testfile ├── overlay │ ├── lowerdir │ │ └── testfile │ └── upperdir ├── upper │ └── upperdir └── work └── work [error opening dir] 9 directories, 2 files
下層側 lower
と上層側 upper
を重ね合わせて overlay
にマウントしています。
これで、overlay
以下に移動して、下層側にあるディレクトリーを mv
でリネームします。このときの内部的な処理を、みなさんもお好きな strace
コマンドで追っかけます。
$ cd overlay $ strace mv lowerdir upperdir
まず、リネーム処理がされているところを探します。rename
をキーワードにすると見つかりました。ここからは strace の出力の一部だけをお見せしますが、上から下に時系列で並んでいると思ってください。
execve("/usr/bin/mv", ["mv", "lowerdir", "newerdir"], 0x7ffdca0c8470 /* 24 vars */) = 0 :(snip) renameat2(AT_FDCWD, "lowerdir", AT_FDCWD, "newerdir", RENAME_NOREPLACE) = -1 EXDEV (Invalid cross-device link)
最初に execve
で mv
コマンドを実行しただいぶ後に renameat2
が呼ばれているのがわかります。そして、たしかにエラーとなっていて、EXDEV
が返っているのがわかります。
次に関係しそうなのが、上層側に必要なオブジェクトを作成することですね。まずはディレクトリーの作成でしょう。
mkdir("newerdir", 0700) = 0
mkdir
で newerdir
が作成されていることがわかります。変更は上層側に行われます。
次に、readdir
で lowerdir
内のオブジェクトを取得しているのでしょうか。
openat(AT_FDCWD, "lowerdir", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 :(snip) getdents64(3, 0x559d7c4a9e40 /* 3 entries */, 32768) = 80 getdents64(3, 0x559d7c4a9e40 /* 0 entries */, 32768) = 0 close(3) = 0
そしてディレクトリー内のファイルの処理を行います。
openat(AT_FDCWD, "lowerdir/testfile", O_RDONLY|O_NOFOLLOW) = 3 newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=0, ...}, AT_EMPTY_PATH) = 0 openat(AT_FDCWD, "newerdir/testfile", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4 newfstatat(4, "", {st_mode=S_IFREG|0600, st_size=0, ...}, AT_EMPTY_PATH) = 0
次は、リネーム元からリネーム先にデータをコピーしなければならないので、元のファイルを openat
でオープンしていますね。そして、移動先のファイルを openat
で作成しています。
read(3, "", 131072) = 0 :(snip) close(4) = 0 close(3) = 0
リネーム元のファイルからデータを読んでますね。書き込んでるところは見つからんけどw close(4)
の前にはファイルの属性を色々設定しています。
unlinkat(4, "testfile", 0) = 0 :(snip) unlinkat(AT_FDCWD, "lowerdir", AT_REMOVEDIR) = 0
その後、下層側ディレクトリー内のファイルとディレクトリーを削除しています(たぶん)。
削除を行うと、OverlayFS は、上層側に下層側のオブジェクトをマスクするための "whiteout" という特殊なデバイスファイル(番号0:0のキャラクターデバイス)を作成します。なので、上記の処理で whiteout が作成されます。
普通は、同じデバイス上のリネームでは返さない EXDEV
というエラーを返すようにしただけで、ユーザー側の変更は不要で、ユーザー側の実装含めて、ちゃんと矛盾なくよしなに動くようになるという、なかなかうまくやってるなあ、と思ったのでブログにしました。
ちなみに、カーネル付属文書の 2 は、OverlayFS でオプション機能を有効にすると、上記のような単純なファイルのコピーが起こらなくなるというものです。詳しくは本を買ってくださいw
なんかイマイチツッコミが足りない記事ですが、技術書典の本をよろしくお願いいたします。