VagrantとDocker: Mac OS X上でPostgres、Elasticsearch、Redisをセットアップする方法

しばらくDockerの動向をチラチラとうかがっていたのですが、Dockerがどれだけすばらしいものであるかいうことを、みんなが話しているのを耳にしました。さらに、今どきの若者たちはもうすでにこれを使っているのだということも知りました。そこで私は、試しにDockerを自分の開発環境で動かしてみることにしました。ここでは、Mac OS XでVagrantを使い、DockerコンテナとしてPostgresElasticsearchRedisをセットアップする方法を説明します。

Dockerとは?

Dockerは内部のオペレーティングシステムからアプリケーションを切り離すために、軽量のコンテナを使用します。そしてアプリケーションを使用するために必要な特定のフォルダや、ポートのみをアクセス可能にする隔離ボックスにアプリケーションを入れます。

そうすることによって、コンテナは再利用や共有が可能になり、アプリケーションのセットアップと使用方法に関する知識ベースにもなるのです。Docker Hubには、すでに15,000もの使用可能なコンテナが存在します。Dockerはショッピングカートのようなもので、アプリケーションを構築するために必要とするサービスを手に取って選んだら、あとはダウンロードして起動させるだけです。

OS XにDockerをセットアップする

DockerはOS Xでネイティブに作動するものではないので、LinuxカーネルとLXC(LinuXコンテナ)が必要です。もし私と同じようにOS Xをお使いの場合は、仮想化しなくてはいけません。

boot2dockerは使わない

Dockerを起動させようとしている途中で、ここで説明されている“簡単な”インストールを見つけました。これはboot2dockerと呼ばれるツールを使います。VirtualBoxのような仮想マシン上の薄いラッパのことです。

私はすぐに、このツールにはいくつか重要な問題点があることに気付きました。例えばこのように、安定状態でDockerを使おうとすると、あらゆる進行が停止するのです。バーチャルウォールにこれ以上頭をぶつけるのはイヤで、他のソリューションを探すことにしました。

Vagrantを使用する

そしてついに、Dockerのサポートが組み込まれているVagrant 1.6を発見したのです。Vagrantは、VirtualBoxのような仮想化ソフトウェアを包む薄いラッパです。必要とする環境を記述するために宣言型であるRubyのDSLを使います。

私はこの仮想環境を定義する方法が好きです。なぜなら、もし何か失敗してもそれを破棄し、マシンをゴミだらけにする環境変数のような面倒なものを、大量に残すことなく新たにやり直すことができるからです。


インストールするもの

