OverlayFS ストレージの使用

OverlayFS は最近の ユニオン・ファイルシステム であり、 AUFS に似ています。AUFS と OverlayFS を比較します。

  • よりシンプルな設計
  • Linux カーネルのバージョン 3.18 からメインラインに取り込まれている
  • より速い可能性

この結果、OverlayFS は Docker コミュニティで急に有名になり、多くの人から AUFS の後継者と自然に思われています。OverlayFS は有望ですが、比較的まだ若いストレージ・ドライバです。そのため、プロダクションの Docker 環境で利用する前に、十分に注意を払うべきでしょう。

Docker の overlay ストレージ・ドライバは、ディスク上でイメージとコンテナを構築・管理するため、複数の OverlayFS 機能を活用します。

注釈

カーネルのメインラインに取り込まれるにあたり、 OverlayFS カーネル・モジュール の名前が「overlayfs」から「overlay」に名称変更されました。そのため、同じドキュメントでも2つの用語が用いられるかもしれません。ですが、このドキュメントでは「OverlayFS」はファイルシステム全体を指すものとし、 overlay は Docker のストレージ・ドライバを指すために遣います。

OverlayFS でイメージのレイヤ化と共有

OverlayFS は1つの Linux ホスト上で2つのディレクトリを扱います。他のレイヤよりも一番上にあるレイヤにより、1つに統一して見えます。これらのディレクトリは レイヤ としてたびたび参照され、レイヤに対しては ユニオン・マウント(union mount) と呼ばれる技術が使われています。OverlayFS 技術では「下位ディレクトリ」が下の方のレイヤであり、「上位ディレクトリ」が上のレイヤになります。統一して表示される場所そのものを、「マージされた」(marged)ディレクトリと呼びます。

下図は Docker イメージと Docker コンテナがレイヤ化されたものです。イメージ・レイヤは「下位ディレクトリ」であり、コンテナ・レイヤは「上位ディレクトリ」です。統一した表示は「マージされた」ディレクトリであり、これがコンテナに対する事実上のマウント・ポイントです。下図では Docker の構造が OverlayFS 構造にどのように割り当てられているか分かります。

OverlayFS の構造

イメージ・レイヤとコンテナ・レイヤに、同じファイルを含められることに注意してください。この時に発生するのは、コンテナ・レイヤ(上位ディレクトリ)に含まれるファイルが優位になり、イメージ・レイヤ(下位ディレクトリ)にある同じファイルを存在しないものとみなします。コンテナ・マウント(マージ化)が、統一した表示をもたらします。

OverlayFS は2つのレイヤだけ扱います。つまり、複数にレイヤ化されたイメージは、複数の OverlayFS レイヤとしては使われません。そのかわり、各イメージ・レイヤは /var/lib/docker/overlay ディレクトリ以下で自身が使われます。下位のレイヤと共有するデータを効率的に参照する手法として、ハードリンクが使われます。Docker 1.10 からは、イメージ・レイヤ ID は /var/lib/docker/ 内のディレクトリ名と一致しなくなりました。

コンテナを作成したら、 overlay ドライバはコンテナのために新しいディレクトリをイメージの最上位レイヤの上に追加し、これらを組みあわせてディレクトリを表示します。イメージの最上位レイヤは、overlay では「下位ディレクトリ」であり、読み込み専用です。コンテナ用の新しいディレクトリは「上位ディレクトリ」であり、書き込み可能です。

例:イメージとコンテナのディスク上の構造

以下の docker images -a コマンドは、Docker ホスト上の1つのイメージを表示しています。表示されているように、イメージは4つのレイヤで構成されています。

以下の docker pull コマンドが表しているのは、4つのレイヤに圧縮された Docker イメージを Docker ホスト上にダウンロードしています。

$ sudo docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
8387d9ff0016: Pull complete
3b52deaaf0ed: Pull complete
4bd501fad6de: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:457b05828bdb5dcc044d93d042863fba3f2158ae249a6db5ae3934307c757c54
Status: Downloaded newer image for ubuntu:latest

