OverlayFS ストレージを使う

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

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

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

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

注釈

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

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

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

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

OverlayFS の構造

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

OverlayFS は2つのレイヤだけ扱います。つまり、複数にレイヤ化されたイメージは、複数の OverlayFS レイヤとしては使われません。その代わり、各イメージ・レイヤは /var/lib/docker/overlay ディレクトリ以下で自身が使われます。下位のレイヤと共有するデータを効率的に参照する手法として、ハードリンクが使われます。下図は4つのレイヤを持つイメージが、Docker ホストのファイルシステム上でどのように見えるかを説明しています。

OverlayFS の構造

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

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

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

$ docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              1d073211c498        7 days ago          187.9 MB
<none>              <none>              5a4526e952f0        7 days ago          187.9 MB
<none>              <none>              99fcaefe76ef        7 days ago          187.9 MB
<none>              <none>              c63fb41c2213        7 days ago          187.7 MB

更に次のコマンド出力は、 /var/lib/docker/overlay/ の下に、4つのイメージ・レイヤが自身のディレクトリを持っているのが分かります。

$ 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 images -a コマンドで見えたイメージ・レイヤの ID で、各ディレクトリに名前が付けられます。イメージ・レイヤのディレクトリには、ユニーク(一意)なファイルが含まれます。レイヤと同様に、データに対するハードリンクで下層レイヤを共有します。これによって、ディスク容量を効率的に使えます。

以下の docker ps コマンドから、同じ Docker ホスト上でコンテナが1つ動いていることがわかります。このコンテナ ID は「73de7176c223」です。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
73de7176c223        ubuntu              "bash"              2 days ago          Up 2 days                               stupefied_nobel

..This container exists on-disk in the Docker host’s filesystem under /var/lib/docker/overlay/73de7176c223.... If you inspect this directory using the ls -l command you find the following file and directories.

このコンテナは、Docker ホストのファイルシステムにある /var/lib/docker/overlay/73de7176c223... 以下のディスク上に存在しています。もしディレクトリに対して ls -l コマンドで調べてみると、次のようにファイルとディレクトリが見えるでしょう。

$ ls -l /var/lib/docker/overlay/73de7176c223a6c82fd46c48c5f152f2c8a7e49ecb795a7197c3bb795c4d879e
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」(上位)ディレクトリは、コンテナの読み書き可能なレイヤです。コンテナに対するあらゆる変更は、このディレクトリに対して書き込まれます。

「marged」(統合)ディレクトリは効率的なコンテナのマウント・ポイントです。これは、イメージ(「lowerdier」)とコンテナ(「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)

..The output reflects the overlay is mounted as read-write (“rw”).

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

overlay でコンテナの読み書き

コンテナのファイルを読み込むために overlay でアクセスする、3つのシナリオを考えます。

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

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

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

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

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

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

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

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

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

ご用心

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

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


$ lsmod | grep overlay
overlay
  1. Docker デーモンを overlay ストレージ・ドライバを使って起動します。
$ docker daemon --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 を使っても動作せず、回避策もありません。
  • inode limitsoverlay ストレージ・ドライバの使用によって、過度の inode 消費を引き起こします。これは特に Docker ホストが成長し、多くのイメージとコンテナを持つ場合に起こるでしょう。Docker ホストは多くの inode を持ち、コンテナの開始と停止を多く行うと、すぐに inode を使い尽くします。

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

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

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