コンテナのオーバーレイファイルシステム(OverlayFS)
数百MBのイメージから数十個のコンテナを瞬時に起動できるのは、ディスクを複製していないからです。OverlayFSがレイヤ共有とコピーアップをどう実現するか、内部構造から解き明かします。
- 1.OverlayFSは読み取り専用のlower群と書き込み可能なupperを重ね、上から透けて見えるユニオンビューを1つのマウントとして提供する。
- 2.lower上のファイルを変更すると初回アクセス時にupperへ丸ごと複製(copy-up)し、削除はwhiteoutという特殊エントリで「無い」ことを表現する。
- 3.全コンテナがlowerレイヤを物理的に共有し、各コンテナはupperディレクトリだけを持つため、起動が速く容量も小さい。
OverlayFSが解く問題
コンテナイメージは「ベースOS+パッケージ追加+アプリ配置」のように、変更を積み重ねたレイヤの集合です。同じベースイメージから100個のコンテナを起動するとき、各コンテナにイメージ全体を物理コピーしていたら、ディスクも起動時間も100倍かかります。
OverlayFS はこれを ユニオンマウント で解く Linux カーネルのファイルシステムです。複数のディレクトリを縦に重ね、上のレイヤが下を隠しながら透ける1枚のビューを作ります。読み取り専用の共通部分は全コンテナで物理的に1つだけ持ち、書き込みはコンテナ固有の薄い層に閉じ込めます。仮想ファイルシステムの上に被さる擬似ファイルシステムという点で、VFS抽象化レイヤ の上に乗る一種のスタッカブルFSです。
複数のディレクトリツリーを1つのマウントポイントに「合成」して見せる仕組みです。同じパスが複数レイヤに存在する場合は上位レイヤが勝ち、片方にしかなければそれが見えます。OverlayFSはこれを2層(lower/upper)のシンプルなモデルに整理し、カーネル本体に取り込みました。
4つのディレクトリ:lower / upper / work / merged
OverlayFS のマウントは4種類のディレクトリで構成されます。それぞれ役割が厳密に分かれています。
| ディレクトリ | アクセス | 役割 |
|---|---|---|
| lowerdir | 読み取り専用 | 下層。複数を `:` 区切りで重ねられる(左が上位)。コンテナイメージの各レイヤ |
| upperdir | 読み書き | 最上層。新規作成・変更・削除の結果がすべてここに溜まる。コンテナの書き込み層 |
| workdir | 内部用 | copy-upやrename中の中間状態を置く作業領域。upperと同一FS上に必須 |
| merged | 読み書き(合成ビュー) | マウントポイント。プロセスが実際に見る合成済みツリー |
マウントは次のように行います。
mount -t overlay overlay \
-o lowerdir=/img/base:/img/pkg,upperdir=/c1/upper,workdir=/c1/work \
/c1/merged
lowerdir は左ほど上位です。上の例では同名ファイルがあれば pkg より base が、それより upper が優先されます。読み取りは merged を上から下へ探索し、最初に見つかった実体を返します。書き込みはすべて upper に向かいます。
copy-up や rename は「中途半端に見えてはいけない」原子的な操作です。OverlayFSはまずworkdir内で実体を組み立て、完成してからupperへrename(2)で原子的に移します。このためworkdirはupperと同じファイルシステム上になければならず(rename原子性の前提)、ユーザーが直接触ってはいけません。
copy-up:lowerのファイルを変更する瞬間に複製する
lower は読み取り専用です。では merged 上で lower 由来のファイルを書き換えるとどうなるか。OverlayFS は 書き込みの直前に、そのファイルを lower から upper へ丸ごと複製 します。これが copy-up です。これは コピーオンライト の発想をファイル単位で適用したものです。
初期状態: 書き込み後:
upper: (空) upper: /app/config.yml ← ここに複製され編集される
lower: /app/config.yml lower: /app/config.yml (元のまま、隠れる)
merged: config.yml merged: config.yml = upper側を見る
copy-up の手順を擬似コードで示します。
copy_up(path):
if path が既に upper にある: return # 二度目以降は不要
workdir に一時ファイルを作成
lower の中身・所有者・モード・xattr を一時ファイルへ複製
rename(workdir/tmp, upper/path) # 原子的に upper へ昇格
以降この path への I/O は upper を直接対象にする
重要なのは 粒度がファイル全体 である点です。1GBのファイルの1バイトを変えるだけでも1GB全体がコピーされます。書き込み主体のワークロードではこのコストが無視できないため、データベースの実体ファイルなどはボリュームマウントで OverlayFS を迂回するのが定石です。
copy-upは「lowerにしか無いファイルへの最初の書き込み」でだけ発生します。一度upperへ昇格したファイルは以後upperを直接読み書きするので、追加コストはありません。読み取りだけのファイルは永遠にlowerのまま共有され、複製されません。
whiteout:削除を「特殊エントリ」で表現する
lower のファイルを merged 上で削除する場合も、lower は変更できません。実体は lower に残ったままなので、「上から見たら無い」状態を作る必要があります。OverlayFS は upper に whiteout という目印を置いて、下層の同名エントリを隠します。
whiteout はキャラクタデバイスファイルで、デバイス番号 0/0 を持つものとして表現されます。merged 探索中にこのエントリに当たると、OverlayFS はそこで打ち切り、下層を見に行きません。結果として「削除されたように見える」わけです。
rm merged/app/old.log を実行すると:
upper/app/old.log → デバイス番号 0/0 の whiteout を作成
lower/app/old.log → そのまま残存(が、隠される)
merged から old.log は消えて見える
ディレクトリ全体を削除して同名で作り直す場合は、個々のwhiteoutでは表現しきれません。このとき upper 側のディレクトリに trusted.overlay.opaque="y" という拡張属性(xattr)を付け、そのディレクトリは下層を一切透かさない(opaque) と宣言します。opaque ディレクトリでは lower の同名ディレクトリ配下が完全に無視されます。
| 操作 | upper側の表現 | lower側 |
|---|---|---|
| lowerのファイルを編集 | copy-upで複製し編集 | 元のまま、隠れる |
| lowerのファイルを削除 | whiteout(0/0 のキャラクタデバイス) | 元のまま、隠れる |
| lowerのディレクトリを作り直す | opaque xattr 付きディレクトリ | 配下を全無視 |
| 新規ファイル作成 | upperに普通に作成 | (存在しない) |
レイヤ共有:イメージ1つでコンテナ多数
ここまでの仕組みを組み合わせると、コンテナイメージの共有が自然に実現します。イメージの各レイヤは読み取り専用ディレクトリとして展開され、複数コンテナの lowerdir として 物理的に同じ実体を共有 します。コンテナごとに違うのは upper と work だけです。
共有される lower(イメージレイヤ、読み取り専用・1セットのみ):
/img/L1 (base OS) ── /img/L2 (runtime) ── /img/L3 (app)
▲ ▲ ▲
└──────── 全コンテナが同じ実体を参照 ────┘
コンテナ固有(書き込み可能・コンテナごとに1つ):
コンテナA: upperdir=/A/upper workdir=/A/work → /A/merged
コンテナB: upperdir=/B/upper workdir=/B/work → /B/merged
コンテナC: upperdir=/C/upper workdir=/C/work → /C/merged
この構造から効果が直接導けます。
- 起動が速い: コンテナ起動時に作るのは空の upper/work と merged のマウントだけ。イメージ本体のコピーは発生しないので、サイズに関係なく一瞬で起動する。
- 容量が小さい: 共有 lower はディスク上に1セットだけ。コンテナが消費するのは copy-up された差分と新規ファイル(=upper の中身)だけ。
- 破棄が安全: コンテナを捨てるときは upper/work を消すだけ。共有 lower は無傷なので、他のコンテナや次回起動に一切影響しない。
Docker や containerd の overlay2 ストレージドライバはまさにこの構成で、各イメージレイヤを内容ハッシュで識別される読み取り専用ディレクトリとして保存し、複数イメージ間でも共通レイヤを使い回します。名前空間でファイルシステムビューを隔離する namespaces と cgroups と組み合わさって、軽量なコンテナが成立します。
「なぜコンテナは軽いのか」への核心的な答えは、(1) lowerレイヤをイメージ間・コンテナ間で物理共有し、(2) 書き込みはcopy-upで差分だけをupperに持つ、の2点です。VMのようにOS全体を複製しないことと混同しないよう、ファイルシステム層の共有メカニズムとして説明できると強いです。
制約と注意点
OverlayFS は便利ですが、レイヤを重ねる代償もあります。
- copy-up のコスト: 前述の通りファイル単位で複製するため、大きなファイルへの書き込みは重い。書き込み集約的なデータは VFS 経由でボリューム(bind mount や named volume)に逃がす。
- inode と hardlink: copy-up すると upper 側は lower とは別 inode になる。lower 内でハードリンクされていたファイル群は、copy-up 後にリンク関係が壊れる場合がある(
index=onである程度緩和)。 - rename の制約: ディレクトリ rename はレイヤをまたぐと単純な rename(2) では済まず、
redirect_dir機能の有無で挙動が変わる。 - lower の不変性: lower を直接書き換えるとオーバーレイの整合が崩れる。lower は read-only として扱うのが大前提。
これらは「読み取り共有・書き込み分離」という設計の裏返しであり、制約を理解した上でレイヤ設計(変更頻度の高いものを上位レイヤへ)を行うことが、イメージの効率化につながります。
まとめ
- OverlayFS は 読み取り専用の lower 群と書き込み可能な upper を重ね、上から透けて見える1枚の merged ビューをユニオンマウントとして提供する。
- lower のファイルを変更すると初回だけ copy-up で upper へ丸ごと複製し、削除は whiteout(0/0 のキャラクタデバイス)、ディレクトリの隠蔽は opaque xattr で表現する。work ディレクトリは原子的な昇格のための作業領域。
- 全コンテナが lower を 物理共有 し、固有の upper/work だけを持つため、コンテナは起動が速く・容量が小さく・破棄が安全になる。これがコンテナ軽量性の正体。
- 仕組みの根は コピーオンライト と VFS抽象化レイヤ、隔離の文脈は namespaces と cgroups も参照。
OS Article
コンテナのオーバーレイファイルシステム(OverlayFS)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
OverlayFS
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
lower上のファイルを変更すると初回アクセス時にupperへ丸ごと複製(copy-up)し、削除はwhiteoutという特殊エントリで「無い」ことを表現する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「OverlayFS / コンテナ」に近いか確認する。
- 強みである「OverlayFSは読み取り専用のlower群と書き込み可能なupperを重ね、上から透けて見えるユニオンビューを1つのマウントとして提供する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。