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 を使う限り、多くの人々は btrfs ストレージドライバが devicemapper ストレージ・ドライバを長期的に置き換えるものに見えてしまうでしょう。しかし、これを書いている時点では、 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 ホストで Bitrfs ファイルシステムとしてマウントされている /var/lib/docker のみを処理します。以下の手順は、 Ubuntu 14.04 LTS 上で Btrfs の設定方法を紹介します。

動作条件

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

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 の性能に影響を与える様々な要素があります。

  • ページ・キャッシュ :btrrfs はページ・キャッシュ共有をサポートしていません。つまり、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 ホスト上でこのオプションwの有効化する前に、自分自身で性能評価をすべきです。いくつかのテストは Docker ホスト上に多数の小さなファイルを作成しますので、良くない性能に影響与える場合があります(あるいは、システムで沢山のコンテナを停止・起動した場合も)。

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

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

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