OverlayFSの物理(レイヤーが重なるCopy-on-Writeの構造)

2026-11-06 / シリーズ「コンテナとLinuxカーネルの物理」第3話 / Docker, Linux, OverlayFS, ファイルシステム, ストレージ, CoW

仮想マシン(VM)を複製したことがある人なら、誰しもが知っている苦痛があります。 数ギガバイト、数十ギガバイトある仮想ディスク(.vmdk や .qcow2)をコピーするために、プログレスバーを眺めながら数分間待たされ、複製するたびにホストのSSD容量がゴリゴリと削られていきます。

しかし、Dockerでコンテナを起動するとき、私たちは異様な光景を目にします。

docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker run -d --name web3 nginx

数十秒どころか、コマンドを叩いて「コンマ何秒」という瞬きする間にコンテナが立ち上がります。 しかも、100個起動しようが1000個起動しようが、ホストマシンのディスク使用量はほとんど増えません。それどころか、web1 のコンテナ内で設定ファイルを書き換えても、隣の web2 や大元のイメージには何の影響も与えません。

「なぜ、巨大なOSファイルツリーを丸ごと持ちながら、一瞬で起動し、容量も喰わず、お互いを汚さずに自由に変更できるのか?」

その秘密は、Linuxカーネルに組み込まれたユニオンマウント・ファイルシステム**「OverlayFS」と、ストレージ工学の至宝である「Copy-on-Write(CoW:書き込み時コピー)」**の物理にあります。


1. アニメのセル画の物理:3つの階層(lower, upper, merged)

OverlayFSの動作原理は、コンピュータというより**「昔ながらのアニメーション制作(セル画)」**に驚くほど似ています。

背景が描かれた透明なシート(セル画)を何枚も机の上に重ね、一番上から覗き込むと、人間の目には「ひとつの完成した絵」として見えます。 下のセル画にある木や山が見えつつ、上のセル画に描かれたキャラクターが重なっている部分は、下の背景が隠されてキャラクターが見えます。

透明なセル画のシートに、筆でキャラクターへ絵の具を塗る手元
透明なシートにキャラクターを描く、本物のセル画の制作風景。OverlayFSは、この「シートの重ね合わせ」をLinuxのディレクトリ構造で実現している。写真:Janke(CC BY-SA 3.0/Wikimedia Commons)

OverlayFSは、この「透明シートの重ね合わせ」をLinuxのディレクトリ構造で実現しています。

merged(合成マウントポイント) プロセスには「完全な1つのルートツリー(/bin, /etc, /var …)」に見える 上から見下ろした合成像 upperdir(最上位:コンテナ読み書き層 R/W) 新規作成・変更されたファイルだけが実体として置かれる workdir(中間作業領域) アトミックな移動・メタデータ操作用の一時バッファ lowerdir 2(イメージ差分層 Read-Only) apt-get install で追加されたパッケージ群 lowerdir 1(ベースイメージ層 Read-Only) AlpineやUbuntuの初期ファイル群

OverlayFSを構成する中核要素は、以下のディレクトリです。

  1. lowerdir(下層:読み取り専用 Read-Only): Dockerイメージの実体です。OSの基本コマンドやライブラリが格納されています。この層は**「絶対に書き換え不能(不変)」**であり、100個のコンテナが立ち上がっても、メモリおよびディスク上で全員がこの1つの lowerdir を完全に共有(参照)します。
  2. upperdir(上層:読み書き可能 Read-Write): コンテナが起動した瞬間に、コンテナごとに新しく作られる「薄っぺらな空っぽのディレクトリ」です。コンテナ内で行われた新規作成や変更は、すべてこの upperdir だけに書き込まれます。
  3. workdir(作業領域): カーネルが内部で使用する一時作業用ディレクトリです。ファイルのコピーや属性変更をアトミック(不可分)に完了させるための裏舞台です。
  4. merged(合成マウントポイント): lowerdir と upperdir をカーネルが重ね合わせて、プロセスに見せる**「統合された仮想ディレクトリ」**です。コンテナのプロセスは、この merged を自分のルート(/)として認識します。

コンテナの起動が一瞬で終わる理由が、ここにあります。 巨大なファイルシステムをコピーしているのではなく、**「空の upperdir を1個作り、既存の lowerdir と重ねてマウント(mount -t overlay)しているだけ」**だからです。行われているのはディスクの物理複製ではなく、カーネル内のポインタ結合に過ぎません。


2. Copy-on-Write(CoW)の物理:変更されるその瞬間まで複製しない

では、コンテナの中で「元から存在するファイル」を変更しようとしたとき、裏側では何が起きているのでしょうか? 例えば、lowerdir に最初から入っている /etc/hosts を書き換える瞬間を追ってみましょう。

1. 読むとき(Read):コピーは起きない open(…, O_RDONLY) プロセスが読み込みを要求 upperdirに無い lowerdirから直接読み出し 高速にデータを返す CoW発生せず・ゼロコスト 2. 書くとき(Write):Copy-on-Write発動! open(…, O_WRONLY) プロセスが書き換えを要求 ★ CoW発動(Copy-Up) 下層から上層へ丸ごと物理コピー upperdir側を変更・保存 下層の原本は無傷のまま

