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 は基盤をなすストレージ・プールから領域を割り当てます。割り当ての単位は chunk と chunks から参照され、通常は 1GB 以下の大きさです。
スナップショットは Btrfs ファイルシステム上の優秀な機能です。つまり、通常のサブボリュームと同じ見た目と、感触、操作が可能です。ネイティブなコピー・オン・ライトの設計のおかげで、Btrfs ファイルシステム内のディレクトリにサブボリュームを構築する時、この技術が必要になります。つまり、Btrfs スナップショットは性能のオーバーヘッドが少ない、あるいは無いため、効率的に領域を使います。次の図はスナップショットが同じデータを共有しているサブボリュームです。
Docker の btrfs
ストレージ・ドライバは、各イメージ・レイヤとコンテナを、自身の Btrfs サブボリュームかスナップショットに保管します。イメージのベース・レイヤはサブボリュームとして保管します。それに対して子イメージ・レイヤとコンテナは、スナップショットに保管します。これを説明したのが次の図です。
Docker ホストが btrfs
ドライバを使い、イメージとコンテナの作成という高レベルの処理手順は、次のように行います。
- イメージのベース・レイヤは
/var/lib/docker/btrfs/subvolumes
以下の Btrfs サブボリュームに保管します。
- 以降のイメージ・レイヤは、親レイヤのサブボリュームの 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 を設定する方法を紹介します。
.. Prerequisites
動作条件¶
既に Docker ホスト上で Docker デーモンを使っている場合は、イメージをどこかに保存する必要があります。そのため、処理を進める前に、それらのイメージを Docker Hub やプライベート Docker Trusted Registry に送信しておきます。
まず Docker デーモンを停止します。そして /dev/xvdb
に予備のブロック・デバイスがあることを確認します。このデバイスは個々の環境によって違うかもしれませんが、処理にあたっては各環境によって違う場合があります。
またこの手順では、カーネルが適切な Btrfs モジュールを読み込まれているものと想定しています。これらを確認したら、以下のコマンドを実行します。
$ cat /proc/filesystems | grep btrfs`
Ubuntu 14.04 LTS で Btrfs を設定¶
システムが動作条件を満たしていると仮定し、次の手順を進めます。
- 「btrfs-tools」パッケージをインストールします。
$ sudo apt-get install btrfs-tools
Reading package lists... Done
Building dependency tree
<出力を省略>
- 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 の利用経験がなければ、現時点ではプロダクションへのデプロイには推奨されていません。
- Docker ホスト上に、ローカル・ストレージ領域が
/var/lib/docker
になければ、ディレクトリを作成します。
$ sudo mkdir /var/lib/docker
- システムのブート時に、毎回自動的に Btrfs ファイルシステムをマウントするよう設定します。
- Btrfs ファイルシステムの UUID を取得します。
$ sudo blkid /dev/xvdb
/dev/xvdb: UUID="a0ed851e-158b-4120-8416-c9b072c8cf47" UUID_SUB="c3927a64-4454-4eef-95c2-a7d44ac0cf27" TYPE="btrfs"
/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
- 新しいファイルシステムをマウントし、操作可能か確認します。
$ 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
ストレージ・ドライバを自動的に読み込めるようにします。
- Docker デーモンを起動します。
$ sudo service docker start
docker start/running, process 2315
この Docker デーモンの起動手順は、使用している Linux ディストリビューションによっては異なる場合があります。
btrfs
ストレージ・ドライバを使って Docker デーモンを起動するには、 docker daemon
コマンドで --storage-driver=btrfs
フラグを渡すか、 Docker 設定ファイルの DOCKER_OPT
行でフラグを追加します。
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 ssh
を指定します。 SSD はメディアを使わずシーク最適化が不要なため、これら最適化により SSD の性能を拡張します。
また、Btrfs は TRIM/Discard プリミティブもサポートします。しかし、 -o discard
マウント・オプションでマウントすると、性能の問題を引き起こします。そのため、このオプションを使う前に、自分自身で性能評価をするのを推奨します。
- データ・ボリュームの使用 :データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを行うのに適しています。