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
でイメージを作る高度な手順は、以下の通りです。
devicemapper
ストレージ・ドライバはシン・プール(thin pool)を作成します。
ブロック・デバイスかループ用にマウントした薄いファイル(sparse files)上に、このプールを作成します。
- 次に ベース・デバイス(base device) を作成します。
ベース・デバイスとは、ファイルシステムのシン・デバイス(thin device)です。どのファイルシステムが使われているかを調べるには、 docker info
コマンドを実行し、 Backing filesystem
値を確認します。
- それぞれの新しいイメージ(とイメージ・レイヤ)は、このベース・デバイスのスナップショットです。
これらがシン・プロビジョニングされたコピー・オン・ライトなスナップショットです。つまり、これらは初期状態では空っぽですが、データが書き込まれる時だけ容量を使います。
devicemapper
では、ここで作成されたイメージのスナップショットが、コンテナ・レイヤになります。イメージと同様に、コンテナのスナップショットも、シン・プロビジョニングされたコピー・オン・ライトなスナップショットです。コンテナのスナップショットに、コンテナ上での全ての変更が保管されます。 devicemapper
は、コンテナに対してデータを書き込む時、このプールから必要に応じて領域を割り当てます。
以下のハイレベルな図は、ベース・デバイスのシン・プールと2つのイメージを表します。
細かく図を見ていきますと、スナップショットは全体的に下向きなのが分かるでしょう。各イメージ・レイヤは下にあるレイヤのスナップショットです。各イメージの最も下にあるレイヤは、プール上に存在するベース・デバイスのスナップショットです。このベース・デバイスとは Device Mapper
のアーティファクト(artifact;成果物の意味)であり、Docker イメージ・レイヤではありません。
コンテナとは、ここから作成したイメージのスナップショットです。下図は2つのコンテナです。一方は Ubuntu イメージをベースにし、もう一方は Busybox イメージをベースにしています。
devicemapper からの読み込み¶
devicemapper
ストレージ・ドライバが、どのように読み書きしているか見ていきましょう。下図は、サンプル・コンテナが単一のブロック( 0x44f
)を読み込むという、ハイレベルな手順です。
- アプリケーションがコンテナ内のブロック
0x44f
に対して読み込みを要求します。
コンテナは、イメージの薄い(thin)スナップショットであり、データを持っていません。そのかわりに、下層のイメージ層(スタック)にあるイメージのスナップショット上の、どこにデータが保管されているかを示すポインタ(PTR)を持っています。
- ストレージ・ドライバは、スナップショットのブロック
0xf33
と関連するイメージ・レイヤa005...
のポインタを探します。
devicemapper
はブロック0xf33
の内容を、イメージのスナップショットからコンテナのメモリ上にコピーします。
- ストレージ・ドライバはアプリケーションがリクエストしたデータを返します。
書き込み例¶
devicemapper
ドライバで新しいデータをコンテナに書き込むには、オンデマンドの割り当て(allocate-on-demand) を行います。コピー・オン・ライト処理により、既存のデータを更新します。Device Mapper はブロック・ベースの技術のため、これらの処理をブロック・レベルで行います。
例えば、コンテナ内の大きなファイルに小さな変更を加える時、 devicemapper
ストレージ・ドライバはファイル全体コピーをコピーしません。コピーするのは、変更するブロックのみです。各ブロックは 64KB です。
新しいデータの書き込み¶
コンテナに 56KB の新しいデータを書き込みます。
- アプリケーションはコンテナに 56KB の新しいデータの書き込みを要求します。
- オンデマンドの割り当て処理により、コンテナのスナップショットに対して、新しい 64KB のブロックが1つ割り当てられます。
書き込み対象が 64KB よりも大きければ、複数の新しいブロックがコンテナに対して割り当てられます。
- 新しく割り当てられたブロックにデータを書き込みます。
既存のデータを上書き¶
既存のデータに対して初めて変更を加える場合、
- アプリケーションはコンテナ上にあるデータの変更を要求します。
- 更新が必要なブロックに対して、コピー・オン・ライト処理が行われます。
- 処理によって新しい空のブロックがコンテナのスナップショットに割り当てられ、そのブロックにデータがコピーされます。
- 新しく割り当てられたブロックの中に、変更したデータを書き込みます。
コンテナ内のアプリケーションは、必要に応じた割り当てやコピー・オン・ライト処理を意識しません。しかしながら、アプリケーションの読み書き処理において、待ち時間を増やすでしょう。
Docker で devicemapper を使う設定¶
複数のディストリビューションにおいて、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 に送信しておきます。
以下の手順は論理データ・ボリュームと、ストレージ・プールの基礎として設定されたシン・プールを使います。ここでは別のブロック・デバイス /dev/xvdf
を持っており、処理するための十分な空き容量があると想定しています。デバイスの識別子とボリューム・サイズは皆さんの環境とは異なるかもしれません。手順を進める時は、自分の環境にあわせて適切に置き換えてください。また、手順は Docker デーモンが停止した状態から始めるのを想定しています。
- 設定対象の Docker ホストにログインし、Docker デーモンを停止します。
- LVM2 パッケージをインストールします。LVM2 パッケージにはユーザー向けのツールが含まれており、簡単に Linux 上で論理ボリュームを管理するものです。
- 物理ボリュームにブロック・デバイス
/dev/xvdf
を作成します。
$ pvcreate /dev/xvdf
docker
ボリューム・グループを作成します。
$ vgcreate docker /dev/xvdf
thinpool
という名前のシン・プール(thin pool)を作成します。
この例では、 docker
ボリューム・グループの論理データ(data logical)は 95% の大きさとします。残りの容量は、データもしくはメタデータによって空き容量が少なくなった時の一時的な退避用に使います。
$ lvcreate --wipesignatures y -n thinpool docker -l 95%VG
$ lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG
- プールをシン・プールに変換します。
$ lvconvert -y --zero n -c 512K --thinpool docker/thinpool --poolmetadata docker/thinpoolmeta
lvm
プロフィールを経由してシン・プールを自動拡張するよう設定します。
$ vi /etc/lvm/profile/docker-thinpool.profile
thin_pool_autoextend_threshold
値を指定します。
ここで指定する値は、先ほどの lvm
領域がどの程度まで到達したら、領域をどこまで自動拡張するかをパーセントで指定します(100 = 無効化です)。
thin_pool_autoextend_threshold = 80
- シン・プールの自動拡張が発生するタイミングを指定します。
シン・プールの領域を増やす空き容量のタイミングをパーセントで指定します(100 = 無効化です)。
thin_pool_autoextend_percent = 20
- 確認をします。
docker-thinpool.profile
は次のように表示されます。
/etc/lvm/profile/docker-thinpool.profile
ファイルの例:
activation {
thin_pool_autoextend_threshold=80
thin_pool_autoextend_percent=20
}
- 新しい lvm プロフィールを適用します。
$ lvchange --metadataprofile docker-thinpool docker/thinpool
lv
(論理ボリューム)をモニタしているのを確認します。
$ lvs -o+seg_monitor
- Docker Engine を起動していた場合は、グラフ・ドライバを直接削除します。
Docker インストール時のイメージとコンテナからグラフ・ドライバを削除します。
$ rm -rf /var/lib/docker/*
- Engine デーモンが devicemapper オプションを使うように設定します。
設定には2つの方法があります。デーモンの起動時にオプションを指定するには、次のようにします。
--storage-driver=devicemapper --storage-opt=dm.thinpooldev=/dev/mapper/docker-thinpool --storage-opt dm.use_deferred_removal=true
あるいは daemon.json
設定ファイルで起動時に指定も可能です。例:
{
"storage-driver": "devicemapper",
"storage-opts": [
"dm.thinpooldev=/dev/mapper/docker-thinpool",
"dm.use_deferred_removal=true"
]
}
- systemd を使っているのであれば、unit あるいはドロップイン・ファイルを経由してデーモン設定を変更するため、変更を読み取るため systemd を再読み込みします。
$ systemctl daemon-reload
- Docker Engine デーモンを起動します。
$ systemctl start docker
Docker Engine デーモンを起動したら、シン・プールとボリューム・グループの空き容量を確認します。ボリューム・グループは自動拡張しますので、容量を使い尽くす可能性があります。論理ボリュームを監視するには、オプションを指定せず lvs
を使うか、 lvs -a
でデータとメタデータの大きさを確認します。ボリューム・グループの空き容量を確認するには vgs
コマンドを使います。
先ほど設定したシン・プールの閾値を越えたかどうかを確認するには、次のようにログを表示します。
journalctl -fu dm-event.service
シン・プールで問題を繰り返す場合は、 dm.min_free_spaces
オプションで Engine の挙動を調整できます。この値は最小値に近づいた時、警告を出して操作させなくします。詳しい情報は ストレージ・ドライバのオプション をご覧ください。
ホスト上の devicemapper 構造の例¶
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 10G 0 disk
├─vg--docker-data 253:0 0 90G 0 lvm
│ └─docker-202:1-1032-pool 253:2 0 10G 0 dm
└─vg--docker-metadata 253:1 0 4G 0 lvm
└─docker-202:1-1032-pool 253:2 0 10G 0 dm
下図は、先ほどの例で扱ったイメージの更新を、 lsblk
コマンドの詳細とあわせて表しています。
この図では、プールは Docker-202:1-1032-pool
と名付けられ、先ほど作成した data
と metadata
デバイスにわたっています。この devicemapper
のプール名は、次のような形式です。
Docker-MAJ:MIN-INO-pool
MAJ
、 NIN
、 INO
は、デバイスのメジャー番号、マイナー番号、i ノード番号です。
Device Mapper はブロック・レベルで処理を行うため、イメージ・レイヤとコンテナ間の差分を見るのは、少し大変です。しかしながら、2つの鍵となるディレクトリがあります。 /var/lib/docker/devicemapper/mnt
ディレクトリには、イメージとコンテナのマウント・ポイントがあります。 /var/lib/docker/devicemapper/metadata
ディレクトリには、それぞれのイメージとコンテナのスナップショットを格納する1つのファイルがあります。このファイルには、各スナップショットのメタデータが JSON 形式で含みます。
実行中デバイスの容量を増やす¶
実行中のシン・プール・デバイスのプール容量を増加できます。データの論理ボリュームが一杯になる時やボリューム・グループの容量が一杯になる時に便利です。
loop-lvm 用の設定¶
このシナリオでは、シン・プールは loop-lvm
モードの設定とします。 docker info
を使うと現在の設定詳細を表示します。
$ sudo docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 2
Server Version: 1.11.0-rc2
Storage Driver: devicemapper
Pool Name: docker-8:1-123141-pool
Pool Blocksize: 65.54 kB
Base Device Size: 10.74 GB
Backing Filesystem: ext4
Data file: /dev/loop0
Metadata file: /dev/loop1
Data Space Used: 1.202 GB
Data Space Total: 107.4 GB
Data Space Available: 4.506 GB
Metadata Space Used: 1.729 MB
Metadata Space Total: 2.147 GB
Metadata Space Available: 2.146 GB
Udev Sync Supported: true
Deferred Removal Enabled: false
Deferred Deletion Enabled: false
Deferred Deleted Device Count: 0
Data loop file: /var/lib/docker/devicemapper/devicemapper/data
WARNING: Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.
Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
Library Version: 1.02.90 (2014-09-01)
Logging Driver: json-file
[...]
Data Space
(データ領域)の値は合計 100GB です。この例ではプールを 200GB に拡張します。
- デバイスの容量一覧を表示します。
$ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
total 1175492
-rw------- 1 root root 100G Mar 30 05:22 data
-rw------- 1 root root 2.0G Mar 31 11:17 metadata
data
ファイルをmetadata
ファイルの容量(約 200GB)に切り出します(truncate)。
$ sudo truncate -s 214748364800 /var/lib/docker/devicemapper/devicemapper/data
- 変更を確認します。
$ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
total 1.2G
-rw------- 1 root root 200G Apr 14 08:47 data
-rw------- 1 root root 2.0G Apr 19 13:27 metadata
- ループ・デバイスをのデータを再読み込みします。
$ sudo blockdev --getsize64 /dev/loop0
107374182400
$ sudo losetup -c /dev/loop0
$ sudo blockdev --getsize64 /dev/loop0
214748364800
- devicemapper シン・プールを再読み込みします。
- まずプール名を取得します。
$ sudo dmsetup status | grep pool
docker-8:1-123141-pool: 0 209715200 thin-pool 91
422/524288 18338/1638400 - rw discard_passdown queue_if_no_space -
名前はコロンの前の文字列です。
- そして、デバイス・マッパー・テーブルをダンプします。
$ sudo dmsetup table docker-8:1-123141-pool
0 209715200 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing
- シン・プールの現在の実合計セクタを計算します。
テーブル情報の2つめの数値(例: セクタ数)を変更するため、ディスク内で新しい 512 バイトのセクタを反映します。例えば、新しいプール容量が 200GB であれば、2つめの数値は 419430400 に変わります。
- 新しいセクタ番号でシン・プールを再読み込みします。
$ sudo dmsetup suspend docker-8:1-123141-pool \
&& sudo dmsetup reload docker-8:1-123141-pool --table '0 419430400 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing' \
&& sudo dmsetup resume docker-8:1-123141-pool
device_tool¶
Docker プロジェクトの contrib
ディレクトリにあるのは、ディストリビューションのコア(中心)ではありません。これらのツールは多くの場面で役立ちますが、古いものがあるかもしれません。ディレクトリ内にある device_tool.go で loop-lvm シン・プールの容量変更も可能です。
ツールを使うためには、まずコンパイルします。それからプール容量を次のように変更します:
$ ./device_tool resize 200GB
direct-lvm モード用の設定¶
この例では direct-lvm
設定を使って実行中デバイスの容量を拡張します。例では /dev/sdh1
ディスク・パーティションを使っているものと想定します。
- ボリューム・グループ(VG)
vg-docker
を拡張します。
$ sudo vgextend vg-docker /dev/sdh1
Volume group "vg-docker" successfully extended
皆さんは別のボリューム名を使っているかもしれません。
data
論理ボリューム(LV)vg-docker/data
を拡張します。
$ sudo lvextend -l+100%FREE -n vg-docker/data
Extending logical volume data to 200 GiB
Logical volume data successfully resized
- devicemapper シン・プールを再読み込みします。
- プール名を取得します。
$ sudo dmsetup status | grep pool
docker-253:17-1835016-pool: 0 96460800 thin-pool 51593 6270/1048576 701943/753600 - rw no_discard_passdown queue_if_no_space
名前はコロン前の文字列です。
- デバイス・マッパー・テーブルをダンプします。
$ sudo dmsetup table docker-253:17-1835016-pool
0 96460800 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing
- シン・プールの現在の実合計セクタを計算します。
blockdev
を使って data 論理ボリュームの実サイズを取得できます。
テーブル情報の2つめの数値(例: ディスク終了セクタ)を変更するため、ディスク内で新しい 512 バイトのセクタを反映します。例えば、新しい data 論理ボリューム容量が 264132100096 バイト であれば、2つめの数値は 515883008 に変わります。
$ sudo blockdev --getsize64 /dev/vg-docker/data
264132100096
- それから新しいセクタ番号でシン・プールを再読み込みします。
$ sudo dmsetup suspend docker-253:17-1835016-pool \
&& sudo dmsetup reload docker-253:17-1835016-pool --table '0 515883008 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing' \
&& sudo dmsetup resume docker-253:17-1835016-pool
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;薄いファイル)を使うため、性能を損ないます。そのため、loop-lvm
は プロダクションへのデプロイに推奨されていません 。プロダクション環境で推奨されるモードはdirect-lvm
です。これはストレージ・ドライバが直接 raw ブロック・デバイスに書き込みます。
- 高速なストレージ :ベストな性能を出すためには、
データ・ファイル
とメタデータ・ファイル
を、 SSD のような高速なストレージ上に配置すべきです。あるいは、 SAN や NAS アレイといった、ダイレクト・アタッチ・ストレージでも同様でしょう。
- メモリ使用量 : Docker ストレージ・ドライバの中で、メモリ使用効率が最も悪いのが
devicemapper
です。同じコンテナのコピーをn個起動する時、n個のファイルをメモリ上にコピーします。これは、Docker ホスト上のメモリに対して影響があります。そのため、 PaaS や他の高密度な用途には、devicemapper
ストレージ・ドライバがベストな選択肢とは言えません。
最後に1点、データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを行うのに適しています。
関連情報¶
参考
- Docker and the Device Mapper storage driver
- https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/