Device Mapper ストレージ・ドライバを使う

Device Mapper は、Linux 上で多くの高度なボリューム管理技術を支えるカーネル・ベースのフレームワークです。Docker の devicemapper ストレージ・ドライバは、シン・プロビジョニングとスナップショット機能のために、イメージとコンテナ管理にこのフレームワークを活用します。この記事では、Device Mapper ストレージ・ドライバを devicemapper とし、カーネルのフレームワークを Device Mapper として言及します。

注釈

devicemapper ストレージ・ドライバを使うには、 RHEL か CentOS Linux 上で商用サポート版 Docker Engine (CS-Engine) を実行 する必要があります。

AUFS の代替

当初の Docker は、Ubuntu と Debian Linux 上で AUFS をストレージのバックエンドに使っていました。Docker が有名になるにつれ、多くの会社が Red Hat Enterprise Linux 上で使いたいと考え始めました。残念ながら、AUFS は Linux カーネル上流のメインラインではないため、RHEL では AUFS を扱いませんでした。

この状況を変えるべく、Red Hat の開発者達が AUFS をカーネルのメインラインに入れられるよう取り組みました。しかしながら、新しいストレージ・バックエンドを開発するの方が良い考えであると決断したのです。さらに、ストレージのバックエンドには、既に存在していた Device Mapper 技術を基盤とすることにしました。

Red Hat は Docker 社と協同で新しいドライバの開発に取り組みました。この協調の結果、ストレージ・バックエンドの取り付け・取り外し可能な(pluggable)Docker エンジンが再設計されました。そして、 devicemapper は Docker がサポートする2番目のストレージ・ドライバとなったのです。

Device Mapper は Linux カーネルのバージョン 2.6.9 以降、メインラインに組み込まれました。これは、RHEL ファミリーの Linux ディストリビューションの中心部です。つまり、 devicemapper ストレージ・ドライバは安定したコードを基盤としており、現実世界における多くのプロダクションへのデプロイや、強力なコミュニティのサポートをもたらします。

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

devicemapper ドライバは、全てのイメージとコンテナを自身の仮想デバイスに保管します。これらのデバイスとは、シン・プロビジョニングされ、コピー・オン・ライト可能であり、スナップショットのデバイスです。Device Mapper 技術はファイル・レベルというよりブロック・レベルで動作します。つまり、 devicemapper ストレージ・ドライバのシン・プロビジョニング(thin-provisioning)とコピー・オン・ライト(copy-on-write)処理は、ファイルではなくブロックに対して行われます。

注釈

また、スナップショットは シン・デバイス(thin device)仮想デバイス(virtual device) としても参照されます。つまり、 devicemapper ストレージ・ドライバにおいては、どれも同じものを意味します。

devicemapper でイメージを作るハイレベルな手順は、以下の通りです。

  1. devicemapper ストレージ・ドライバはシン・プール(thin pool)を作成します。

ブロック・デバイスかループ用にマウントされた薄いファイル(sparse files)上に、このプールが作成されます。

  1. 次に ベース・デバイス(base device) を作成します。

ベース・デバイスとは、ファイルシステムのシン・デバイス(thin device)です。どのファイルシステムが使われているかを調べるには、 docker info コマンドを実行し、 Backing filesystem 値を確認します。

  1. それぞれの新しいイメージ(とイメージ・レイヤ)は、このベース・デバイスのスナップショットです。

これらがシン・プロビジョニングされたコピー・オン・ライトなスナップショットです。つまり、これらは初期状態では空っぽですが、データが書き込まれるときだけ容量を使います。

devicemapper では、ここで作成されたイメージのスナップショットが、コンテナ・レイヤになります。イメージと同様に、コンテナのスナップショットも、シン・プロビジョニングされたコピー・オン・ライトなスナップショットです。コンテナのスナップショットに、コンテナ上での全ての変更が保管されます。 devicemapper は、コンテナに対してデータを書き込むとき、このプールから必要に応じて領域を割り当てます。

以下のハイレベルな図は、ベース・デバイスのシン・プールと2つのイメージを表します。

ベース・デバイス

細かく図をみていくと、スナップショットは全体的に下っているのが分かるでしょう。各イメージ・レイヤは下にあるレイヤのスナップショットです。各イメージの最も下にあるレイヤは、プール上に存在するベース・デバイスのスナップショットです。このベース・デバイスとは Device Mapper のアーティファクト(成果物)であり、Docker イメージ・レイヤではありません。

コンテナは、ここから作成されたイメージのスナップショットです。下図は2つのコンテナです。一方がベースにしているのは Ubuntu イメージであり、もう一方は Busybox イメージをベースにしています。

