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
のアーティファクト(成果物)であり、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 よりも大きければ、複数の新しいブロックがコンテナに対して割り当てられます。
- 新しく割り当てられたブロックにデータを書き込みます。
既存のデータを上書き¶
既存のデータに対して初めて変更を加える場合、
- アプリケーションはコンテナ上にあるデータの変更を要求します。
- 更新が必要なブロックに対して、コピー・オン・ライト処理が行われます。
- 処理によって新しい空のブロックがコンテナのスナップショットに割り当てられ、そのブロックにデータがコピーされます。
- 新しく割り当てられたブロックの中に、変更したデータを書き込みます。
コンテナ内のアプリケーションは、必要に応じた割り当てやコピー・オン・ライト処理を意識しません。しかしながら、アプリケーションの読み書き処理において、待ち時間を増やすでしょう。
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
(停止)した状態から始めることを想定しています。
- Docker ホストにログインし、設定対象の Docker デーモンを停止します。
- 終了したら、
/var/lib/docker
ディレクトリに保管されている既存のイメージを削除します。
$ sudo rm -rf /var/lib/docker
- もう1つのブロックデバイス上で
pvcreate
コマンドを使い、 LVM 物理ボリューム(PV; Physical Volume)を作成します。
$ sudo pvcreate /dev/xvdf
Physical volume `/dev/xvdf` successfully created
このデバイス識別子は、皆さんの環境によって異なります。このコマンドを実行する時は、適切な値に書き換えてください。
- 先の手順で作成した物理ボリュームを使い、
vg-docker
という名称の新しいボリューム・グループ(VG; Volume Group)を作成します。
$ sudo vgcreate vg-docker /dev/xvdf
Volume group `vg-docker` successfully created
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 によって既に使われているか、あるいは過去に使われていたかです。
vg-docker
ボリューム・グループ上の領域に、metadata
と呼ばれる新しい論議ボリューム(LV)を作成します。
$ sudo lvcreate -L 4G -n metadata vg-docker
Logical volume `metadata` created.
これは metadata
という名称の LVM 論理ボリュームを作成し、 /dev/vg-docker/metadata
にあるブロック・デバイス・ファイルに関連づけられます。次のステップで、 devicemapper
ストレージ・ドライバがこのブロックデバイスを使い、イメージやコンテナのデータを保管するように指示します。
- Docker デーモンが
devicemapper
ストレージ・ドライバを使って起動するため、--storage-opt
フラグを使います。
先ほどの手順で作成した data
と metadata
デバイスを --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 の設定ファイルか、デーモンの起動に使う service
や systemd
コマンドでも指定できます。
docker info
コマンドを使い、デーモンが先ほど作成したdata
とmetadata
デバイスが使われていることを確認します。
$ 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行から、適切なデバイスが Datafile
と Metadata file
を使っていることも分かります。
ホスト上の 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
は、デバイスのメジャー番号、マイナー番号、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点、データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを行うのに適しています。
関連情報¶
参考
- Docker and the Device Mapper storage driver
- https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/