ZFS ストレージの使用

ZFS は次世代のファイルシステムです。ボリューム管理、スナップショット、チェックサム処理、圧縮、重複除外(deduplication)、レプリケーションなどの高度なストレージ技術をサポートしています。

ZFS はサン・マイクロシステムズ(現オラクル・コーポレーション)によって開発され、CDDL ライセンスの下でオープンソース化されています。CDDL と GPL 間でライセンスの互換性がないため、ZFS は Linux カーネル・モジュールのメインラインに取り込まれません。しかし、ZFS On Linux (ZoL)プロジェクトにより、外部のカーネル・モジュールとユーザ用のツールを別々にインストールできるように提供されています。

ZFS on Linux (ZoL) への移植は正常かつ成熟しています。しかし ZFS on Linux に対する十分な経験がないのであれば、現時点では zfs Docker ストレージ・ドライバをプロダクションで使うことを推奨しません。

注釈

Linux プラットフォーム上では、FUSE で ZFS を実装する方法もあります。Docker とも動作するでしょうが、推奨されません。ネイティブな ZFS ドライバ(ZoL)の方がよりテストされ、性能も良く、より幅広く使われています。このドキュメントでは、ネイティブな ZoL 移植版について言及してるのでご注意ください。

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

Docker zfs ストレージ・ドライバは、3種類の豊富な ZFS データセットを使えます。

  • ファイルシステム(filesystems)
  • スナップショット(snapshots)
  • クローン(clones)

ZFS ファイルシステムはシン・プロビジョニング(訳者注:データ書き込みの領域が、初期環境では薄く構築される)であり、ZFS プール(zpool)から要求処理があるごとに、領域を割り当てます。スナップショットとクローンは、その時々で ZFS ファイルシステムのコピーをするため、領域を効率的に使います。スナップショットは読み込み専用です。クローンは読み書きできます。クローンはスナップショットからのみ作成可能です。以下の図は、これらの関係性を簡単にしたものです。

ZFS のクローン

図の実線はクローン作成手順の流れです。手順1はファイルシステムのスナップショットを作成します。手順2はスナップショットからクローンを作成します。図の点線はクローンとスナップショットの関係であり、スナップショットを経由しています。

Docker ホストで zfs ストレージ・ドライバを使えば、イメージのベース・レイヤは ZFS ファイルシステムになります。それぞれの子レイヤとは、下にあるレイヤの ZFS スナップショットをベースとした ZFS クローンです。コンテナとは、作成するにあたってイメージの最上位レイヤの ZFS スナップショットをベースとした ZFS クローンです。全ての ZFS データセットは、共通の zpool から領域を取り込みます。以下の図は2つのレイヤ・イメージをベースにまとめたもので、コンテナを実行しています。

ZFS pool

以下で説明する手順は、どのようにイメージをレイヤ化し、コンテナを作成するかです。手順は先ほどの図を元にしています。

  1. イメージのベース・レイヤは Docker ホスト上に ZFS ファイルシステムとして存在しています。

ファイルシステムが要領を使うのは、Docker ホスト上のローカル・ストレージ領域 /var/lib/docker に zpool を作成する時です。。

  1. 追加のイメージ・レイヤは、直下にあるイメージ・レイヤが保存されているデータセットのクローンにあたります。

図において、「レイヤ1」にはベース・レイヤの ZFS スナップショットによって追加されたものです。そして、スナップショットからクローンを作成します。クローンは書き込み可能であり、必要があれば zpool の容量を使います。スナップショットは読み込み専用であり、ベース・レイヤが変更できない(イミュータブルな)ものとして管理されます。

  1. コンテナが起動したら、そのイメージ上に読み書き可能なレイヤが追加されます。

先ほどの図では、コンテナの読み書きレイヤは、イメージ(レイヤ1)の最上位レイヤのスナップショットの上に作成されます。そして、スナップショットからクローンが作成されます。

コンテナに対して変更が発生したら、オンデマンドの割り当て(allocate-on-demand)処理によって zpool から領域が割り当てられます。デフォルトでは、ZFS が割り当てる領域は 128KB のブロックです。

子レイヤの作成と、読み込み専用のスナップショットからコンテナを作成する手順において、イメージを不変な(イミュータブルな)オブジェクトとして扱えます。

コンテナを ZFS で読み書き