2つの Device Mapper 上のコンテナ

devicemappr からの読み込み

devicemapper ストレージ・ドライバが、どのように読み書きしているか見ていきましょう。下図は、サンプル・コンテナが単一のブロック( 0x44f )を読み込むという、ハイレベルな手順です。

Device Mapper 上のコンテナ
  1. アプリケーションがコンテナ内のブロック 0x44f に対して読み込みを要求します。

コンテナは、イメージの薄い(thin)スナップショットであり、データを持っていません。その代わりに、下層のイメージ層(スタック)にあるイメージのスナップショット上の、どこにデータが保管されているかを示すポインタ(PTR)を持っています。

  1. ストレージ・ドライバは、スナップショットのブロック 0xf33 と関連するイメージ・レイヤ a005... のポインタを探します。
  1. devicemapper はブロック 0xf33 の内容を、イメージのスナップショットからコンテナのメモリ上にコピーします。
  1. ストレージ・ドライバはアプリケーションがリクエストしたデータを返します。

書き込み例

devicemapper ドライバで新しいデータをコンテナに書き込むには、オンデマンドの割り当て(allocate-on-demand) を行います。コピー・オン・ライト処理をによって、既存のデータを更新します。Device Mapper はブロック・ベースの技術のため、これらの処理はブロック・レベルで行われます。

例えば、コンテナ内の大きなファイルに小さな変更を加えるとき、 devicemapper ストレージ・ドライバはファイル全体コピーをコピーしません。コピーするのは、変更するブロックのみです。各ブロックは 64KB です。

新しいデータの書き込み

コンテナに 56KB の新しいデータを書き込みます。

  1. アプリケーションはコンテナに 56KB の新しいデータの書き込みを要求します。
  1. オンデマンドの割り当て処理により、コンテナのスナップショットに対して、新しい 64KB のブロックが1つ割り当てられます。

書き込み対象が 64KB よりも大きければ、複数の新しいブロックがコンテナに対して割り当てられます。

  1. 新しく割り当てられたブロックにデータを書き込みます。

既存のデータを上書き

既存のデータに対して初めて変更を加える場合、

  1. アプリケーションはコンテナ上にあるデータの変更を要求します。
  1. 更新が必要なブロックに対して、コピー・オン・ライト処理が行われます。
  1. 処理によって新しいブロックがコンテナのスナップショットに割り当てられ、そのブロックにデータがコピーされます。
  1. 新しく割り当てられたブロックの中に、変更したデータを書き込みます。

コンテナ内のアプリケーションは、必要に応じた割り当てやコピー・オン・ライト処理を意識しません。しかしながら、アプリケーションの読み書き処理において、待ち時間を増やすでしょう。

Device Mapper を Docker を使う設定

複数のディストリビューションにおいて、devicemapper は標準の Docker ストレージ・ドライバです。ディストリビューションはRHEL や派生したものが含まれます。現時点では、以下のディストリビューションがドライバをサポートしています。

  • RHEL/CentOS/Fedora
  • Ubuntu 12.04
  • Ubuntu 14.04
  • Debian

Docker ホストは devicemapper ストレージ・ドライバを、デフォルトでは loop-lvm というモードで設定します。このモードは、イメージとコンテナのスナップショットが使うシン・プール(thin pool)を構築するために、スパース・ファイル(sparse file;まばらなファイル)を使う指定です。このモードは、設定に変更を加えることなく、革新的な動きをするように設計されています。しかしながら、プロダクションへのデプロイでは、 loop-lvm モードの下で実行すべきではありません。

どのようなモードで動作しているか確認するには docker info コマンドを使います。

$ sudo docker info
Containers: 0
Images: 0
Storage Driver: devicemapper
 Pool Name: docker-202:2-25220302-pool
 Pool Blocksize: 65.54 kB
 Backing Filesystem: xfs
 ...
 Data loop file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
 Library Version: 1.02.93-RHEL7 (2015-01-28)
 ...

この実行結果から、Docker ホストは devicemapper ストレージ・ドライバの操作に loop-lvm モードを使っているのが分かります。実際には、 データ・ループ・ファイル (data loop file)メタデータ・ループ・ファイル (Metadata loop file) のファイルが /var/lib/docker/devicemapper/devicemapper 配下にあるのを意味します。これらがループバックにマウントされているパース・ファイルです。

プロダクション用に direct-lvm モードを設定

プロダクションへのデプロイに適した設定は direct lvm です。このモードはシン・プールの作成にブロック・デバイスを使います。以下の手順は、Docker ホストが devicemapper ストレージ・ドライバを direct-lvm 設定を使えるようにします。