/var/lib/docker/overlay 以下のディレクトリに、各イメージ・レイヤを置くディレクトリがあります。ここが、各イメージ・レイヤの内容を保管する場所です。

以下のコマンドの出力は、取得した各イメージ・レイヤの内容が保管されている4つのディレクトリを表しています。しかしながら、これまで見てきたように、イメージ・レイヤ ID は /var/lib/docker/overlay にあるディレクトリ名と一致しません。これは Docker 1.10 以降の通常の挙動です。

$ ls -l /var/lib/docker/overlay/
total 24
drwx------ 3 root root 4096 Oct 28 11:02 1d073211c498fd5022699b46a936b4e4bdacb04f637ad64d3475f558783f5c3e
drwx------ 3 root root 4096 Oct 28 11:02 5a4526e952f0aa24f3fcc1b6971f7744eb5465d572a48d47c492cb6bbf9cbcda
drwx------ 5 root root 4096 Oct 28 11:06 99fcaefe76ef1aa4077b90a413af57fd17d19dce4e50d7964a273aae67055235
drwx------ 3 root root 4096 Oct 28 11:01 c63fb41c2213f511f12f294dd729b9903a64d88f098c20d2350905ac1fdbcbba

イメージ・レイヤのディレクトリに含まれるファイルはレイヤに対してユニークなものです。つまり、下層レイヤと共有するデータのハード・リンクと同等です。これにより、ディスク容量を効率的に使えます。

また、コンテナは Docker ホストのファイルシステム上の /var/lib/docker/overlay/ 以下に存在します。実行中のコンテナに関するディレクトリを直接 ls -l コマンドで調べたら、次のようなファイルとディレクトリが見えるでしょう。

$ ls -l /var/lib/docker/overlay/<実行中コンテナのディレクトリ>
total 16
-rw-r--r-- 1 root root   64 Oct 28 11:06 lower-id
drwxr-xr-x 1 root root 4096 Oct 28 11:06 merged
drwxr-xr-x 4 root root 4096 Oct 28 11:06 upper
drwx------ 3 root root 4096 Oct 28 11:06 work

これら4つのファイルシステム・オブジェクトは全て OverlayFS が作ったものです。「lower-id」ファイルに含まれるのは、コンテナが元にしたイメージが持つ最上位レイヤの ID です。これは OverlayFS で「lowerdir」(仮想ディレクトリ)として使われます。

$ cat /var/lib/docker/overlay/73de7176c223a6c82fd46c48c5f152f2c8a7e49ecb795a7197c3bb795c4d879e/lower-id
1d073211c498fd5022699b46a936b4e4bdacb04f637ad64d3475f558783f5c3e

「upper」(上位)ディレクトリは、コンテナの読み書き可能なレイヤです。コンテナに対するあらゆる変更は、このディレクトリに対して書き込まれます。

「merged」(統合)ディレクトリは効率的なコンテナのマウント・ポイントです。これは、イメージ(「lowerdir」)とコンテナ(「upperdir」)を統合して表示する場所です。あらゆるコンテナに対する書き込みは、直ちにこのディレクトリに反映されます。

「work」(作業)ディレクトリは OverlayFS が機能するために必要です。 コピーアップ(copy_up) 処理などで使われます。

これら全ての構造を確認するには、 mount コマンドの出力結果から確認できます(以下の出力は読みやすくするため、省略と改行を施しています)。

$ mount | grep overlay
overlay on /var/lib/docker/overlay/73de7176c223.../merged
type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay/1d073211c498.../root,
upperdir=/var/lib/docker/overlay/73de7176c223.../upper,
workdir=/var/lib/docker/overlay/73de7176c223.../work)

出力結果から、overlay は読み書き可能(「rw」)としてマウントされているのが分かります。

overlay でコンテナの読み書き