コンテナが zfs ストレージ・ドライバから読み込むのは、非常にシンプルです。直近で起動したコンテナは、ZFS クローンを元にしています。このクローンは作成時、まず全てのデータセットを共有します。つまり、 zfs ストレージ・ドライバの読み込み処理が高速なことを意味します。これは、読み込み対象のデータがコンテナ内にコピーされていなくてもです。データブロックの共有は、次のような図になります。

ZFS zpool ブロック

コンテナに対する新規データの書き込みは、オンデマンドの割り当て処理によって完了します。コンテナが書き込みのために新しい領域が必要であれば、その都度、新しいブロックが zpool から割り当てられます。つまりコンテナに対する書き込みによって、新しいデータ用の領域が追加される時のみ、容量を消費します。新しい領域は、根底にある zpool からコンテナ(ZFS クローン)に対して割り当てられるものです。

コンテナ内に 存在するデータ に対する更新は、コンテナのクローンに新しいブロックを割り当て、変更したデータを新しいブロックに保管したら、処理が完了となります。オリジナルのデータに対する変更は行われません。元になったイメージのデータセットは、変わらない(イミュータブルな)ままです。つまり、通常の ZFS ファイルシステムに対する書き込みと同じであり、そこにコピー・オンライトの仕組みが実装されています。

Docker で ZFS ストレージ・ドライバを使う設定

zfs ストレージ・ドライバがサポートされるのは、Docker ホスト上で /var/lib/docker が ZFS ファイルシステムでマウントされている場合のみです。このセクションでは、Ubuntu 14.04 システム上に、ネイティブな ZFS on Linux (ZoL) のインストールと設定方法を紹介します。

動作条件

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

まず、Docker デーモンを停止します。それから別のブロックデバイス /dev/xvdb があることを確認します。このデバイス識別子は皆さんの環境によって異なるかもしれません。そのような場合は、以降の処理で適切なものに置き換えてください。

Ubuntu 14.04 LTS に Zfs をインストール

  1. Docker daemon を実行中であれば、停止します。
  1. software-properties-common パッケージをインストールします。

この時 apt-get-repository コマンドが必要です。

$ sudo apt-get install -y software-properties-common
Reading package lists... Done
Building dependency tree
<出力を省略>
  1. zfs-native パッケージ・アーカイブを追加します。
$ sudo add-apt-repository ppa:zfs-native/stable
 The native ZFS filesystem for Linux. Install the ubuntu-zfs package.
<出力を省略>
gpg: key F6B0FC61: public key "Launchpad PPA for Native ZFS for Linux" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
OK
  1. 全ての登録リポジトリとパッケージ・アーカイブから、最新のパッケージ一覧を取得します。
$ sudo apt-get update
Ign http://us-west-2.ec2.archive.ubuntu.com trusty InRelease
Get:1 http://us-west-2.ec2.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
<output truncated>
Fetched 10.3 MB in 4s (2,370 kB/s)
Reading package lists... Done
  1. ubuntu-zfs パッケージをインストールします。
$ sudo apt-get install -y ubuntu-zfs
Reading package lists... Done
Building dependency tree
<出力を省略>
  1. zfs モジュールを読み込みます。
$ sudo modprobe zfs
  1. 正常に読み込まれていることを確認します。
$ lsmod | grep zfs
zfs                  2768247  0
zunicode              331170  1 zfs
zcommon                55411  1 zfs
znvpair                89086  2 zfs,zcommon
spl                    96378  3 zfs,zcommon,znvpair
zavl                   15236  1 zfs

ZFS を Docker に設定

ZFS をインストールして読み込んだので、Docker で ZFS 設定をする準備が整いました。

  1. 新しい zpool を作成します。
$ sudo zpool create -f zpool-docker /dev/xvdb

このコマンドは zpool を作成し、そこに「zpool-docker」という名前を割り当てています。この名前は任意です。

  1. zpool が存在しているかどうか確認します。
$ sudo zfs list
NAME            USED  AVAIL    REFER  MOUNTPOINT
zpool-docker    55K   3.84G    19K    /zpool-docker
  1. /var/lib/docker に新しい ZFS ファイルシステムを作成・マウントします。
$ sudo zfs create -o mountpoint=/var/lib/docker zpool-docker/docker
  1. 直前の手順が正常に行われたか確認します。
