Dockerのボリュームプラグインとストレージドライバ(Dockerの最新機能を使ってみよう:第2回)

新たなサーバー環境構築ツールとして普及が始まっているDockerは、その開発も積極的に行われている。そこで本連載記事では、4回に渡って最近Dockerに実装された新機能について紹介していく。今回は、Dockerのボリュームプラグインとストレージドライバについて紹介する。

Dockerの「ボリュームプラグイン」と「ストレージドライバ」

昨今のDockerでは、各機能を個別のコンポーネントに分離する方向で開発が進められている。ストレージ関連の処理もすでに分離されており、「ボリュームプラグイン」やストレージドライバ」を使って目的や環境に応じた設定を行うことが可能になっている。この2つは名前が似通っているため混乱しやすいが、まったく別のものだ(図1)。

図1 ボリュームプラグインとストレージドライバ
図1 ボリュームプラグインとストレージドライバ

 まずボリュームプラグインだが、これはコンテナ外のストレージをコンテナ内に「ボリューム」としてマウントするためのプラグイン機構だ。Dockerではコンテナを削除するとコンテナ内のファイルシステム内に加えた変更点はすべて破棄されてしまうが、ボリュームを利用することで、簡単にコンテナ内からコンテナ外のストレージにファイルを保存し、データを永続化できる。当初はDockerホスト上のディレクトリをコンテナ内にマウントする機能のみが提供されていたが、その後Docker 1.8ではプラグイン機構が導入され、さまざまなストレージデバイスをコンテナにマウントできるようになっている。

いっぽうのストレージドライバは、Dockerがイメージやコンテナを管理する際に使用するものだ。Dockerではイメージやコンテナ内のファイルシステムに加えられた変更を差分形式で保持することで、効率良くストレージを利用できるよう設計されている。当初Dockerはこれを実現するためにaufsのような特別なファイルシステムを使用していたのだが、バージョン0.7でストレージドライバ機構が導入され、Device Mapperやvfsといった機構を利用したイメージの管理が可能となった。その後、OverlayFSやBtrfs、ZFSといった差分管理機構を持つファイルシステムを活用するドライバが追加されている。

以下ではこのボリュームプラグインおよびストレージドライバについて、実際の利用例と設定方法を紹介していく。なお、特に言及のない限り利用した検証環境はFedora 23で、Dockerのバージョンは1.10.3だ。

コンテナ内にコンテナ外のストレージをマウントするための「ボリューム」機能

前述の通り、2015年6月にリリースされたDocker 1.7ではボリュームプラグイン機構が利用できるようになった。それまではDockerを実行するホスト(Dockerホスト)上のディレクトリのみがコンテナ内にマウントできたが、プラグインを利用することでネットワーク上にあるストレージなどをコンテナに直接マウントできるようになる。以下ではまず基本となるDockerのボリューム機構について説明し、続いてプラグインの利用方法について紹介する。

Dockerホストのディレクトリをコンテナ内にマウントする

Dockerでは、「docker run」コマンドでコンテナを起動する際に「-v <ホスト上ディレクトリ>:<マウント先>」オプションを利用することで、Dockerホスト上の指定したディレクトリをコンテナ内の指定したディレクトリにマウントできる。また、「-v <ホスト上ディレクトリ>:<マウント先>:ro」のようにマウント先指定のあとに「:ro」を付けることで、リードオンリーでマウントを行うことも可能だ。

たとえば次の例では、Dockerホスト上の/var/testdataディレクトリを、コンテナ内の/dataディレクトリにマウントしている。

# docker run -ti -v /var/testdata:/data busybox

このようにしてマウントしたディレクトリ内に永続化したいファイルを保存することで、コンテナを削除してもそのデータを残しておけるようになる。

「-v」オプションでは、次のようにDockerホスト上のディレクトリ指定を省略することも可能だ。

# docker run -ti -v /data --name test01 busybox

この場合、Dockerはvar/lib/docker/volumesディレクトリ以下にランダムな名前を持つディレクトリを作成し、そのディレクトリがコンテナにマウントされる。コンテナにマウントされているボリュームは「docker inspect」コマンドで確認できる。

たとえば先のように起動したコンテナの詳細を確認すると、以下のように「"Mounts":」以下で「e0553ba36bf2c33b83fada68610e5103b6596c9e7dcc17622c277e180cd7f317」という名前のボリュームがマウントされており、このボリュームはDockerホストの/var/lib/docker/volumes/e0553ba36bf2c33b83fada68610e5103b6596c9e7dcc17622c277e180cd7f317/_dataディレクトリに対応していることが分かる。

