AUFS ストレージ・ドライバを使う

AUFS は Docker に使った初めてのストレージ・ドライバです。そのため、Docker の歴史で長く使われており、非常に安定し、多くの実際の開発に使われ、強力なコミュニティのサポートがあります。AUFS には複数の機能があります。これらは Docker の良い選択肢となるでしょう。次の機能を有効にします。

  • コンテナ起動時間の高速化
  • ストレージの効率的な利用
  • メモリの効率的な利用

性能に拘わらず Docker で長い間使われてきていますが、いくつかのディストリビューションは AUFS をサポートしていません。たいていの場合、AUFS は Linux カーネルのメインライン(upstream)ではないためです。

以下のセクションでは、AUFS 機能と Docker とどのように連携するか紹介します。

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

AUFS とは 結合したファイルシステム(unification filesystem) です。つまり、1つの Linux ホスト上に複数のディレクトリが存在し、それぞれが互いに積み重なり、1つに結合された状態に見えます。これらを実現するため、 AUFS は ユニオン・マウント(union mount) を使います。

AUFS は複数のディレクトリの積み重ねであり、1つのマウントポイントを通して、それらが統合されて見えます。全てのディレクトリが層(スタック)と結合したマウントポイントを形成するので、全てが同一の Linux ホスト上に存在する必要があります。AUFS は各ディレクトリを、 ブランチ(branch) という層の積み重ねとして参照します。

Docker 内部では、 AUFS ユニオン・マウントがイメージのレイヤ化を行います。AUFS ストレージ・ドライバは、ユニオン・マウント・システムを使って Docker イメージを扱います。AUFS ブランチが Docker イメージ・レイヤに相当します。以下の図は ubuntu:latest イメージをベースとする Docker コンテナです。

イメージ層

この図は、Docker イメージ・レイヤと、Docker ホスト上のローカル・ストレージ領域 /var/lib/docker/aufs にある AUFS ブランチ(ディレクトリ)の関係を表しています。ユニオン・マウント・ポイントは、全てのレイヤを一体化して見えるようにします。

また、AUFS はコピー・オン・ライト技術(CoW)もサポートしています。これは、全てのドライバがサポートしているものではありません。

AUFS でコンテナの読み書き

Docker は AUFS CoW 技術をテコに、イメージ共有とディスク使用量の最小化をできるようにします。AUFS はファイルレベルで動作します。つまり、AUFS CoW 処理は、ファイル全体をコピーします。それがファイルの一部を変更する場合でもです。この処理はコンテナの性能に大きな影響を与えます。特に、コピーする対象のファイルが大きい場合は、配下のイメージ・レイヤが沢山あったり、あるいは、CoW 処理により深いディレクトリ・ツリーを検索する必要なためです。

考えてみましょう。例えばコンテナで実行しているアプリケーションが、大きなキーバリュー・ストア(ファイル)に新しい値を追加したとします。これが初回であれば、コンテナの一番上の書き込み可能なレイヤに、まだ変更を加えるべきファイルが存在していません。そのため、 CoW 処理は、下部のイメージからファイルを 上にコピー する必要があります。AUFS ストレージ・ドライバは、各イメージ・レイヤ上でファイルを探します。検索順番は、上から下にかけてです。ファイルが見つかれば、対象のファイルをコンテナの上にある書き込み可能なレイヤに コピー (copy up)します。そして、やっとファイルを開き、編集出来るようになります。

小さなファイルに比べて、大きなファイルであれば明らかにコピー時間がかかります。そして、高いイメージ・レイヤにファイルがあるよりも、低いレイヤにある場合も時間がかかります。しかしながら、コピー作業が発生するのは、対象のコンテナ上では1度だけです。次にファイルの読み書き処理が発生しても、コンテナの一番上のレイヤにコピー済みのファイルがある場合は、ファイルを再度コピーしません。

AUFS ストレージ・ドライバでのファイル削除

AUFS ストレージ・ドライバでコンテナからファイルを削除すると、コンテナの一番上のレイヤに ホワイトアウト・ファイル(whiteout file) が置かれます。ホワイトアウト・ファイルとは、イメージの下の読み込み専用レイヤに存在しているファイルを、効果的に隠すものです。以下に単純化した図は、3つのイメージ・レイヤのイメージに基づくコンテナを表しています。

イメージ層

ファイル3 はコンテナ上で削除されました。すると、AUFS ストレージ・ドライバは、コンテナの最上位レイヤにホワイトアウト・ファイルを置きます。このホワイトアウト・ファイルは、イメージの読み込み専用なベース・レイヤに存在するオリジナルのファイルを隠すことにより、コンテナ上から事実上 ファイル3 が削除されたものとします。もちろん、イメージは他のレイヤの一部であり続けますし、更にレイヤが’構築されれば、そこに依存関係も追加されます。

Docker で AUFS を使う設定

AUFS ストレージ・ドライバを使えるのは、AUFS がインストールされた Linux システム上でのみです。以下のコマンドを使い、システムが AUFS をサポートしているかどうか確認します。

$ grep aufs /proc/filesystems
nodev   aufs

この出力は、システムが AUFS をサポートしています。自分のシステムで AUFS をサポートしているのを確認したら、Docker デーモンに対して AUFS を使う指示が必要です。これには docker daemon コマンドを使えます。

$ sudo docker daemon --storage-driver=aufs &

あるいは、Docker の設定ファイルを編集し、 DOCKER_OPTS 行に --storage-driver=aufs オプションを追加します。

# DOCKER_OPTS で、デーモン起動時のオプションを編集
DOCKER_OPTS="--storage-driver=aufs"

デーモンを起動すると、 docker info コマンドでストレージ・ドライバを確認します。