読むだけなら「ゼロコスト」

コンテナがファイルを読み込む(Read)だけのときは、upperdir にファイルがなくても、カーネルが勝手に下の lowerdir を見に行って直接データを返します。100個のコンテナが同時に同じファイルを読んでも、ディスクI/Oもメモリも一切消費されません。

初めてペンを入れた瞬間に「クローン」が生まれる

しかし、プロセスが書き込み権限(O_WRONLY や O_RDWR)を指定して open() システムコールを発行した瞬間、カーネルが割り込みます。

  1. 探索:upperdir にそのファイルが存在するか調べる。
  2. 複製(Copy-Up):存在しない場合、下の lowerdir からファイル丸ごと1個を、現在の上層 upperdir へと物理コピーする。
  3. 書き込み:コピーが終わった後、プロセスには upperdir 側のファイル記述子(fd)を渡し、そこにデータを書き込ませる。

これが**「Copy-on-Write(書き込み時コピー)」**の物理です。 以後のアクセスでは、上層(upperdir)にある新しいファイルが下の古いファイルを覆い隠すため、プロセスからは「ファイルが書き換わった」ように見えます。 一方、下の lowerdir にある元ファイルは無傷のまま残るため、他のコンテナには何の影響も及びません。

触って試す:Copy-on-Writeの体験

🗂️ Copy-on-Writeの体験(下層は絶対に汚れない)

下層(lowerdir)にあるファイルを選んで、「読み取る」と「書き換える」を押してみてください。読み取りでは何も起きませんが、書き換えた瞬間にだけ上層(upperdir)へコピーが生まれます。

まずはファイルを読み取ってみてください。


3. ファイルを消したら何が起きるのか?「Whiteout」という黒い布

ここで、鋭い疑問が浮かびます。 「lowerdir は読み取り専用で、絶対に改変できないはずだ。それなら、コンテナの中で元からあるファイルを削除(rm /etc/hosts)したら、一体どうやって消すのか?」

下の層を削り取ることは物理的に許されません。 そこでOverlayFSは、消去を**「見えないように隠す特殊なオブジェクトを上に被せる」ことで表現します。これが「ホワイトアウト(Whiteout)」**です。

削除前 rm hosts 実行後 upperdir:空っぽ 何も置かれていない upperdir:★ Whiteout 同名のキャラクタデバイス (0, 0) 透過して見える 完全に遮光・目隠し! lowerdir:hostsファイル 実体が存在する lowerdir:元のhosts 不変のまま残存している ➔ mergedからは hosts が見える ➔ mergedから hosts が消えて見える

キャラクタデバイス(メジャー0, マイナー0)の目隠し布

コンテナ内でファイルを削除すると、カーネルは upperdir の中に、**削除されたファイルと全く同じ名前の「特殊なキャラクタデバイス(メジャー番号0, マイナー番号0)」**を作成します。

OverlayFSのレイアウトエンジンは、上からディレクトリを走査するときにこの「メジャー0, マイナー0」のデバイスを見つけると、**「あ、この名前のファイルは削除されたことになっているんだな」**と解釈し、下層にある同名のファイルを merged の視界から完全に抹消します。

ファイルを消したはずなのに、ディスクの物理レイヤーでは**「削除マークのついた極小のデバイスファイルが upperdir に新しく1個書き込まれている」**のです。 この徹底した「追加のみで状態を表現する」不変アーキテクチャこそが、堅牢なレイヤードファイルシステムの根幹を支えています。


4. Linux標準コマンドだけでOverlayFSを体感する(1分ハンズオン)

このOverlayFSの物理は、Dockerを一切使わずに、Linuxの標準コマンド mount -t overlay だけで今すぐ手元で実証できます。

実験:手動でセル画を重ねてみる

Linux環境(UbuntuやWSL2など)のターミナルで、以下のコマンドを実行してみてください。

# 1. 実験用のディレクトリを作成
mkdir -p /tmp/overlay-test/{lower,upper,work,merged}

# 2. 下層(ベースイメージ役)にファイルを作成
echo "Original Data from Lower" > /tmp/overlay-test/lower/file.txt

# 3. ★ OverlayFSで下層と上層をマウント!
sudo mount -t overlay overlay \
  -o lowerdir=/tmp/overlay-test/lower,upperdir=/tmp/overlay-test/upper,workdir=/tmp/overlay-test/work \
  /tmp/overlay-test/merged

マウントが完了した瞬間、合成窓口である merged の中を覗いてみます。

ls /tmp/overlay-test/merged
# ➔ file.txt が見える!
cat /tmp/overlay-test/merged/file.txt
# ➔ "Original Data from Lower" と表示される

この時点では、上層(upper)は空っぽです。 では次に、合成窓口(merged)にあるファイルを書き換えてみましょう。

# マウントポイント経由で追記してみる(CoW発動!)
echo "Modified by Container" >> /tmp/overlay-test/merged/file.txt

書き換えた後、それぞれのディレクトリの実体を暴きます。