コンテナのファイルを overlay 経由で読み込む、3つのシナリオを考えます。

  • ファイルがコンテナ・レイヤに存在しない場合 。コンテナがファイルを読み込むためにアクセスする時、ファイルがコンテナ(「upperdir」)に存在しなければ、ファイルをイメージ(「lowerdir」)から読み込みます。これにより、非常に小さな性能のオーバヘッドを生じるかもしれません。
  • ファイルがコンテナ・レイヤのみに存在する場合 。コンテナがファイルを読み込むためにアクセスする時、ファイルがコンテナ(「upperdir」)に存在してイメージ(「lowerdir」)に存在しなければ、コンテナから直接読み込みます。
  • ファイルがコンテナ・レイヤとイメージ・レイヤに存在する場合 。コンテナがファイルを読み込むためにアクセスする時、イメージ・レイヤにもコンテナ・レイヤにもファイルが存在する場合は、コンテナ・レイヤにある方のファイルが読み込まれます。これはコンテナ・レイヤ(「upperdir」)のファイルがイメージ・レイヤ(「lowerdir」)にある同名のファイルを隠蔽するからです。

同様に、コンテナに対するファイルを編集するシナリオを考えましょう。

  • ファイルに対して初めて書き込む場合 。コンテナ上に存在するファイルに初めて書き込む時は、ファイルがコンテナ(「upperdir」)に存在しません。 overlay ドライバはコピーアップ処理を行い、イメージ(「lowerdir」)にあるファイルをコンテナ(「upperdir」)にコピーします。コンテナは、以降の書き込みに対する変更は、コンテナ・レイヤ上に新しくコピーしたファイルに対して行います。

しかしながら、OverlayFS はファイル・レベルでの処理であり、ブロック・レベルではありません。つまり、全ての OverlayFS のコピーアップ処理はファイル全体をコピーします。これは、非常に大きなファイルのごく一部分だけを編集する場合でも、全体をコピーします。そのため、コンテナの書き込み性能に対して大きな注意を払う必要があります。

ですが、次の2つの場合は心配不要です。
  • コピーアップ処理が発生するのは、書き込もうとするファイルを初めて処理する時のみです。以降の書き込み処理は、既にコンテナ上にコピー済みのファイルに対して行われます。
  • OverlayFS が動作するのは2つのレイヤのみです。つまり、性能は AUFS より良くなります。AUFS では、多くのイメージ・レイヤがある場合、そこからファイルを探すのに待ち時間が発生の考慮が必要だからです。
  • ファイルとディレクトリを削除する場合 。コンテナ内のファイル削除では、 ホワイトアウト・ファイル(whiteout file) がコンテナ内のディレクトリ(「upperdir」)に作成されます。イメージ・レイヤ(「lowerdir」)にあるバージョンのファイルは削除されません。しかし、コンテナ内のホワイトアウト・ファイルが見えなくします。

コンテナ内のディレクトリを削除したら、「upperdir」で作成されたディレクトリを隠蔽します。これはホワイトアウト・ファイルと同様の効果であり、「lowerdir」イメージのディレクトリを効率的にマスクするものです。

Docker で overlay ストレージ・ドライバを使う設定

Docker が overlay ストレージ・ドライバを使うには、Docker ホスト上の Linux カーネルのバージョンが 3.18 (より新しいバージョンが望ましい)であり、overlay カーネル・モジュールを読み込み実行する必要があります。OverlayFS は大部分の Linux ファイルシステムで処理できます。しかし、プロダクション環境での利用にあたっては、現時点では ext4 のみが推奨されています。

以下の手順では Docker ホスト上で OverlayFS を使うための設定方法を紹介します。手順では、Docker デーモンが停止している状態を想定しています。

ご用心

既に Docker ホスト上で Docker デーモンを使っている場合は、イメージを維持する必要がありますので、処理を進める前に、それらのイメージを Docker Hub やプライベート Docker Trusted Registry に送信しておきます。

  1. Docker デーモンが実行中であれば、停止します。
  1. カーネルのバージョンと overlay カーネル・モジュールが読み込まれているかを確認します。
$ uname -r
3.19.0-21-generic


$ lsmod | grep overlay
overlay
  1. Docker デーモンを overlay ストレージ・ドライバを使って起動します。
