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 は基盤をなすストレージ・プールから領域を割り当てます。割り当ての単位は 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 ホストで 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 を設定¶
システムが動作条件を満たしていると仮定して、次の通りに行います。
- 「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 の性能に影響を与える様々な要素があります。
- ページ・キャッシュ :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
マウント・オプションでマウントすると、性能の問題を引き起こします。そのため、このオプションを使う前に、自分自身で性能評価をすることを推奨します。
- データ・ボリュームの使用 :データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを行うのに適しています。