はじめに
OverlayFS(Overlay Filesystem)―――
Linux 上の Docker でよく利用される仕組みとして知られていますが、あらためて考えてみるといろいろ疑問が湧きました。
「名前に “ファイルシステム” と付いているけど、ext4 とかの仲間?」
「ファイルを保存するだけではなく、どのファイルを見せる/見せない まで制御してるって、ファイルシステムにしては利口すぎないか?」
私は、OverlayFS が複数の層を重ねる仕組みだということだけは なんとなく知っていましたが、その裏で何が起きているのかはほとんど理解していないということに気付きました。
本記事では、実際に OverlayFS をマウントして挙動を観察しながら、その仕組みを調べていきます。さらに、マウント中に下層のファイルを直接変更するという、少し悪い実験も行ってみます。
概念の理解を主な目的とするため、前提となるファイルシステムや VFS についても簡単に触れますが、詳細には踏み込みません。また、Linux の基本的なファイル操作や「マウント」という言葉については、知っているものとして進めます。
仕組みをつかみやすくするため、一部の説明を単純化しています。誤りや不正確な点がありましたら、ご指摘いただけるとうれしいです。
ファイルシステムおさらい
ファイルシステムは、データをファイルやディレクトリという形で整理し、名前を付けて操作できるようにする仕組みです。
ファイルシステムの種類は 1 つではなく、Linux では ext4、XFS、Btrfs など、用途や特徴の異なるさまざまなファイルシステムを利用できます。
ext4 や XFS のようなファイルシステムは、記憶装置上のデータやメタ情報も管理しますが、どのように保存するか、といったルールや仕組みがファイルシステムごとに異なります。
VFS (Virtual File System)の役割
ファイルシステムにはいくつか種類があると述べました。
でも、使用するファイルシステムに応じて、アプリケーションがファイルの読み書き方法を変えなければいけないとしたら大変です。
どのファイルシステムを使うときでも同じように操作したい!
それを実現するために Linux カーネル内に設けられている抽象化レイヤーが VFS です。
VFS のおかげで、アプリケーションはファイルシステムの種類を意識せず、open、read、write などの共通したシステムコールを使ってファイルを操作できます。
アプリケーションからの要求をカーネル内部で受け取り、共通のインターフェースとして扱えるようにしたうえで、それぞれのファイルシステムが実装した処理へ受け渡すのが VFS の主な仕事です。
では本題の OverlayFS とは
まずは信頼と実績の Arch Wiki より抜粋。
Overlayfs を使用すると、読み書きできる通常のディレクトリツリーを、他の読み取り専用のディレクトリツリーの上に重ねることができます。変更は全て、上部の書き込み可能なレイヤーに行われます。
OverlayFS は、複数のディレクトリツリーを重ね合わせ、あたかも 1 つのファイルシステムであるかのように見せてくれる仕組みです。
ここでいう「ディレクトリツリー」は、ファイルシステム上に存在するディレクトリやファイルの集合を指します。
OverlayFS 自体は、ext4 のように記憶装置上へデータをどう格納するかを管理するものではありません。
アプリケーションからの操作は VFS を通して OverlayFS に渡され、OverlayFS はパス解決時に後述する lower や upper として指定されたディレクトリを参照して、どのファイルを見せるか、変更をどこへ書き込むかを判断します。
つまり OverlayFS は、「ファイルを直接格納するファイルシステム」ではなく、**「他のファイルシステム上にあるディレクトリツリーを組み合わせ、1 つのファイルシステムに見せてくれるファイルシステム」**と考えると、イメージしやすいかもしれません。
冒頭の「ext4 とかの仲間?」への答えは、「Linux の VFS に接続されるファイルシステムという意味では仲間。ただし、担当している役割はかなり違う」というのが私なりの理解です。
OverlayFS の仕組み
OverlayFS は、大きく下層ディレクトリ (lowerdir) と上層ディレクトリ (upperdir) に分かれており、複数のディレクトリツリーを重ね合わせ、ひとつのディレクトリツリーとして見せてくれます。
統合されたディレクトリツリーは、OverlayFS のマウントポイントから見えるようになります。(本記事では、このマウントポイントを merged と呼びます。)
upperdir は OverlayFS からの変更を書き込む層です。
lowerdir は OverlayFS 経由で変更を書き込む対象にはならず、基本的には読み取り元として扱われます。
※ ただし、本記事の後半で実験するように、lowerdir の実体そのものが読み取り専用であるとは限りません。
また、書き込み可能な OverlayFS をマウントする場合は、workdir というディレクトリも必要です。workdir は OverlayFS が内部処理に使用する作業用のディレクトリで、空のディレクトリとして用意し、upperdir と同じファイルシステム上に配置する必要があります。
OverlayFS のファイルの探し方
OverlayFS はパスからファイルを探す際に、upper と lower のどちらに対象が存在するかを調べます。
読み込み専用でファイルにアクセスする場合、ファイルが upperdir に存在すれば upperdir のファイルをオープンし、upperdir になければ lowerdir のファイルをオープンします。
また、ディレクトリでは upperdir と lowerdir の両方に同名のディレクトリがある場合は、両者の内容を統合します。
書き込みを行う場合は、upperdir に存在すればそのままそのファイルを編集します。まだ upperdir になく、lower にあるファイルを変更する場合や、内容や属性を変更する場合は、必要に応じて upper に copy-up してから変更します。
なお、merged 経由で lowerdir にあるファイルを削除しても、lowerdir の実体が削除されるわけではありません。代わりに upperdir 側に、その名前を隠すための whiteout が作られ、lowerdir の同名ファイルが見えなくなります。
ファイルシステム、VFS、OverlayFS は、図にするとざっくり以下のような関係性です
アプリケーション
↓ open / read / write
VFS
↓
OverlayFS
├── upperdir ── ext4 などの上にあるディレクトリ
├── lowerdir ── ext4 などの上にあるディレクトリ
└── workdir ───── 内部処理に使用
upper / lower は誰が決めてるの?
これは OverlayFS 自身が勝手に探すのではなく、マウントする側が指定します。
たとえばこんな具合です:
$ sudo mount -t overlay overlay \
-o lowerdir=/lower,upperdir=/upper,workdir=/work \
merged
Docker の overlay2 ストレージドライバーでは、イメージレイヤーを lowerdir、コンテナ固有の書き込み層を upperdir として用意し、OverlayFS をマウントします。
なお、OverlayFS は Linux カーネルが提供するファイルシステムであり、overlay2 はそれを利用する Docker のストレージドライバーです。
実験① 実際に OverlayFS をマウントして体験してみる
※ 本実験は、VirtualBox 上の Ubuntu 26.04 LTS、Linux カーネル 7.0.0-27-generic、ext4 環境で実施しました。
ここからは、ここまで説明してきた upperdir, lowerdir, workdir, merged をそれぞれ観察してみます。
- 実験場に各ディレクトリを作成し、
lowerディレクトリにだけファイルを置きます
$ mkdir -p ~/overlayfs-lab/{lower,upper,work,merged}
$ echo "I am lower" > ~/overlayfs-lab/lower/hello.txt
# 実験②用の準備
$ echo "original: before direct edit" > ~/overlayfs-lab/lower/illegal.txt
- 絶対パスを作ってマウントします
LAB="$(realpath ~/overlayfs-lab)"
$ sudo mount -t overlay overlay \
-o lowerdir="$LAB/lower",upperdir="$LAB/upper",workdir="$LAB/work" \
"$LAB/merged"
-
mergedディレクトリを確認してみると…lowerディレクトリに配置したファイルが見えます!
$ cat ~/overlayfs-lab/merged/hello.txt
I am lower
- 今度は
merged経由で hello.txt を書き換えてみます。
$ echo "I was edited through merged" > ~/overlayfs-lab/merged/hello.txt
- 各ディレクトリの状態を確認すると、次のような結果になります
$ cat ~/overlayfs-lab/lower/hello.txt
I am lower
$ cat ~/overlayfs-lab/upper/hello.txt
I was edited through merged
$ cat ~/overlayfs-lab/merged/hello.txt
I was edited through merged
以上の実験から、マウント後に merged を通したファイル操作では、
-
lower/hello.txtのファイルは変更されない -
upper/hello.txtが作られる -
merged/hello.txtからは upper 側が見える
という copy-up を観察できます。
実験② lower を無理やり書き換えてみる
実験① で見てきたように、OverlayFS を利用する側は、マウントポイントとして用意された merged を経由してファイルにアクセスするのがお作法です。
ここでは、あえてそのお作法を破り、実験①で使用した OverlayFS をマウントしたまま、今度はマウントポイントを経由せずにlowerdir の実体を直接変更してみたらどうなるのかを実験してみます。
※ マウント中に lowerdir や upperdir の実体を直接変更することは、OverlayFS で許可されている操作ではありません。変更した場合の挙動は未定義です。試す場合は、VirtualBox 環境などを利用するのがおすすめです。
マウントまでは実験① と同じ操作ですが、今度はlowerdir にしか存在しないファイルを直接上書きします。
$ echo "illegal: edited directly in lower" \
> ~/overlayfs-lab/lower/illegal.txt
確認すると、merged からも変更後の内容が見えました。
$ cat ~/overlayfs-lab/lower/illegal.txt
illegal: edited directly in lower
$ cat ~/overlayfs-lab/merged/illegal.txt
illegal: edited directly in lower
次に、同じパスのファイルを別のファイルで置き換えてみます。
$ echo "illegal: replaced with another file" \
> ~/overlayfs-lab/lower/replacement.txt
$ mv ~/overlayfs-lab/lower/replacement.txt \
~/overlayfs-lab/lower/illegal.txt
すると、lowerdir と merged で異なる内容が見える状態になりました…!
$ cat ~/overlayfs-lab/lower/illegal.txt
illegal: replaced with another file
$ cat ~/overlayfs-lab/merged/illegal.txt
illegal: edited directly in lower
同じ illegal.txt というパスにもかかわらず、lowerdir からは置き換え後のファイル、merged からは置き換え前の内容が見えています。
ですが、一度アンマウントして再度マウントすると、merged からも置き換え後の内容が見えるようになりました。
$ cat ~/overlayfs-lab/merged/illegal.txt
illegal: replaced with another file
最初の上書きでは、既存のファイルそのものの内容が変更されましたが、mv による置き換えでは、同じ名前が別のファイルを指すようになりました。
今回の実験結果からは、少なくともマウント中の OverlayFS が、毎回 lowerdir のパス名から単純にファイルを探し直しているわけではなく、既に解決済みのファイルへの参照やキャッシュなどの情報を利用している可能性が考えられます。
最初の上書きでは、既存のファイルそのものの内容が変更されたため、保持している参照からも変更後の内容が見えました。しかし、mv による置き換えでは、同じパス名が別のファイルを指すようになったため、OverlayFS 側に保持された以前の参照との食い違いが生じたと考えられます。
※ ただし、マウント中に lowerdir や upperdir の実体を直接変更した場合の挙動は未定義のため、今回と同じ結果がほかの環境でも再現されるとは限りません。
おわりに
そういえば昔のアニメーションは 1 枚 1 枚、手描きのセル画を重ね合わせて制作されていましたが、もはやセル画をご存知の方は少ないかしら。
OverlayFS は、下層にある共通部分をそのまま再利用し、変更が必要な部分だけを上層に持つことができます。
これは、昔のアニメーション制作で使われていたセル画に少し似ていますね。
背景はそのまま使い回し、キャラクターや表情など、動かしたい部分だけを別のセルに描いて重ねる。
OverlayFS も、変更されない下層を共有しながら、変更された部分だけを上層に持つことで、全体をひとつのファイルシステムとして見せています。
Docker が大量のイメージレイヤーを高速に扱える理由も、この OverlayFS の仕組みを知ると納得できます。
ただ、便利な仕組みだとは分かったものの、Docker 以外でどのように活用されているのかは今後の私の学習課題です。
参考文献: