Btrfs ストレージ・ドライバの使用

Btrfs (ビーツリー・エフエス)は次世代のコピー・オン・ライト対応ファイルシステムです。多くの高度なストレージ技術をサポートしており、Docker に適しています。Btrfs は Linux カーネルのメインラインに含まれており、オンディスク・フォーマットは安定していると考えられています。しかしながら、その機能の大部分はまだ開発中です。そのため、ユーザとしては Btrfs を動きの速い「モノ」と考えるべきでしょう。

Docker の btrfs ストレージ・ドライバは、イメージとコンテナを管理するために、多くの Btrfs 機能を活用します。機能とは、シン・プロビジョニング、コピー・オン・ライト、スナップショットに関するものです。

このセクションでは、Docker の Btrfs ストレージ・ドライバを btrfs と表記します。また、Btrfs ファイルシステム全体を Btrfs として表記します。

注釈

商用サポート版 Docker Engine(CS-Engine) は、現時点では btrfs ストレージ・ドライバをサポートしていません。

Btrfs の未来

Btrfs は将来の Linux ファイルシステムとして、長く賞賛されています。Linux カーネルの主流として完全にサポートされ、安定したオン・ディスク・フォーマットと、安定性に焦点をあてた活発な開発が、より現実性を高めています。

Linux プラットフォームで Docker を使う限り、多くの皆さんには devicemapper ストレージ・ドライバを長期的に置き換えるものとして、 btrfs ストレージドライバがあるように見えてしまうでしょう。しかし、これを書いている時点では、 devicemapper ストレージ・ドライバの方が安全で、より安定しており、より プロダクションに対応している と考えるべきです。 btrfs ドライバを理解し、Btrfs の経験がある場合のみ、プロダクションの開発に btrfs ドライバを検討すべきでしょう。

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

ディスク上のイメージの構成物とコンテナ・レイヤを管理するため、Docker は Btrfs の サブボリュームスナップショット を活用します。Btrfs サブボリュームの見た目は、通常の Unix ファイルシステムと同じです。各々が内部にディレクトリ構造を持つのは、幅広い Unix ファイルシステムで見られるものです。

サブボリュームはコピー・オン・ライトにネイティブに対応しており、必要があれば基礎をなすストレージ・プールから領域を割り当てます。また、領域はネスト(入れ子)にすることができ、スナップ化できます。次の図は4つのボリュームを表します。「サブボリューム4」は内部にディレクトリ・ツリーを持っているのに対し、「サブボリューム2」と「サブボリューム3」はネストされたものです。

btrfs サブボリューム

スナップショットとは、ある時点におけるサブボリューム全体の読み書きをコピーします。既存のディレクトリの下にサブボリュームを作成します。下図のように、スナップショットのスナップショットも作成できます。

btrfs スナップショット

サブボリュームとスナップショットの必要に応じ、Btrfs は基盤をなすストレージ・プールから領域を割り当てます。割り当ての単位は chunkchunks から参照され、通常は 1GB 以下の大きさです。

スナップショットは Btrfs ファイルシステム上の優秀な機能です。つまり、通常のサブボリュームと同じ見た目と、感触、操作が可能です。ネイティブなコピー・オン・ライトの設計のおかげで、Btrfs ファイルシステム内のディレクトリにサブボリュームを構築する時、この技術が必要になります。つまり、Btrfs スナップショットは性能のオーバーヘッドが少ない、あるいは無いため、効率的に領域を使います。次の図はスナップショットが同じデータを共有しているサブボリュームです。

btrfs プール

Docker の btrfs ストレージ・ドライバは、各イメージ・レイヤとコンテナを、自身の Btrfs サブボリュームかスナップショットに保管します。イメージのベース・レイヤはサブボリュームとして保管します。それに対して子イメージ・レイヤとコンテナは、スナップショットに保管します。これを説明したのが次の図です。

btrfs コンテナ・レイヤ

Docker ホストが btrfs ドライバを使い、イメージとコンテナの作成という高レベルの処理手順は、次のように行います。

  1. イメージのベース・レイヤは /var/lib/docker/btrfs/subvolumes 以下の Btrfs サブボリュームに保管します。

  1. 以降のイメージ・レイヤは、親レイヤのサブボリュームの Btrfs スナップショットとして保存されるか、(単体の)スナップショットになります。

以下の図は3つのイメージ・レイヤを表しています。ベース・レイヤはサブボリュームです。レイヤ1はベース・レイヤに対するサブボリュームのスナップショットです。レイヤ2はレイヤ1のスナップショットです。

ディスク構造上のイメージ

Docker 1.10 からは、イメージ・レイヤ ID は /var/lib/docker 以下のディレクトリ名と一致しません。

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