$ dockerd --storage-driver=overlay &
[1] 29403
root@ip-10-0-0-174:/home/ubuntu# INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
INFO[0000] Option DefaultDriver: bridge
INFO[0000] Option DefaultNetwork: bridge
<出力を省略>

あるいは、Docker デーモンが自動起動時に必ず overlay ドライバを使うようにします。Docker の設定ファイルを開き、 DOCKER_OPTS 行に --storage-driver=overlay フラグを追加します。このオプションを設定しておけば、Docker デーモンを津風に起動するだけで自動的に適用されます。手動で --storage-driver フラグを指定する必要がありません。

  1. デーモンが overlay ストレージ・ドライバを使用するのを確認します。
$ docker info
Containers: 0
Images: 0
Storage Driver: overlay
 Backing Filesystem: extfs
<出力を省略>

この出力では、背後のファイルシステムが extfs なのに注意してください。複数のファイルシステムをサポートしていますが、プロダクションでの使用が推奨されているのは extfs (ext4) のみです。

これで Docker ホストは overlay ストレージ・ドライバを使えるようになりました。mount コマンドを実行したら、Docker が自動的に overlay マウントを作成し、そこに必要となる構成物「lowerdir」「upperdir」「merged」「workdir」も作っています。

OverlayFS と Docker の性能

一般的に overlay ドライバは速いでしょう。 aufsdevicemapper と比べれば、ほとんどの場合に速いはずです。特定の環境においては btrfs より速いかもしれません。ここでは、Docker が overlay ストレージ・ドライバを使う時、性能に関して注意すべきことを言及します。

  • ページ・キャッシュ 。OverlayFS はページキャッシュ共有をサポートします。つまり、複数のコンテナが同じファイルにアクセスする時、1つのページキャッシュ・エントリ(あるいはエントリ群)を共有します。これにより、 overlay ドライバはメモリを効率的に使うことができ、PaaS や高密度の使い方に適すでしょう。
  • コピーアップ 。AUFS と同様に、OverlayFS ではコンテナ上のファイルに書き込みするとき、初めての場合はコピーアップ処理をします。これは書き込み処理に対して待ち時間を発生させます。特に大きなファイルをコピーアップする場合です。しかし、コピーアップが処理されるのは一度だけであり、以降のファイルに対する書き込みの全てにおいて更なるコピーアップ処理は発生しません。

OverlayFS のコピーアップ処理は AUFS の同じ処理よりも高速でしょう。これは AUFS が OverlayFS より多くのレイヤをサポートしているためであり、多くの AUFS レイヤからファイルを探すのには、時間を必要とする場合があるためです。

  • RPM と Yum 。OverlayFS は POSIX 標準のサブセットのみ実装しています。そのため、いくつかの OverlayFS 処理は POSIX 標準を使っていません。そのような処理の1つがコピーアップ処理です。そのため、 Docker ホストが overlay ストレージ・ドライバを使っている場合、コンテナの中で yum を使っても動作せず、回避策もありません。
  • iノード消費overlay ストレージ・ドライバの使用は、過度の i ノード消費を引き起こします。これは特に Docker ホストが成長し、多くのイメージとコンテナを持つ場合に起こるでしょう。Docker ホストが多くの inode を持っていても、コンテナの開始と停止を多く行えば、すぐに i ノードを使い尽くします。

残念ながら、i ノード数を指定できるのはファイルシステムの作成時のみです。そのため、 /var/lib/docker を異なったデバイスにすることを検討した方が良いかもしれません。そのデバイスが自身でファイルシステムを持っており、ファイルシステム作成時に手動で i ノード数を指定する方法があります。

一般的な性能に関するベスト・プラクティスは、OverlayFS にも適用できます。

  • SSD 。ベストな性能のために、SSD(ソリッド・ステート・デバイス)のような高速なストレージ・メディアを使うのは常に良い考えです。
  • データ・ボリュームの使用 。データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを行う場合に使うべきでしょう。