$ sudo docker info
Containers: 1
Images: 4
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 6
 Dirperm1 Supported: false
Execution Driver: native-0.2
...出力を省略...

このような出力から、起動中の Docker デーモンが既存の ext4 ファイルシステム上で AUFS ストレージ・ドライバを使っていることが分かります。

ローカルのストレージと AUFS

As the docker daemon runs with the AUFS driver, the driver stores images and containers on within the Docker host’s local storage area in the /var/lib/docker/aufs directory.

docker daemon を AUFS ドライバで実行すると、ドライバは Docker ホスト上のローカル・ストレージ領域である /var/lib/docker/aufs ディレクトリ内に、イメージとコンテナを保管します。

イメージ

イメージ・レイヤと各コンテナは、 /var/lib/docker/aufs/mnt/diff/<イメージID> ディレクトリ以下に保管されます。この場所にあるイメージ・レイヤに含まれるのは、対象のイメージ・レイヤに所属する全てのファイルとディレクトリです。

/var/lib/docker/aufs/layers/ ディレクトリに含まれるのは、どのようにイメージ・レイヤを重ねるかというメタデータです。このディレクトリには、Docker ホスト上のイメージかコンテナ毎に1つのファイルがあります。各ファイルの中にはイメージ・レイヤの名前があります。次の図は1つのイメージが4つのレイヤを持つのを示しています。

AUFS メタデータ

イメージの最上位レイヤのファイル内容を調べると、下層にある3つのイメージ・レイヤに関する情報が含まれています。これらは積み重ねられた順番で並べられています。

$ cat /var/lib/docker/aufs/layers/91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c
d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82
c22013c8472965aa5b62559f2b540cd440716ef149756e7b958a1b2aba421e87
d3a1f33e8a5a513092f01bb7eb1c2abf4d711e5105390a3fe1ae2248cfde1391

The base layer in an image has no image layers below it, so its file is empty.

イメージのベース・レイヤは下層にイメージ・レイヤを持ちませんので、対象となるファイルの内容は空っぽです。

コンテナ

実行中のコンテナは /var/lib/docker/aufs/mnt/<コンテナ ID> ディレクトリの中にマウントされます。これが AUFS ユニオン・マウント・ポイントであり、コンテナと下層のイメージ・レイヤが1つに統合されて公開されている場所です。コンテナが実行されていなければ、これらのディレクトリは存在しますが、内容は空っぽです。なぜなら、コンテナが実行する時のみマウントするための場所だからです。

コンテナのメタデータやコンテナの実行に関する様々な設定ファイルは、 /var/lib/containers/<コンテナ ID> に保管されます。ディレクトリ内に存在するファイルはシステム上の全コンテナに関するものであり、停止されたものも含みます。しかしながら、コンテナを実行すると、コンテナのログファイルもこのディレクトリに保存されます。

コンテナの薄い書き込み可能なレイヤ(thin writable layer)は /var/lib/docker/aufs/diff/<コンテナ ID> に保管されます。このディレクトリは AUFS によってコンテナの最上位の書き込みレイヤとして積み重ねられるもので、コンテナに対する全ての変更が保管されます。コンテナが停止しても、このディレクトリは存在し続けます。つまり、コンテナを再起動しても、その変更内容は失われません。コンテナが削除された時のみ、このディレクトリは削除されます。

コンテナ最上位の書き込み可能なレイヤの下に、どのようなイメージ・レイヤが積み重ねられているかという情報は、ファイル /var/lib/docker/aufs/layers/<コンテナ ID> のファイルを調べます。以下のコマンドから、コンテナ ID b41a6e5a508d が4つのイメージ・レイヤを下層に持っているのが分かります。

$ cat /var/lib/docker/aufs/layers/b41a6e5a508dfa02607199dfe51ed9345a675c977f2cafe8ef3e4b0b5773404e-init
91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c
d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82
c22013c8472965aa5b62559f2b540cd440716ef149756e7b958a1b2aba421e87
d3a1f33e8a5a513092f01bb7eb1c2abf4d711e5105390a3fe1ae2248cfde1391

イメージ・レイヤは順番に表示されます。先ほどの実行結果では、イメージ ID 「d3a1...」のレイヤをベース・イメージとしているのが分かります。イメージ ID「91e5...]のイメージ・レイヤ、イメージ最上位のレイヤです。

AUFS と Docker の性能

既に言及している性能面について、まとめます。

  • AUFS ストレージ・ドライバは PaaS とコンテナの密度が重要な類似事例にとって、良い選択肢です。これは複数の実行中のコンテナ間で、 AUFS が効率的にイメージを共有するためです。それにより、コンテナの起動時間を早くし、ディスク使用量を最小化します。
  • AUFS がイメージ・レイヤとコンテナ間でどのように共有するのか、その根底にある仕組みは、システム・ページ・キャッシュを非常に効率的に使います。
  • AUFS ストレージ・ドライバはコンテナに対する書き込み性能に対し、著しい待ち時間をもたらし得ます。これはコンテナに何らかのファイルを書き込もうとすると、ファイルをコンテナ最上位の書き込み可能レイヤに対してコピーする必要があるためです。ファイルが多くのイメージ・レイヤに存在する場合や、ファイル自身が大きい場合には、待ち時間が増え、悪化するでしょう。

最後に1つだけ。データ・ボリュームは最高かつ最も予想可能な性能をもたらします。これはデータ・ボリュームがストレージ・ドライバを迂回するためであり、シン・プロビジョニングやコピー・オン・ライトによるオーバヘッドの影響を受けないためです。この理由のため、思い書き込み処理を行いたい場合には、データ・ボリュームを使ったほうが良い場合もあるでしょう。