イメージ・レイヤとコンテナは、 Docker ホスト上のファイルシステム /var/lib/docker/btrfs/subvolumes/ にあります。しかしながら、以前とは異なり、ディレクトリ名はイメージ ID の名前を表しません。コンテナ用のディレクトリは、コンテナが停止した状態でも表示されます。ストレージ・ドライバがデフォルトでマウントするのは、 /var/lib/docker/subvolumes のサブボリュームをトップレベルとする場所です。その他全てのサブボリュームとボリューム名は、 Btrfs ファイルシステムのオブジェクトとして個々にマウントするのではなく、この下に存在しています。

Btrfs はファイルシステム・レベルで動作するものであり、ブロック・レベルではありません。各イメージとコンテナのレイヤは、通常の Unix コマンドを使って参照できます。次の例は、イメージの最上位レイヤに対して ls -l コマンドを実行した結果を省略したものです。

$ ls -l /var/lib/docker/btrfs/subvolumes/0a17decee4139b0de68478f149cc16346f5e711c5ae3bb969895f22dd6723751/
total 0
drwxr-xr-x 1 root root 1372 Oct  9 08:39 bin
drwxr-xr-x 1 root root    0 Apr 10  2014 boot
drwxr-xr-x 1 root root  882 Oct  9 08:38 dev
drwxr-xr-x 1 root root 2040 Oct 12 17:27 etc
drwxr-xr-x 1 root root    0 Apr 10  2014 home
...表示結果を省略...

Btrfs でコンテナを読み書き

コンテナはイメージ領域を効率的に扱うスナップショットです。スナップショットの中のメタデータが示す実際のデータ・ブロックは、ストレージ・プールの中にあります。これはサブボリュームの扱いと同じです。そのため、スナップショットの読み込み性能は、サブボリュームの読み込み性能と本質的に同じです。その結果、Btrfs ドライバ使用による性能のオーバヘッドはありません。

新しいファイルをコンテナに書き込む時、コンテナのスナップショットに新しいデータブロックを割り当てる処理が、必要に応じて発生します。それから、ファイルを新しい領域に書き込みます。必要に応じて書き込む処理は Btrfs によってネイティブに書き込まれ、新しいデータをサブボリュームに書き込むのと同じです。その結果、コンテナのスナップショットに新しいファイルを書き込む処理は、ネイティブな Btrfs の速度になります。

コンテナ内にある既存のファイルを更新したら、コピー・オン・ライト処理(技術的には、書き込みのための転送、という意味です)が発生します。ドライバはオリジナルのデータをそのままに、スナップショットに新しい領域を割り当てます。更新されたデータは新しい領域に書き込みます。それから、ドライバはファイルシステムのメタデータを更新し、スナップショットが新しいデータを示すようにします。元々あったデータはサブボリュームとスナップショットのための更なるツリーの活用場所として維持されます。この動作はコピー・オン・ライトのファイルシステムがネイティブな Btrfs 向けであり、非常に小さなオーバヘットとなります。

Btrfs を使う場合、大量の小さなファイルの書き込みと更新は、パフォーマンスの低下を招きます。詳細は後ほど扱います。

Docker で Btrfs を設定

btrfs ストレージ・ドライバは、Docker ホストで Btrfs ファイルシステムとしてマウントしている /var/lib/docker のみ処理します。以下の手順で、 Ubuntu 14.04 LTS 上で Btrfs を設定する方法を紹介します。

動作条件

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

まず Docker デーモンを停止します。そして /dev/xvdb に予備のブロック・デバイスがあることを確認します。このデバイスは個々の環境によって違うかもしれませんが、処理にあたっては各環境によって違う場合があります。

またこの手順では、カーネルが適切な Btrfs モジュールを読み込まれているものと想定しています。これらを確認したら、以下のコマンドを実行します。

