DockerのLayerFSとキャッシュの仕組みを理解する
はじめに
Dockerは、効率的な開発環境を提供するために、レイヤー化されたファイルシステム(LayerFS)とキャッシュ機能を活用しています。今回、Dockerイメージの最適化を考える中で、LayerFSについて調査する機会がありました。そのため、ここではLayerFSやキャッシュの仕組み、ファイル操作がディレクトリの状態にどのような影響を及ぼすか、パフォーマンス向上のためのベストプラクティス、LayerFSのデバッグ方法についてまとめています。
対象読者
- Dockerの基礎を理解しており、LayerFSの詳細に興味があるエンジニア
- Dockerイメージのビルド効率や、レイヤー構造の最適化を目指している方
1. DockerのLayerFSとは?
LayerFS(Layered File System)は、Dockerがイメージをレイヤー単位で管理するファイルシステムとして使用されています。Dockerはoverlay2
ドライバを活用し、各レイヤーに変更があった場合のみ新しいレイヤーを追加する仕組みです。このアプローチにより、ビルド効率が向上し、ディスクスペースも節約されるというメリットがあります。
LayerFSの構造には以下のディレクトリが含まれます。
- LowerDir: 読み取り専用のイメージレイヤーで、イメージの各ステップがLowerDirとして積み上げられ、基本的なファイルや基盤構造が含まれるディレクトリです。
- UpperDir: 書き込み可能なコンテナレイヤーで、コンテナ内で行われた変更がここに記録されます。
- MergedDir: LowerDirとUpperDirを統合した結果を表示する仮想的なファイルシステムで、コンテナ内からはこのディレクトリを介してファイルシステム全体を確認することができます。
- WorkDir: MergedDirの構築に必要な一時ファイルが保存される作業ディレクトリです。
以下の図は、LayerFSのディレクトリ構造と各レイヤーの関係を示しています。
├── LowerDir
│ ├── 基盤ファイル
├── UpperDir
│ ├── 変更されたファイル
├── MergedDir
│ ├── LowerDirとUpperDirの統合結果
└── WorkDir
└── 作業ディレクトリ
図: LayerFS構造図(出典:Docker公式ドキュメント)
2. 環境別のLayerFSの確認方法
DockerはLinux環境で最も効率的に動作しますが、WindowsやMacの場合は仮想化を通じてLinuxコンテナが稼働しています。そのため、overlay2
ドライバの詳細を確認する際には、Linux環境が適していると言えます。MacやWindowsの場合、以下の手順で仮想環境にアクセスし、overlay2
の構造を確認することが可能です。
-
MacやWindowsから仮想環境へのアクセス:
docker run --rm -it --privileged --pid=host justincormack/nsenter1
このコマンドを実行すると、仮想環境に入ってDockerのファイルシステムにアクセスできるようになります。
-
Linux環境でのドライバ確認:
Linux環境では、Dockerがoverlay2
ストレージドライバを使用しているかどうかを確認するため、次のコマンドを使用します。docker info | grep "Storage Driver"
出力例:
Storage Driver: overlay2
3. DockerのLayerFSにおけるファイルやディレクトリの変更
LayerFSではファイルやディレクトリに対する変更に関して具体的な動作パターンが存在し、LowerDir
やUpperDir
に基づいて動作が異なります。以下に、代表的なケースごとにLayerFSがどのように動作するかを整理しています。
1. ファイルの初回書き込み(copy-up)
ファイルが初めて変更される場合、そのファイルがLowerDirに存在していれば、UpperDirにそのファイルがコピーされ、そこに変更が加えられます。この「copy-up」操作は初回のみ発生し、それ以降はUpperDir内のファイルが参照されるようになります。
例: 設定ファイル/etc/config.yaml
を変更する場合の動作
LowerDir: /etc/config.yaml (元のファイル)
UpperDir: /etc/config.yaml (copy-upされたファイル)
-
変更前: LowerDir内の
config.yaml
が参照されています。 -
初回変更時:
config.yaml
がUpperDirにコピーされ、以降はUpperDirのファイルに変更が反映されます。
2. ファイルの削除
LowerDirにあるファイルを削除すると、直接削除されるのではなく、UpperDirに「ホワイトアウトファイル」と呼ばれる特別なファイルが作成されます。これにより、LowerDirのファイルは削除されたように扱われ、MergedDirからは見えなくなります。
例: /etc/config.yaml
を削除する場合
UpperDir: /etc/config.yaml(ホワイトアウトファイルとしてマーク)
-
削除前:
config.yaml
はLowerDirに存在し、MergedDirで参照可能。 -
削除後: UpperDirにホワイトアウトファイルが作成され、MergedDirには
config.yaml
が存在しないように見えます。
3. ディレクトリのリネーム(名前変更)
LayerFSではLowerDir内のディレクトリを直接リネームすることはできません。LowerDirが読み取り専用であるため、リネーム操作は以下のように処理されます。
-
ディレクトリのコピーと再配置
リネーム対象のディレクトリがLowerDirにある場合、UpperDirに新しい名前でコピーされます。これにより、UpperDir内でのみ「新しい名前」で参照可能となります。 -
元のディレクトリの隠蔽(ホワイトアウトファイル)
元のディレクトリを見えなくするために、UpperDirにホワイトアウトファイルが作成され、元のディレクトリが削除されたように見えます。
例: /var/logs
を/var/old_logs
にリネームする場合
-
リネーム前の状態
- LowerDirに
/var/logs
が存在。 - UpperDirにはまだ存在しません。
- LowerDirに
-
リネーム操作の実行
コンテナ内で以下のコマンドを実行します。mv /var/logs /var/old_logs
-
リネーム処理の内部動作
-
logs
ディレクトリがold_logs
としてUpperDirにコピー。 - UpperDirにホワイトアウトファイルが作成され、LowerDirの
logs
ディレクトリが削除されたように見えます。
-
-
リネーム後の状態
- コンテナ内からは
/var/old_logs
のみが表示され、/var/logs
は削除されたように見えます。 - 内部的にはUpperDirに新しいディレクトリとホワイトアウトファイルが作成され、リネーム後の状態を再現しています。
- コンテナ内からは
4. DockerのLayerFSにおけるファイル操作によるディレクトリ状態の変化
Dockerコンテナ内でファイル操作を行うと、UpperDir
やMergedDir
がどのように変化するのかについて、Upper Layerの作成および削除のタイミングを踏まえながら、以下のステップごとに確認しています。
1. コンテナの起動とLower Layerの確認
まず、コンテナを起動し、初期状態のディレクトリ構成を確認します。
docker run -it --name demo-container イメージ名 /bin/bash
この時点で、LowerDir
にはイメージレイヤーのファイルが含まれ、UpperDir
にはまだ何も変更がありません。
2. Upper Layerの作成確認とUpperDirパスの特定
次に、コンテナ内で新しいファイルを作成します。この操作により、Upper Layerに変更内容が記録されます。
touch /tmp/samplefile
別のターミナルでdocker diff
コマンドを使用して変更内容がUpperDirに保存されたことを確認します。
docker diff demo-container
出力例:
A /tmp/samplefile
この出力から、/tmp/samplefile
がUpper Layerに保存されたことが確認できます。次に、Upper Layerの保存場所も特定してみます。
-
docker inspect
コマンドでUpperDirのパスを取得します。docker inspect demo-container | grep -i "UpperDir"
出力例:
"UpperDir": "/var/lib/docker/overlay2/<layer-ID>/diff"
-
上記で取得したパスにアクセスし、
samplefile
がUpper Layer内に保存されていることを確認します。ls /var/lib/docker/overlay2/<layer-ID>/diff/tmp
出力例:
samplefile
3. Upper Layerの削除確認
コンテナを停止・削除すると、Upper Layerも削除され、UpperDir内のファイルはすべて消去されます。
docker stop demo-container
docker rm demo-container
この後、再度UpperDirのパスを確認すると、ファイルが削除されていることが確認できます。
5. Docker Build後のLower Layer内容確認
Dockerイメージを構築後、Lower Layerに保存される内容を確認する方法を紹介します。
-
Dockerイメージの構築
新しいイメージを作成し、不要なレイヤーがある場合はキャッシュを無効化します。docker build --no-cache -t layerfs-demo .
-
Dockerイメージ内のLower Layer確認
ビルド後にdocker history
コマンドを使用し、各レイヤーの状態を確認します。docker history --no-trunc layerfs-demo
-
Lower Layer内のファイル詳細
Lower Layerのファイル内容を確認するには、次のコマンドで詳細を確認します。docker inspect <layer-ID> | grep -i "LowerDir"
6. Dockerイメージ間のレイヤー共有確認
Dockerでは、同じベースイメージを使用するイメージ間でレイヤーを共有することで、ディスクスペースを効率的に使用しています。これを確認するための手順は以下の通りです。
-
同じベースイメージを使用した複数のDockerイメージの作成
Dockerfile-A
とDockerfile-B
を使用して、それぞれ異なるイメージを作成しますが、共通のベースイメージとしてubuntu:latest
を使います。Dockerfile-AFROM ubuntu:latest RUN apt-get update && apt-get install -y curl
Dockerfile-BFROM ubuntu:latest RUN apt-get update && apt-get install -y git
-
ベースレイヤー共有の確認
同じベースイメージを共有しているかを確認するには、以下の手順で最下層レイヤーのIDを確認します。docker inspect --format='{{.RootFS.Layers}}' image-A docker inspect --format='{{.RootFS.Layers}}' image-B
これにより、各イメージのレイヤーIDのリストが表示されます。最下層のレイヤーIDが一致していれば、同じベースイメージを共有していることが確認できます。
7. パフォーマンス向上のためのベストプラクティス
LayerFSのパフォーマンスを最大限に引き出すため、以下の具体例に基づいた方法があります。
-
ページキャッシュの共有
大きなファイルをLower Layerに含めると、複数のコンテナでページキャッシュが共有され、メモリ使用量が抑えられます。これにより、ファイルの読み込み速度も向上します。Dockerはキャッシュ共有を自動で行うため、特別な設定は不要です。 -
書き込みワークロードの最適化
頻繁に書き込みが発生する場面では、ボリュームを活用してレイヤーのコピーアップを回避することで、効率的なデータ保存が可能です。- 例: ログファイルやデータベースキャッシュの書き込みにボリュームを使用することで、パフォーマンスが向上します。
-
高速なストレージの利用
SSDなどの高速ストレージを使用すると、レイヤーのコピーアップが高速化され、特に初回の書き込み操作が多い場面で有効です。
8. レイヤーの削除とディスクスペースの管理
Dockerではキャッシュやレイヤーが蓄積し、ディスクスペースが増加します。不要なリソースは以下のコマンドで削除できます。
-
不要なイメージレイヤーの削除:
docker image prune
このコマンドにより、タグが付いていない「dangling」イメージが削除され、ディスクスペースが確保されます。
-
すべての不要リソースの一括削除:
docker system prune
このコマンドにより、以下のリソースが削除されます。
- 停止中のコンテナ
- 未使用のネットワーク
- ダングリングイメージ
- ビルドキャッシュ
9. OverlayFSの互換性と制約
OverlayFSは便利なファイルシステムですが、いくつかの制約が存在します。
-
サポートされるファイルシステムの種類:
ext4
やxfs
のみが対応しています。 - NFSとの互換性: NFS上ではOverlayFSは動作しません。
- レイヤー数の制限: 最大で128レイヤーまでしかサポートされていません。
これらの制約は、たとえばCI/CD環境で大量のレイヤーを持つビルドが発生する場合に問題になる可能性があります。LayerFSの構成が制限に達するとビルドが失敗する場合もあるため、ビルドパイプライン内で定期的にキャッシュをクリーンアップすることが推奨されます。
10. OverlayFSのデータ保存先の変更手順
DockerのLayerFSで使用されるoverlay2
ディレクトリは、デフォルトで/var/lib/docker
以下に保存されています。このデータ保存先を別の場所に変更する手順を以下に示します。
1. Dockerのサービスを停止する
まず、データの移行を行うためにDockerサービスを停止します。
sudo systemctl stop docker
2. 現在のデータのバックアップ
現在のDockerデータディレクトリ(通常は/var/lib/docker
)を新しい保存先にコピーします。ここでは、追加購入したSSDを/mnt/docker-data
にマウントし、保存先として指定する例を示しています。
sudo rsync -aP /var/lib/docker /mnt/docker-data
3. Dockerの設定ファイルを更新する
Dockerがデータ保存先として新しいディレクトリを参照するように、設定ファイルを変更します。
-
/etc/docker/daemon.json
ファイルを開きます(ファイルがない場合は新規作成します)。sudo vim /etc/docker/daemon.json
-
次の内容を追加して、データディレクトリを
/mnt/docker-data/docker
に変更します。{ "data-root": "/mnt/docker-data/docker" }
4. Dockerサービスの再起動
設定が完了したら、Dockerサービスを再起動します。
sudo systemctl start docker
11. LayerFSのデバッグ方法
LayerFSのデバッグには、docker diff
やdocker inspect
といったコマンドが有効です。以下の手順で各レイヤーの状態を確認し、レイヤー内のファイル追加、変更、削除の詳細を把握することができます。
docker diff <コンテナ名またはID>
docker inspect <コンテナ名またはID> | grep -i "UpperDir\|LowerDir\|MergedDir"
docker diff
コマンドの詳細な見方
docker diff
コマンドは、指定したコンテナ内で行われたファイルやディレクトリの変更をリストします。出力には、変更内容を示すプレフィックス(A、C、D)が付加されており、それぞれ以下の意味を持ちます。
- A (Added): コンテナ内に新しく追加されたファイルやディレクトリ。
- C (Changed): コンテナ内で内容が変更されたファイルやディレクトリ。
- D (Deleted): コンテナ内で削除されたファイルやディレクトリ。
これらのプレフィックスは、どのような種類の変更が発生したかを簡潔に示しており、デバッグの際に非常に役立ちます。
docker diff
出力例
例えば、以下のような出力が得られるとします。
C /tmp
A /tmp/samplefile
D /var/log/example.log
この出力は以下の内容を表しています。
-
C /tmp
:/tmp
ディレクトリに変更が加えられたことを示します。これは、/tmp
ディレクトリにファイルが追加されたり、既存のファイルが変更された場合に表示されます。 -
A /tmp/samplefile
:/tmp
ディレクトリ内にsamplefile
というファイルが新たに追加されたことを意味します。 -
D /var/log/example.log
:/var/log
ディレクトリ内にあったexample.log
というファイルが削除されたことを示しています。
docker diff
とLayerFSの関係
docker diff
で検出される変更内容は、LayerFSのUpperDir
に記録されます。コンテナ内で行われたファイルの追加、変更、削除といった操作はすべてUpperDir
に反映されるため、デバッグ時にはdocker diff
の出力を参考にしつつ、docker inspect
コマンドでUpperDir
のパスを確認し、変更内容を追跡することが可能です。
LayerFSの構造確認手順
docker inspect
コマンドでUpperDir
やLowerDir
のパスを確認することで、コンテナのファイルシステム状態や、どのレイヤーで変更が行われたかをさらに詳しく調べられます。
docker inspect <コンテナ名またはID> | grep -i "UpperDir\|LowerDir\|MergedDir"
おわりに
DockerのLayerFSに関する構造やパフォーマンス向上の方法について、自分が調査した内容をまとめてみました。この結果が少しでも役に立っていれば幸いです。
Discussion