まず、インストールしておく必要があるものを見てみましょう。
[Homebrew](http://brew.sh/)のインストール:

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

[Cask](https://github.com/caskroom/homebrew-cask)のインストール:

brew tap caskroom/homebrew-cask  
brew install brew-cask

VagrantとVirtualBoxのインストール:

brew cask install virtualbox  
brew cask install vagrant

Vagrantfile

Vagranfileは、Ruby DSLを使って、必要な仮想マシン環境を構築します。Dockerコンテナを使う場合、Vagrantは各コンテナを仮想マシンのように見せます。しかし、実際はそうではありません。Dockerコンテナがプロキシ仮想マシン内で起動しているのです。

したがって、Vagrantfileは2つ必要となります。1つはプロキシ仮想マシン(プロビジョナ)を定義するため、もう1つはDockerコンテナ(プロバイダ)を定義するためです。

プロキシVMのVagrantfile

以下はVagrantfile.proxyと呼ばれるVagrantfileのプロキシです。

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|  
  config.vm.box = "hashicorp/precise64"
  config.vm.provision "docker"
  config.vm.provision "shell", inline:
    "ps aux | grep 'sshd:' | awk '{print $2}' | xargs kill"

  config.vm.network :forwarded_port, guest: 6379, host: 6379
  config.vm.network :forwarded_port, guest: 5432, host: 5432
  config.vm.network :forwarded_port, guest: 9200, host: 9200
end

これには、hashicorp/precise64のUbuntu 12.04 64bit版のプロキシVM用イメージを使用します。また、dockerを設定し、シェルを使ってdockerを起動します(説明はここ)。

最後に、ポートフォワーディングを設定します。config.vm.networkを使って、Redis、Elasticsearch、PostgresのポートをプロキシVMからOS Xに転送します。

DockerコンテナのVagrantfile

メインとなるVagrantfileは以下の通りです。

VAGRANTFILE_API_VERSION = "2"  
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.define "redis" do |v|
    v.vm.provider "docker" do |d|
      d.image = "dockerfile/redis"
      d.volumes = ["/var/docker/redis:/data"]
      d.ports = ["6379:6379"]
      d.vagrant_vagrantfile = "./Vagrantfile.proxy"
    end
  end

  config.vm.define "elasticsearch" do |v|
    v.vm.provider "docker" do |d|
      d.image = "dockerfile/elasticsearch"
      d.ports = ["9200:9200"]
      d.vagrant_vagrantfile = "./Vagrantfile.proxy"
    end
  end

  config.vm.define "postgres" do |v|
    v.vm.provider "docker" do |d|
      d.image = "paintedfox/postgresql"
      d.volumes = ["/var/docker/postgresql:/data"]
      d.ports = ["5432:5432"]
      d.env = {
        USER: "root",
        PASS: "abcdEF123456",
        DB: "root"
      }
      d.vagrant_vagrantfile = "./Vagrantfile.proxy"
    end
  end

end

このファイルはRedis、Elasticsearch、Postgresの3つのコンテナをdockerfile/redisdockerfile/elasticsearchpaintedfox/postgresqlのイメージで定義します。

各ファイルは、プロキシVMファイルとしてvagrant_vagrantfileを定義し、同じプロキシ仮想マシン内のファイルをすべて起動させます。

RedisおよびPostgresの容量は、コンテナではなくプロキシVM内に保存される情報によって定義されます。したがって、コンテナが削除または更新されてもデータは消えません。次にすることとしては、これらのフォルダをプロキシVMからOS Xへ転送することがあげられますが、これは必ずしも必要なことではありません。

各コンテナのポートは、プロキシVMに転送するポートを定義します。このポートは、プロキシVMがOS Xに転送するポートと一致している必要があります。

Postgresコンテナは、サーバのセットアップに必要な環境変数も定義します。環境変数PGHOST=localhost PGUSER=root PGPASSWORD=abcdEF123456を設定することによって、これらの環境変数をOS XのPostgresサーバの初期設定として使用することができます。

Vagrantと組み合わせて動かす

Vagrantfileと同じディレクトリで次のコマンドを実行します

vagrant up --provider=docker 

初回実行時、VagrantはプロキシVMをダウンロードして起動し、次にDockerコンテナをダウンロードして起動します。Vagrant実行時はいつもこの初回ダウンロードが行われ、2回目以降はダウンロード済みのイメージを再利用します。
Dockerコンテナのステータスは以下のコマンドで確認できます

vagrant status 

このコマンドを実行すると、以下のような内容が出力されます

Current machine states:

redis                     running (docker)  
elasticsearch             running (docker)  
db                        running (docker)  

Dockerコンテナが正しく稼働しているかを調べるには、RedisやPostgresnのクライアントやElasticsearchのcurlコマンドを使うことができます。redis-cliとpsqlでサーバへのアクセスを調べ、curl http://localhost:9200コマンドのレスポンスを確認すればよいのです。

プロキシVM(デバッグには大変便利です)にアクセスする場合は、vagrant global-statusコマンドを実行します。これによりプロキシを含むすべての仮想マシンを一覧表示できます。次にプロキシのIDを入力し、vagrant ssh コマンドで呼び出します。手動でプロキシVMを変更することはお勧めしません。Chef(またはそれに類似した)スクリプトを使って変更するほうがよいでしょう。そうすればテストしやすく、分散可能になります。

パフォーマンス

仮想化を行う際、一番の気がかりは「どの程度の負荷がかかるのか」という点です。負荷を調べるために、私は 同僚とともに、Postgresと ElasticsearchおよびRedisの徹底的なテストを行いました。同一環境のハードウェア上で、ネイティブにインストールされたソフトウェアを使うケースと、Dockerによるコンテナを使う異なるケースを設定しました。このテストで、ネイティブの場合は実行に2分、Dockerコンテナの場合は3分という結果が出ました。

負荷は期待していたほど小さくはありませんでしたが、悪い結果が出たわけではないのでよしとしましょう。結果はどうあれ、私は今後も開発においてDockerを使うつもりです。ただし、開発環境で起きる様々な問題を解決する万能薬としては、お勧めできません。

注:VagrantとDockerを組み合わせて使用する際の、その他の制限事項はこちらをご覧ください。

結論

Vagrantと Dockerの組み合わせについて、今後の方向性はまだ分かりません。しかし、こうして多彩な使用方法を見ると、他には一体どんな使い方ができるのだろうと、つい考えてしまいます。さらに言えば、Vagrantと Dockerの組み合わせは、これまでの仮想化に関する経験の中では最も面白いものです。そしてこの面白さこそが、プログラミングの醍醐味なのです。