$ cat /proc/filesystems | grep btrfs`

Ubuntu 14.04 LTS で Btrfs を設定

システムが動作条件を満たしていると仮定し、次の手順を進めます。

  1. 「btrfs-tools」パッケージをインストールします。

$ sudo apt-get install btrfs-tools
Reading package lists... Done
Building dependency tree
<出力を省略>
  1. Btrfs ストレージ・プールを作成します。

Btrfs ストレージ・プールは mkfs.btrfs コマンドで作成します。複数のデバイスにわたるプールを作成するには、それぞれのデバイスで mkfs.btrfs コマンドを実行します。ここでは、作成したプールは単一デバイス上の /dev/xvdb と想定しています。

$ sudo mkfs.btrfs -f /dev/xvdb
WARNING! - Btrfs v3.12 IS EXPERIMENTAL
WARNING! - see http://btrfs.wiki.kernel.org before using


Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
fs created label (null) on /dev/xvdb
    nodesize 16384 leafsize 16384 sectorsize 4096 size 4.00GiB
Btrfs v3.12

/dev/xvdb には、各システム上の適切なデバイスを割り当ててください。

警告

Btrfs は実験的な実装なのでご注意ください。先ほど説明した通り、Btrfs の利用経験がなければ、現時点ではプロダクションへのデプロイには推奨されていません。

  1. Docker ホスト上に、ローカル・ストレージ領域が /var/lib/docker になければ、ディレクトリを作成します。

$ sudo mkdir /var/lib/docker
  1. システムのブート時に、毎回自動的に Btrfs ファイルシステムをマウントするよう設定します。

  1. Btrfs ファイルシステムの UUID を取得します。

$ sudo blkid /dev/xvdb
/dev/xvdb: UUID="a0ed851e-158b-4120-8416-c9b072c8cf47" UUID_SUB="c3927a64-4454-4eef-95c2-a7d44ac0cf27" TYPE="btrfs"
  1. /etc/fstab エントリに、システムのブート時に毎回自動的に /var/lib/docker をマウントする記述を追加します。

/dev/xvdb /var/lib/docker btrfs defaults 0 0
UUID="a0ed851e-158b-4120-8416-c9b072c8cf47" /var/lib/docker btrfs defaults 0 0
  1. 新しいファイルシステムをマウントし、操作可能か確認します。

$ sudo mount -a
$ mount
/dev/xvda1 on / type ext4 (rw,discard)
<出力を省略>
/dev/xvdb on /var/lib/docker type btrfs (rw)

最後の行の出力から、 /dev/xvdb は Btrfs として /var/lib/docker をマウントしているのが分かります。

これで Btrfs ファイルシステムが /var/lib/docker をマウントしたので、デーモンは btrfs ストレージ・ドライバを自動的に読み込めるようにします。

  1. Docker デーモンを起動します。

$ sudo service docker start
docker start/running, process 2315

この Docker デーモンの起動手順は、使用している Linux ディストリビューションによっては異なる場合があります。

btrfs ストレージ・ドライバを使って Docker デーモンを起動するには、 docker daemon コマンドで --storage-driver=btrfs フラグを渡すか、 Docker 設定ファイルの DOCKER_OPT 行でフラグを追加します。

  1. docker info コマンドでストレージ・ドライバを確認します。

$ sudo docker info
Containers: 0
Images: 0
Storage Driver: btrfs
[...]

これで、Docker ホストは btrfs ストレージ・ドライバを使う新しい設定で動いています。

Btrfs と Docker の性能

btrfs ストレージ・ドライバ配下では、Docker の性能に影響を与える様々な要素があります。

  • ページ・キャッシュ :btrfs はページ・キャッシュ共有をサポートしていません。つまり、n個のコンテナがキャッシュするために、n個のコピーを必要とします。そのため、 btrfs ドライバは、PaaS や、その他にも密度を求められる用途には適していません。

  • 小さな書き込み :コンテナに対する小さな書き込み(Docker ホストが多くのコンテナを起動・停止している場合)が大量であれは、Btrfs の塊(chunk)を粗く消費します。これにより、停止するまで Docker ホスト上で多くの容量を消費します。これが現時点の Btrfs バージョンにおける主な障害です。

もし btrfs ストレージ・ドライバを使うのであれば、 btrfs filesystem show コマンドで Btrfs ファイルシステム上の空き容量を頻繁に監視してください。 df のような通常の Unix コマンドを信頼しないでください。つまり、常に Btrfs のネイティブ・コマンドを使ってください。

  • シーケンシャルな書き込み :Btrfs はジャーナリングの技術を使ってデータをディスクに書き込みます。この影響により、シーケンシャル(連続した)書き込みでは、性能が半減します。

  • 断片化 :断片化(fragmentation)とは、Btrfs のようなフィルシステム上でコピー・オン・ライトを行うと生じる自然な副産物です。たくさんの小さなランダムな書き込みが、断片化を引き起こします。SSD メディアを使う Docker ホスト上では CPU のスパイク(突発的な利用)が顕著ですし、回転メディア(HDD)で Docker ホストを動かす場合も、メディアをむち打つものです。いずれの場合も、パフォーマンス低下の影響を招きます。

Btrfs の最近のバージョンは、 autodefrag をマウント用のオプションに指定できます。このモードによって、断片化せずにランダムに書き込みをします。ただ、Docker ホスト上でこのオプションを有効化する前に、自分自身で性能評価をすべきです。いくつかのテストは Docker ホスト上に多数の小さなファイルを作成しますので、良くない性能に影響与える場合があります(あるいは、システムで沢山のコンテナを停止・起動した場合も)。

  • SSD(ソリッド・ステート・ドライブ) :Btrfs は SSD メディアをネイティブに最適化します。最適化を有効化するには、マウントオプションで -o ssd を指定します。 SSD はメディアを使わずシーク最適化が不要なため、これら最適化により SSD の性能を拡張します。

また、Btrfs は TRIM/Discard プリミティブもサポートします。しかし、 -o discard マウント・オプションでマウントすると、性能の問題を引き起こします。そのため、このオプションを使う前に、自分自身で性能評価をするのを推奨します。

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

関連情報