ご用心

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

以下の手順は 90GB のデータ・ボリュームと 4GB のメタデータ・ボリュームを作成し、ストレージ・プールの基礎として使います。ここでは別のブロック・デバイス /dev/xvdf を持っており、処理するための十分な空き容量があると想定しています。デバイスの識別子とボリューム・サイズは皆さんの環境とは異なるかもしれません。手順を勧めるときは、自分の環境にあわせて適切に置き換えてください。また、手順は Docker デーモンが stop (停止)した状態から始めることを想定しています。

  1. Docker ホストにログインし、設定対象の Docker デーモンを停止します。
  1. 終了したら、 /var/lib/docker ディレクトリに保管されている既存のイメージを削除します。
$ sudo rm -rf /var/lib/docker
  1. もう1つのブロックデバイス上で pvcreate コマンドを使い、 LVM 物理ボリューム(PV; Physical Volume)を作成します。
$ sudo pvcreate /dev/xvdf
Physical volume `/dev/xvdf` successfully created

このデバイス識別子は、皆さんの環境によって異なります。このコマンドを実行する時は、適切な値に書き換えてください。

  1. 先の手順で作成した物理ボリュームを使い、 vg-docker という名称の新しいボリューム・グループ(VG; Volume Group)を作成します。
$ sudo vgcreate vg-docker /dev/xvdf
Volume group `vg-docker` successfully created
  1. vg-docker ボリューム・グループ上の領域に、 data という名所の新しい 90GB の論理ボリューム(LV; Logical Volume)を作成します。
$ sudo lvcreate -L 90G -n data vg-docker
Logical volume `data` created.

このコマンドは data と呼ばれる LVM 論理ボリュームを作成し、 /dev/vg-docker/data にであるブロック・デバイス・ファイルに関連づけます。後の手順で、 devicemapper ストレージ・ドライバがこのブロックデバイスを使い、イメージやコンテナのデータを保管するように指示します。

署名に関する警告が表示される場合は、作業を続ける前に、正しいデバイスが動作しているかどうか確認します。署名の警告が意味するのは、作業対象が LVM によって既に使われているか、あるいは過去に使われていたかです。

  1. vg-docker ボリューム・グループ上の領域に、 metadata と呼ばれる新しい論議ボリューム(LV)を作成します。
$ sudo lvcreate -L 4G -n metadata vg-docker
Logical volume `metadata` created.

これは metadata という名称の LVM 論理ボリュームを作成し、 /dev/vg-docker/metadata にあるブロック・デバイス・ファイルに関連づけられます。次のステップで、 devicemapper ストレージ・ドライバがこのブロックデバイスを使い、イメージやコンテナのデータを保管するように指示します。

  1. Docker デーモンが devicemapper ストレージ・ドライバを使って起動するため、 --storage-opt フラグを使います。

先ほどの手順で作成した datametadata デバイスを --storage-opt オプションで指定します。

$ sudo docker daemon --storage-driver=devicemapper --storage-opt dm.datadev=/dev/vg-docker/data --storage-opt dm.metadatadev=/dev/vg-docker/metadata &
[1] 2163
[root@ip-10-0-0-75 centos]# INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
INFO[0027] Option DefaultDriver: bridge
INFO[0027] Option DefaultNetwork: bridge
<出力を省略>
INFO[0027] Daemon has completed initialization
INFO[0027] Docker daemon commit=0a8c2e3 execdriver=native-0.2 graphdriver=devicemapper version=1.8.2

また、 --storage-driver--storage-opt フラグは Docker の設定ファイルか、デーモンの起動に使う servicesystemd コマンドでも指定できます。

  1. docker info コマンドを使い、デーモンが先ほど作成した datametadata デバイスが使われていることを確認します。
$ sudo docker info
INFO[0180] GET /v1.20/info
Containers: 0
Images: 0
Storage Driver: devicemapper
 Pool Name: docker-202:1-1032-pool
 Pool Blocksize: 65.54 kB
 Backing Filesystem: xfs
 Data file: /dev/vg-docker/data
 Metadata file: /dev/vg-docker/metadata
[...]

このコマンドの出力から、ストレージ・ドライバが devicemapper であることが分かります。最後の2行から、適切なデバイスが DatafileMetadata file を使っていることも分かります。

ホスト上の devicemapper 構造の例

..You can use the lsblk command to see the device files created above and the pool that the devicemapper storage driver creates on top of them.

lsblk コマンドを使うと、先ほど作成したデバイス・ファイルと、その上に devicemapper ストレージ・ドライバによって作られた pool (プール)を確認できます。

