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 以下にあるローカル・ストレージ領域との関係性を表しています。ユニオン・マウント・ポイントは、全てのレイヤを一体化して見えるようにします。Docker 1.10 からは、イメージ ID はデータが置かれるディレクトリ名と対応しなくなりました。

また、AUFS はコピー・オン・ライト技術(copy-on-write; 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/diff/<イメージID> ディレクトリ以下に保管されます。Docker 1.10 以降はイメージ・レイヤ ID はディレクトリ名と一致しません。

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

AUFS メタデータ

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

以下のコマンドは、 /var/lib/docker/aufs/layers/ にあるメタデータ・ファイルを表示しています。ここで表示されるディレクトリの一覧は、ユニオン・マウントに積み重ねられている(スタックしている)ものです。ただし、覚えておかなくてはいけないのは、Docker 1.10 以上ではディレクトリ名とイメージ・レイヤ ID が一致しなくなりました。

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

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

コンテナ

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

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

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

AUFS と Docker の性能

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

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

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