# docker inspect test01
  
  
        "Mounts": [
            {
                "Name": "e0553ba36bf2c33b83fada68610e5103b6596c9e7dcc17622c277e180cd7f317",
                "Source": "/var/lib/docker/volumes/e0553ba36bf2c33b83fada68610e5103b6596c9e7dcc17622c277e180cd7f317/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
  
  

なお、この例はDocker 1.10.3でのものだ。Docker 1.7系以前では次のように「Mounts」ではなく「Volumes」という項目でマウントされているボリュームの情報が表示される。

    "Volumes": {
        "/data": "/var/lib/docker/vfs/dir/b1adacb83f130cdd2fffbbec8e77c70c23685c5278cc34eba12ed05be81ff669"
    },
    "VolumesRW": {
        "/data": true
    }

このようにして作成されたボリュームは、前述のとおりコンテナを削除しても削除されない。ボリュームをコンテナと一緒に削除したい場合、コンテナを削除する「docker rm」コマンドを「-v」オプション付きで実行すれば良い。

# docker rm -v <コンテナ名>

あるコンテナがマウントしているボリュームを、ほかのコンテナにマウントさせることも可能だ。コンテナの作成時、「--volumes-from <コンテナ名>」オプションを指定することで、指定したコンテナにマウントされているすべてのボリュームを新たに作成するコンテナにマウントできる。

次の例は「test02」というコンテナを作成するとともに、先ほど作成した「test01」というコンテナにマウントされているボリュームをtest02コンテナにもマウントさせるものだ。

# docker run -ti --volumes-from test01 --name test02 busybox

test01では/dataディレクトリにボリュームがマウントされており、このボリュームがtest02でも同じ/dataディレクトリにマウントされる。この場合、/dataディレクトリは両方のコンテナで共有され、test01コンテナで/dataディレクトリに対し変更を行うと、それがtest02コンテナの/dataディレクトリにも反映される(逆も同様)。

Docker 1.8以降のボリューム関連の新機能

Docker 1.7ではボリューム関連機能のコードが一新され、プラグインによる拡張が可能となった。さらにDocker 1.8以降ではこれらの機能が標準で利用可能となり、多くの機能強化が追加されている。

まず、Docker 1.8では「-v <ボリューム名>:<マウント先>」のように指定することで、作成するボリュームに名前を付けることが可能となった。次の例は、「testvolume01」という名前のボリュームを作成してコンテナにマウントするものだ。

# docker run -t -i -v testvolume01:/data --name test03 busybox

なお、ボリューム名として「/」で始まる文字列を与えてしまうと、Dockerホスト上のディレクトリを指定してマウントすることになってしまうので注意したい。

作成したボリュームは、その名前を指定して別のコンテナにマウントさせることができる。この場合、--volumes-fromオプションとは異なり、最初にボリュームを作成したコンテナでのマウント先とは異なるマウント先を指定することも可能だ。

たとえば新たに「test04」というコンテナを作成し、その/data2ディレクトリに先ほど作成したtestvolume01ボリュームをマウントしたい場合、次のように指定する。

# docker run -t -i -v testvolume01:/data2 --name test04 busybox

また、Docker 1.9以降では「docker volume」コマンドが導入され、こちらでボリュームの管理が可能となった。たとえば、「docker volume ls」コマンドでボリューム一覧を確認できる。

# docker volume ls
DRIVER              VOLUME NAME
local               testvolume01

ボリュームの詳細については「docker volume inspect」コマンドで確認できる。

# docker volume inspect testvolume01
[
    {
        "Name": "testvolume01",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/testvolume01/_data"
    }
]

ボリュームを作成する「docker volume create」コマンドも用意されている。このとき、「--name」オプションで作成するボリュームの名前を指定できる。

# docker volume create --name testvolume02
testvolume02

名前を指定しない場合、ランダムな文字列が名前として使用される。

# docker volume create
1a7a1bd3f4a7e359a685cb5a90b4d47849da631ccf94640f5d740c69fae9d75b

作成したボリュームは次のように「docker run」コマンドの実行時に「-v」オプションを使ってコンテナにマウントできる。

# docker run -d -i -v testvolume02:/data --name test05 busybox

ボリュームの削除は「docker volume rm」コマンドで行える。

# docker volume rm testvolume01
testvolume01

ちなみにDocker 1.8以前ではボリュームを個別に指定して削除するインターフェイスは存在せず、手動で/var/lib/docker/volumesディレクトリ以下のディレクトリを削除する必要があった。また、Docker 1.10以降では「docker rm -v」コマンドでコンテナと一緒にボリュームを削除しようとした場合でも、名前が付けられたボリュームについては削除されないよう仕様変更が行われている。

ローカル以外のストレージをボリュームとして利用する

Docker 1.8で利用可能になったボリュームプラグイン機能を利用することで、Dockerホスト以外のストレージをボリュームとして利用できるようになった。誰もが独自のプラグインを開発できるよう仕様も公開されており、すでにいくつかのプラグインがサードパーティによって開発されている(Docker公式ドキュメントの「Understand Engine plugins」ページ)。

今回はこのうち、NFSやWindowsファイル共有(Samba/CIFS)、Amazon EFSを使ってボリュームを作成できる「docker-volume-netshare」(以下、netshareプラグイン)を紹介する。

netshareプラグインを利用するには、まずプラグインのインストールが必要となる。ソースコードからビルドしても良いが、今回はリリースページで公開されているバイナリを利用する。今回はコンパイル済みバイナリ(docker-volume-netshare_0.16_linux_amd64.tar.gz)を利用した。

ダウンロードしたアーカイブファイルにはドキュメントとともに「docker-volume-netshare」というバイナリファイルが含まれているので、これを/usr/local/binディレクトリ以下にコピーする。

# tar xvzf ../docker-volume-netshare_0.16_linux_amd64.tar.gz
# cd docker-volume-netshare_0.16_linux_amd64/
# cp docker-volume-netshare /usr/local/bin/

また、NFSで共有されているディレクトリをボリュームとして利用する際は、nfsを利用するためのパッケージを別途インストールしておく必要がある。Red Hat Enterprise LinuxやCentOS、Fedora環境であれば、「nfs-utils」パッケージを、UbuntuやDebian環境では「nfs-common」パッケージをインストールしておけば良い。

netshareプラグインを利用する際は、使用するコンテナの起動前にこのdocker-volume-netshareバイナリを実行しておく必要がある。実行時に指定する引数についてはREADMEファイルを参照して欲しいが、NFSを利用する場合は以下のように「nfs」を引数として指定する。

# /usr/local/bin/docker-volume-netshare nfs &

netshareプラグインを使ってNFS共有されているディレクトリ上にボリュームを作成するには、「docker volume create」コマンドを利用する。ここで、「-d nfs」オプションでnfsドライバを利用するよう指示し、「--name」オプションに「<NFSサーバーのホスト名>/<パス>」を指定することで、NFS共有されているディレクトリをボリュームとして扱える。次の例は、「example.com」というホスト上の「/var/nfs_exports」というディレクトリを使用するものだ。

# docker volume create -d nfs --name example.com/var/nfs_exports
example.com/var/nfs_exports

作成したボリュームは、以下のように「-v <コンテナ名>:<マウント先ディレクトリ>」オプションを指定することでコンテナにマウントできる。

docker run -ti -v example.com/var/nfs_exports:/data --name test01 busybox

なお、今回はdocker-volume-netshareバイナリを直接実行しているが、運用環境などで利用する場合はsystemd等でサービスとして起動するように設定しておくほうが良いだろう。そのための設定ファイルとして、systemd設定ファイルおよびそこで使用する設定ファイルが公開されている。また、Debian/Ubuntu向けにはdebパッケージが提供されているので、そちらを利用すれば良いだろう。

Dockerが使用するストレージへのアクセスを提供する「ストレージドライバ」

続いて、ストレージドライバについて説明していこう。前述のとおり、ストレージドライバはDockerがイメージを管理するために使用する機構だ。Docker 1.10では、表1の6つのストレージドライバが提供されている。

表1 Docker 1.10で利用できるストレージドライバ
ドライバ名 使用するストレージ
overlay OverlayFS
aufs AUFS
btrfs Btrfs
devicemapper Device Mapper
vfs VFS(イメージの差分管理を行わない)
zfs ZFS

1つのホスト上では1つのドライバしか利用できない。つまり、コンテナ毎に異なるストレージドライバを使うことはできない。現在使用されているドライバは「docker info」コマンドで確認できる。下記の例ではdevicemapperドライバが使われていることが分かる。

# docker info
  
  
Storage Driver: devicemapper
 Pool Name: docker-253:0-223022-pool
 Pool Blocksize: 65.54 kB
 Base Device Size: 10.74 GB
 Backing Filesystem: xfs
 Data file: /dev/loop0
 Metadata file: /dev/loop1
 Data Space Used: 1.086 GB
 Data Space Total: 107.4 GB
 Data Space Available: 14.11 GB
 Metadata Space Used: 2.843 MB
 Metadata Space Total: 2.147 GB
 Metadata Space Available: 2.145 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.109 (2015-09-22)
Execution Driver: native-0.2
  
  

btrfsやzfsはそれぞれbtrfs、ZFS専用で、他のファイルシステムでは利用できない。逆にoverlayはbtrfsやAUFS、ZFS上、aufsはbtrfsおよびAUFS上では利用できないという制約がある。さらに、AUFSやZFSはLinuxカーネルでは標準サポートされておらず、利用のためには別途カーネルモジュールなどを用意する必要がある。Ubuntuなど一部のディストリビューションではこれらモジュールが標準で利用できるが、そうでないディストリビューションもあるため注意が必要だ。たとえばFedoraやRed Hat Enterprise Linux(RHEL)、CentOSではAUFSについて標準ではサポートされていない。

使用するドライバはdocker daemon実行時の「--storage-driver=<ドライバ名>」引数で指定できる。また、ディストリビューションによっては別途特別な設定ツールが用意されている場合がある。たとえばRed Hat Enterprise Linux/CentOS/Fedoraでは「docker-storage-setup」というツールが用意されており、devicemapperおよびoverlayドライバを利用するための設定を自動的に行えるようになっている。

なお、Docker公式ドキュメントの「Select a storage driver」ページによると、「production-ready」(実運用環境で利用可能な段階)となっているのはaufsとdevicemapperのみとされている。overlayドライバおよびzfs(ZFS FUSE経由での利用)については「stable」(安定)となっているが、まだテスト・検証向けという段階のようだ(表2)。

表2 各ストレージドライバの特徴
ドライバ名 Linuxカーネルでの標準サポート 実運用環境での利用 安定度 適した用途 適さない用途
aufs なし 安定 PaaS等での利用 書き込み頻度の高いもの
devicemapper(loopback利用) あり × 安定 研究/テスト パフォーマンスが要求されるもの
devicemapper(LVM利用) あり 安定 - PaaS等での利用
brtfs あり × × ビルドプール 書き込み頻度が高いもの、コンテナの頻繁な操作
overlay あり × 安定 研究/テスト コンテナの頻繁な操作
zfs(ZFS on Linux利用) なし × × PaaS等での利用
zfs(FUSE利用) なし × 安定 研究/テスト

overlayドライバを利用してみる

前述のように、現時点で実運用環境向けに推奨されているドライバはaufsおよびdevicemapperのみだ。そのためUbuntuではaufsが、Red Hat Enterprise LinuxやCentOS、Fedoraではdevicemapperがデフォルトのストレージドライバになっている。いっぽう、よりパフォーマンスの良いドライバとして注目されているのがoverlayドライバだ。ovarlayドライバはほかのドライバと比較してコンテナの起動/停止時のパフォーマンスが優れていると言われている。overlayドライバは実運用環境向けではないというステータスだが、テスト環境などでの利用においては十分安定しているようだ。そこで、以下ではこのoverlayドライバを利用するための設定を紹介する。

まずRed Hat Enterprise Linux/CentOS/Fedora環境のデフォルト設定ではストレージドライバとしてdevicemapperが利用されるが、overlayドライバも標準でサポートされており、簡単な設定で利用が可能だ。まず、/etc/sysconfig/docker-storage-setupファイル内に下記の1行を追加する。

STORAGE_DRIVER=overlay

続いて「docker-storage-setup」コマンドを実行すると、各種設定が自動的に変更される。

# docker-storage-setup

また、現時点ではoverlayドライバはSELinuxが有効だと正しく動作しない。SELinuxを「Permissive」設定にする(「setenfoce」コマンドで0に設定する)のではなく完全に無効にする必要があるため、/etc/sysconfig/selinuxファイル内の「SELINUX=」部分を次のように修正して再起動しておく。

SELINUX=disabled

以上が完了した状態でdockerサービスを起動すると、overlayドライバが利用されるようになる。

また、Ubuntuでは/etc/default/dockerファイル内の「DOCKER_OPTS」変数でDockerデーモンに与えるオプションを指定できるので、ここで下記のように「--storage-driver=」オプションを追加すれば良い。

DOCKER_OPTS="--storage-driver=overlay"

続いてDockerサービスを再起動すると、overlayドライバが利用されるようになる。

systemctl restart docker.service

どちらも場合も、Dockerの再起動後は「docker info」コマンドでoverlayドライバが利用されているかを確認しておこう。

# docker info
  
  
Storage Driver: overlay
 Backing Filesystem: extfs
  
  

着実に進化したDockerのストレージ関連機構

かつてのDockerにおいてはボリュームの管理方法等がやや不明確であったが、ボリュームプラグインの導入と「docker volume」コマンドの導入によって管理が容易になっている。また、複数のDockerホストを組み合わせた環境においては、プラグインを利用してネットワーク経由でアクセスできるストレージをボリュームとして利用することで、Dockerのみで簡単に異なるDockerホスト上にあるコンテナ間でボリュームを共有できるようになっている。これによって、より容易に複数のDockerホストを組み合わせたクラスタ環境を構築できるようになる。

またストレージドライバについては、一部はまだ開発途上の技術ではあるものの、overlayドライバを実際に利用してみると、確かにコンテナの起動や削除などが高速になったように感じられた。まだ実運用環境にはおすすめできないというステータスではあるが、テスト環境等、万が一トラブルが発生したとしても影響が少ない用途では試してみる価値はあるだろう。