$ sudo lsblk
NAME                       MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda                       202:0    0    8G  0 disk
└─xvda1                    202:1    0    8G  0 part /
xvdf                       202:80   0  100G  0 disk
├─vg--docker-data          253:0    0   90G  0 lvm
│ └─docker-202:1-1032-pool 253:2    0  100G  0 dm
└─vg--docker-metadata      253:1    0    4G  0 lvm
  └─docker-202:1-1032-pool 253:2    0  100G  0 dm

下図は、先ほどの例で使ったイメージの更新を、 lsblk コマンドの詳細で表しています。

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

この図では、プールは Docker-202:1-1032-pool と名付けられ、先ほど作成した datametadata デバイスに渡っています。この devicemapper のプール名は、次のような形式です。

Docker-MAJ:MIN-INO-pool

MAJNININO は、デバイスのメジャー番号、マイナー番号、inode 番号です。

Device Mapper はブロック・レベルで処理を行うため、イメージ・レイヤとコンテナ間の差分を見るのは、少し大変です。しかしながら、2つの鍵となるディレクトリがあります。 /var/lib/docker/devicemapper/mnt ディレクトリには、イメージとコンテナのマウント・ポントがあります。 /var/lib/docker/devicemapper/metadata ディレクトリには、それぞれのイメージとコンテナのスナップショットを格納する1つのファイルがあります。このファイルには、各スナップショットのメタデータが JSON 形式で含まれています。

Device Mapper と Docker 性能

重要なのは、オンデマンドの割り当て(allocate-on-demand)とコピー・オン・ライト(copy-on-write)処理が、コンテナ全体の性能に対して影響があるのを理解することです。

オンデマンドの割り当てが性能に与える影響

devicemapper ストレージ・ドライバは、オンデマンドの割り当て処理時、コンテナに対して新しいブロックを割り当てます。この処理が意味するのは、コンテナの中でアプリケーションが何かを書き込みをするごとに、プールから1つまたは複数の空ブロックを探し、コンテナの中に割り当てます。

全てのブロックは 64KB です。64KB より小さな書き込みの場合でも、64Kb のブロックが1つ割り当てられます。これがコンテナの性能に影響を与えます。特にコンテナ内で多数の小さなファイルを書き込む場合に影響があるでしょう。しかしながら、一度ブロックがコンテナに対して割り当てらたら、以降の読み込みは対象のブロックを直接処理できます。

コピー・オン・ライトが性能に与える影響

コンテナ内のデータを初めて更新するたびに、毎回 devicemapper ストレージ・ドライバがコピー・オン・ライト処理を行います。このコピーとは、イメージのスナップショット上のデータを、コンテナのスナップショットにコピーするものです。この処理が、コンテナの性能に対して留意すべき影響を与えます。

コピー・オン・ライト処理は 64KB 単位で行われます。そのため、1GB のファイルのうち 32KB を更新する場合は、コンテナのスナップショット内にある 64KB のブロックをコピーします。これはファイル・レベルのコピー・オン・ライト処理に比べて、著しい性能をもたらします。ファイルレベルであれば、コンテナ・レイヤに含まれる 1GB のファイル全体をコピーする必要があるからです。

しかしながら、現実的には、コンテナが多くの小さなブロック(64KB以下)に書き込みをするのであれば、 devicemapper は AUFS を使うよりも性能が劣ります。

Device Mapper の性能に対するその他の考慮

devicemapper ストレージ・ドライバの性能に対して、他にもいくつかの影響を与える要素があります。

  • モード :Docker が devicemapper ストレージ・ドライバを使用する時、デフォルトのモードは loop-lvm です。このモードはスパース・ファイル(space files;薄いファイル)を使うので、性能を損ないます。そのため、 プロダクションへのデプロイでは推奨されません 。プロダクション環境で推奨されるモードは direct-lvm です。これはストレージ・ドライバが直接 raw ブロック・デバイスに書き込みます。
  • 高速なストレージ :ベストな性能を出すためには、 データ・ファイルメタデータ・ファイル を、 SSD のような高速なストレージ上に配置すべきです。あるいは、 SAN や NAS アレイといった、ダイレクト・アタッチ・ストレージでも同様でしょう。
  • メモリ使用量devicemapper は Docker ストレージ・ドライバのなかで、最も悪いメモリ使用効率です。同じコンテナのn個のコピーを起動するとき、n個のファイルをメモリ上にコピーします。これは、Docker ホスト上のメモリに対して影響があります。このため、 PaaS や他の高密度な用途には、devicemapper ストレージ・ドライバがベストな選択肢とは言えません。

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