# 1. 下層(lower)の中身を確認
cat /tmp/overlay-test/lower/file.txt
# ➔ "Original Data from Lower"(★元データは1文字も変わっていない!)

# 2. 上層(upper)の中身を確認
cat /tmp/overlay-test/upper/file.txt
# ➔ "Original Data from Lower\nModified by Container"(★ここにだけ複製・更新されている!)

lower は神聖不可侵のまま守られ、upper にだけ差分データが生まれ、ユーザーには merged を通して最新の姿が見える。 Dockerイメージとコンテナのレイヤー構造の正体は、この数行のシェルコマンドで再現できるカーネルの標準マウント機能そのものだったのです。


5. 実務の罠:Copy-on-Writeが引き起こす「巨大DBの性能劣化」

OverlayFSは、Webサーバーやマイクロサービスのような「ステートレス(状態を持たない)」なアプリケーションにとっては奇跡のような恩恵をもたらします。 しかし、インフラの現場で**「データベース(PostgreSQLやMySQL)」をDockerコンテナで安易に動かそうとした瞬間、強烈な物理のしっぺ返しを喰らう**ことになります。

10GBのファイルに「1バイト」書くだけで10GBコピーされる

もし、コンテナイメージの lowerdir の中に、10GBある巨大なデータベースファイル(data.db)が入っていたとします。 このコンテナを起動し、データベースに**「たった1行のレコード(数百バイト)」**を書き込もうとすると、何が起きるでしょうか?

🐬 MySQL:1行だけINSERT実行 OverlayFS:書き込みを検知 upperdirにあるか? ➔ 無い! ★ 10GB丸ごとCopy-Up開始! SSDへの激しいI/Oバースト発生 ディスク容量が一瞬で10GB蒸発 / クエリが数十秒間完全にフリーズ!

OverlayFSのCopy-on-Writeは、ブロック単位ではなく**「ファイル単位」**で行われます。 たとえ1バイトの変更であっても、カーネルは10GBの巨大ファイル全体を lowerdir から upperdir へと忠実に物理コピーし始めます。 その間、ディスクI/Oは飽和し、SSDの書き込み寿命は削れ、データベースのトランザクションは何十秒もブロックされてタイムアウトします。

解決策:コンテナの皮を剥ぐ「Volumeマウント」

この物理ボトルネックを回避するために、実務では必ず**「ボリューム(Volume / Bind Mount)」**を使います。

# OverlayFSをバイパスして、ホストの生ディレクトリを直接マウントする
docker run -d -v /data/mysql:/var/lib/mysql mysql:latest

ボリュームを指定されたディレクトリは、OverlayFSの階層構造から完全に除外され、ホストマシンのファイルシステム(ext4やXFS)にダイレクトに貫通します。 Copy-on-Writeのオーバーヘッドがゼロになり、ブロック単位のネイティブな速度で読み書きできるようになります。 「何でもコンテナのレイヤーに乗せるな、巨大データはボリュームで抜け」。これがコンテナインフラを破綻させないための絶対の鉄則です。


結論:不変(Immutable)の基盤が生み出す俊敏性

従来のシステム管理では、サーバーは「生きたペット」でした。 パッケージを更新し、設定ファイルを直接書き換え、時間が経つにつれて誰にも元の状態が分からなくなる「環境のドリフト(秘伝のタレ化)」に怯えていました。

DockerとOverlayFSが成し遂げた真の革命は、**「すべての基盤を不変(Immutable)にしたこと」**です。

  • 下層のイメージ(lowerdir)は絶対に汚されない。
  • 変更はすべて使い捨ての薄い皮(upperdir)に蓄積される。
  • 気に入らなければ、コンテナを破棄した瞬間に upperdir ごと差分を消し去れば、1秒で寸分違わぬ初期状態へと巻き戻る。

この不変の物理構造を手に入れたからこそ、私たちはインフラを「使い捨ての牛(Cattle)」として扱い、数千台のサーバーへ同一のイメージを瞬時にばら撒くことができるようになったのです。

視野を制限し(Namespaces)、胃袋を縛り(cgroups)、ディスクを不変のセル画にした(OverlayFS)。 次なる謎は、**「この完全に孤立した箱庭が、どうやって外部のネットワークや他のコンテナとパケットをやり取りしているのか?」**です。

次回第4話、仮想イーサネットケーブルを引く**「コンテナネットワークとvethペアの物理」**を暴きます。


📝 3行で頭に入る『試験・実務・投資の要点まとめ』

  1. 【OverlayFSの3層構造】 読み取り専用の lowerdir(イメージ)の上に、空の書き込み層 upperdir を重ね、合成された merged をコンテナのルートとして見せる。
  2. 【Copy-on-Writeと削除の物理】 変更時は下層から上層へファイル丸ごと複製(Copy-Up)され、削除時は特殊なキャラクタデバイス(Whiteout)を下層の上に被せて不可視化する。
  3. 【実務のI/O地獄対策】 CoWはファイル単位で発動するため、巨大DBファイルをコンテナレイヤーで変更すると激しいI/Oフリーズを起こす。大規模データは必ず「Volumeマウント」でOverlayFSをバイパスする。

確認クイズ

📝 確認クイズ(3問)