$ sudo zfs list -t all
NAME                 USED  AVAIL  REFER  MOUNTPOINT
zpool-docker         93.5K  3.84G    19K  /zpool-docker
zpool-docker/docker  19K    3.84G    19K  /var/lib/docker

これで ZFS ファイルシステムを /var/lib/docker にマウントしました。デーモンは自動的に zfs ストレージを読み込むでしょう。

  1. Docker デーモンを起動します。
$ sudo service docker start
docker start/running, process 2315

使用している Linux ディストリビューションによっては、この Docker デーモンの開始手順は少し異なる場合があります。Docker デーモンに対して zfs ストレージ・ドライバの利用を明示する場合は、 docker daemon コマンドで --storage-driver=zfs フラグを使うか、Docker 設定ファイル中の DOCKER_OPTS 行を編集します。

  1. デーモンが zfs ストレージ・ドライバを使っているのを確認します。
$ sudo docker info
Containers: 0
Images: 0
Storage Driver: zfs
 Zpool: zpool-docker
 Zpool Health: ONLINE
 Parent Dataset: zpool-docker/docker
 Space Used By Parent: 27648
 Space Available: 4128139776
 Parent Quota: no
 Compression: off
Execution Driver: native-0.2
[...]

先ほどの出力は、Docker デーモンが zfs ストレージ・ドライバを使っており、親データセットは先ほど作成した zpool-docker/docker ファイルシステムだと分かります。

これで Docker ホストは、イメージとコンテナの管理・保管に ZFS を使います。

ZFS と Docker 性能

Docker で zfs ストレージ・ドライバを使うにあたり、パフォーマンスに影響を与えるいくつかの要素があります。

  • メモリ 。ZFS の性能に、メモリはとても大きな影響があります。そもそもの事実として、本来の ZFS は、大きな Sun Solaris サーバ上で大容量のメモリを使うよう設計されていました。Docker ホストのサイジング時には、この点を忘れないでください。
  • ZFS の機能 。ZFS の機能、例えば重複削除(deduplication)は ZFS が使うメモリ容量が明らかに増加します。メモリの消費と性能面から、ZFS 重複削除の機能を無効にすることを推奨します。しかし、別のスタック上(SAN や NAS アレイ)のレイヤに対する重複削除は、ZFS のメモリ使用や性能に関する影響がありませんので、利用できるでしょう。もし SAN、NAS、その他のハードウェア RAID 技術を使うのであれば、ZFS の利用にあたり、以下にある既知のベストプラクティスをご利用ください。
  • ZFS キャッシュ 。ZFS はディスク・ブロックを、アダプティブ・リプレースメント・キャッシュ(ARC; adaptive replacement cache)と呼ばれるメモリ上の構造にキャッシュします。ZFS の Single Copy ARC 機能により、ブロックをコピーした単一キャッシュが、ファイルシステムの複数のクローンから共有されます。つまり、複数の実行中のコンテナは、キャッシュされたブロックのコピーを共有できるのです。これが意味するのは、ZFS は PaaS や他の高密度の利用例にとっては良い選択肢となるでしょう。
  • 断片化 。断片化は ZFS のようなコピー・オン・ライトなファイルシステムにおける、自然な副産物です。ZFS は 128KB のブロックに書き込みますが、slabs(複数の 128KB ブロック)をコピー・オン・ライト処理に割り当てますので、断片化を減らそうとしています。また、 ZFS intent log (ZIL) と書き込みの一体化も断片化を減らすものです。
  • ネイティブな Linux 用 ZFS ドライバの使用 。Docker zfs ストレージ・ドライバは ZFS FUSE 実装をサポートしているとはいえ、高い性能が必要な場合は推奨されません。ネイティブな Linux 用 ZFS ドライバは、FUSE 実装よりも良い性能でしょう。

以下の一般的な性能に関するベストプラクティスは、ZFS でも適用できます。

  • SSD 。ベストな性能のために、SSD(ソリッド・ステート・デバイス)のような高速なストレージ・メディアを使うのは常に良い考えです。十分に利用可能な SSD ストレージ容量があるのなら、ZIL を SSD 上に置くことを推奨します。
  • データ・ボリュームの使用 。データ・ボリュームは最上かつ最も予測可能な性能を提供します。これは、ストレージ・ドライバを迂回し、シン・プロビジョニングやコピー・オン・ライト処理を行わないためです。そのため、データ・ボリューム上で重たい書き込みを場合に使うべきでしょう。