Vagrant と Chef による仮想環境構築の自動化（VirtualBox編）


AWS のようなクラウドサービスが一般に浸透したことにより、仮想環境を前提とした開発が一般的になってきました。必要になったときに必要な構成のリソースにアクセスできるクラウドの強みを活かすため、オンデマンドで仮想環境を構築し直す機会が以前よりも増えてきています。このような状況では、仮想環境をより効率的に構築する必要があります。本記事のシリーズでは効率的な仮想環境構築の手段の一つとして、Vagrant と Chef というツールで環境構築を自動化する方法を紹介します。

# はじめに

最初に Vagrant と Chef を使った環境構築の概要、および今回構築する仮想環境についてご紹介します。

## 仮想環境構築の自動化が求められる背景

クラウドが一般に浸透した現代では仮想化技術を利用することが当たり前となっています。仮想環境では、物理的な制約がないため、環境を廃棄したり、必要なときに再構築することが容易です。そのため、ソフトウェアのバージョンアップやパフォーマンスチューニングなどに伴い、環境を変更したい場合は、既存の環境に変更を加えるのではなく、新たな設定を施した環境を新たに作成して既存の環境を破棄することが一般的になってきました。これにより、環境への変更の整合性を管理する必要がなくなり、運用管理の負荷は劇的に下がりました。

一方で、仮想環境を構築する頻度が増大したため、環境構築をより効率的にミスなく行うことが重要になってきました。手作業で環境構築を行う場合、作業ミスにより環境に差分が生まれてしまい、それがシステムの不具合につながるリスクがあります。

そこで、本記事で提案する手法は、ツールを利用し環境構築手順をコードとして表現することで環境構築を自動化することです。自動化には以下のようなメリットがあります。

* 構築手順がコードによって自動化できるため、何度でも同じ結果を得ることができます。環境の差分によってシステムの動作が変わるといったトラブルを防ぐことができます。
* 個人の暗黙的なノウハウに依存していた環境構築作業がコードとして形式知化できます。ノウハウの共有が容易になります。

## 環境構築の自動化に利用するツール

本記事ではタイトルにある通り、環境構築に以下の 2 つのツールを使います。元々は別のツールですが、プラグインで両者を連携させることで、仮想環境の構築で必要となる一連の作業（仮想マシンの立ち上げ、OS や各種ソフトウェアのインストール、アプリケーションのビルドなど）を自動化できます。

* Vagrant
 * 仮想マシンを操作する際のフロントエンドとなるツールです。指定した設定を施した仮想マシンを立ち上げることができます。また、プラグインを利用することで、機能拡張ができます。Chef の制御もプラグインによって行います。
* Chef
 * 各種設定作業を Ruby のコードで自動化するツールです。各種設定作業の手順（Chefでは Recipe という）や設定（Chef では Attribute という）をクックブック（Cookbook）と呼ばれる再利用可能な単位で記述できます。シェルスクリプトで自動化する方法と比較してChef を利用するメリットとして以下があります。
 * あらかじめ決められた Ruby の DSL に沿って非常に簡潔なコードで手順を記述できる。
 * クックブックの依存関係の解決を自動的に行ってくれる（依存するクックブックの取得、依存関係に沿った設定作業の実施など）

#実際に Vagrant と Chef を動かしてみよう

ここからは実際に手を動かしながら、Vagrant と Chef の利用イメージを掴んでいただきます。最初に必要なツールをひと通りインストールした後、筆者があらかじめ用意した設定ファイルを利用して、アプリケーションが起動するまでを体験していただきます。最初にインストール作業が続きますが、少々辛抱してください ^^;

## 構築対象の仮想環境

本記事では、以下の構成の仮想環境を構築し、さらにデータベース中のデータを一覧表示する簡単な Web アプリケーションが起動するまでの作業を自動化します。

* 仮想マシン: VirtualBox 4.3.20
* OS: CentOS 7.0
* DB: PostgreSQL 9.2.7
* アプリケーションサーバ: Netty (Play Framework に付属）
* プログラミング言語: Java 8

今回は手元で確認できる仮想マシンとして VirtualBox を利用します。次回公開する記事では、クラウドホスティングサービスとして最も人気のある Amazon EC2 での環境構築手順も紹介します。

## VirtualBoxのインストール

今回は VirtualBox で仮想マシンを立ち上げます。以下の URL から VirtualBox のインストーラを取得し、インストールしてください。

http://www.oracle.com/technetwork/server-storage/virtualbox/overview/index.html

## Vagrant のインストール

以下の URL からバージョン 1.6.5 の Vagrant のインストーラをダウンロードし、インストールを行って下さい。

http://www.vagrantup.com/downloads

（注意）本記事執筆時点での最新バージョンは 1.7.2 ですが、1.7 系を利用した場合、Vagrant プラグインの動作がまだ不安定なため、本記事では動作が安定した 1.6系 の安定バージョンを使います。

インストールしたら以下のコマンドで Vagarant が実行できることを確認してください。

	% vagrant --version
	Vagrant 1.6.5

## Chef のインストール

Chef-DK という Chef を利用する際に必要なツールをひとまとめにしたキットを利用します。以下の URL から Chef-DK（バージョン 0.3.5 ） のインストーラをダウンロードしてください。画面中央にある OS のアイコンをクリックし、OS に合ったインストーラのダウンロードページを開く必要がある点に注意して下さい。

https://downloads.chef.io/chef-dk/

（注意）
* 本記事執筆時点での最新バージョンは 0.3.6 ですが、Vagrantプラグインの動作が安定しないため、安定動作が確認できたバージョンを使用しています。
* Chef-DK にバンドルされた Ruby の利用が推奨されています。以下のコマンドで Chef-DK の Ruby を使うように指定して下さい。

	% eval "$(chef shell-init SHELL_NAME)"

（注意）SHELL_NAME の箇所は自分が使っているシェル名に置き換えてください。

以下のコマンドで Chef-DK がインストールされていることを確認してください。

	% chef -v
	Chef Development Kit Version: 0.3.5

## Vagrantプラグインのインストール

Vagrant は仮想環境構築のフロントエンドとなるツールです。仮想マシンを立ち上げたり、Chef でソフトウェアをインストール作業は Vagrant のプラグインから行います。

以下のコマンドで vagrant-vbguest という VirtualBox を Vagrant から利用するためのプラグインをインストールします。

	$ vagrant plugin install vagrant-vbguest

次に Vagrant から Chef Zero Server を起動するためのプラグイン vagrant-chef-zero をインストールします。

	% vagrant plugin install vagrant-chef-zero

次にゲスト OS に Chef をインストールするために必要なプラグイン vagrant-omnibus をインストールします。

	% vagrant plugin install vagrant-omnibus

以上で環境設定は終了です。

## アプリケーションの起動

ようやく実際に Vagrant と Chef でアプリケーションを起動してみましょう。以下のコマンドで Vagrant や Chef の設定ファイルを Github リポジトリから取得してください。

	git clone https://github.com/ogis-onishi/vagrant-chef-sample.git
	cd vagrant-chef-sample

次に以下のコマンドで仮想マシンを立ち上げてください。

	$ vagrant up

Vagrant は仮想マシンの起動時間を短縮するため、Box と呼ばれる仮想マシンのベースイメージを使用します。本サンプルでは、初回の起動時に Box をダウンロードするよう指示しているため、初回のみ起動に時間がかかる点に注意してください。

以下のようなメッセージが出力されると、Chefの実行が完了し、環境構築は終了しています。

	INFO: Chef Run complete in 0.046189483 seconds

Vagrant のコマンドを使って、ゲスト OS に SSH で接続できます。

	$ vagrant ssh

環境構築が無事に完了していれば、以下のコマンドで PostgreSQL がインストールされ、データベースが構築されていることを確認できます。

	$ psql -h localhost -U postgres -W
	(postgres ユーザのパスワード postgres を入力)
	postgres=# \connect sampledb
	postgres=# select * from sample_table;

さらにゲスト側で Netty（Play Framework の組み込み Web サーバ）が動作していることを確認してください。

	$ curl localhost:9000
	（データベースの内容を出力するHTMLが表示される）

以上で環境構築が完了し、アプリケーションが立ち上がるところまで自動化できていることが確認できました。一番最初に環境構築を行うホスト側の設定に一苦労しますが、手順が一度確立していれば、簡単に環境構築できることがお分かりいただけたと思います。

動作が確認できたら、以下のコマンドで一旦作成した仮想マシンを破棄してください。

	$ vagarnt destroy

#Vagrant と Chef の実践

ここからは前の節で環境構築し、動作を確認したアプリケーションについて、実際に自分でゼロから構築する手順を紹介します。なお、Vagarnt、Chef、Vagrant プラグインといったツールはひと通りホスト側にインストールされているという前提で手順を説明します。まだインストールされていない場合は、一度、前の節に戻って、ホスト側の環境設定を行って下さい。

## Vagrantfile の作成

Vagarnt を実行する際には Vagrantfile という設定ファイルで Vagrant の実行方法を指定します。Vagrantfile のひな形は以下のコマンドで作成できます。

	$ vagrant init

前述のとおり、 Vagrant は Box と呼ばれる仮想マシンのベースイメージを使用して、短時間で仮想環境を立ち上げます。Vagrantfile には Box の取得場所を指定します。ここでは Vagrantbox.es という有志が作成した Box を公開する Web サイトから取得する旨を指示します。

http://www.vagrantbox.es/

	$vim Vagrantfile
	 Vagrant.configure(2) do |config|
	   config.vm.box = "centos7"
	   config.vm.box_url="https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
	 end

* config.vm.box に自分の box の名前として box の内容が分かるような文字列を指定します。
* config.vm.box_url には CentOS 7 の Box の URL を指定します。
* Vagrantbox.es に掲載されている CentOS 7 の Box のうち、「CentOS7.0 x86_64 minimal (VirtualBoxGuestAddtions 4.3.14)」を利用します。

これらの記述により、ローカルの Box 置き場に "centos7" という Box が存在する場合、ローカルの Box を使います。存在しない場合、指定した URL から Box をダウンロードし、ローカルに配置します。

## 仮想マシンの起動

この時点で一度仮想マシンが起動できるか確かめてみましょう。

	$ vagrant up
	==> default: Machine booted and ready!
	==> default: Checking for guest additions in VM...
	==> default: Mounting shared folders...
    	default: /vagrant => /Users/yohei/work/vagrant-chef-sample

うまく起動できたら、SSH でゲスト OS に接続できることも確認します。

	$ vagrant ssh

## Chef Zero Server の起動および Chef Client の実行

仮想マシンの起動まで確認できたため、次に Vagrant プラグインからホストOS側で Chef Zero Server が起動出来る点、またゲスト OS 側に Chef Client がインストールされることを確認します。

	% vagrant plugin install vagrant-omnibus

両プラグインを利用する設定を Vagrantfile に追記します。以下の設定ファイルでは以下を指示しています。

## ゲスト OS に最新の Chef をインストールする。
Chef Zero Server のリポジトリとしてカレントディレクトリを設定する。
プロビジョン（各種設定を行うツール）として Chef Client を設定する。
クックブックは何も実行しない。

	Vagrant.configure(2) do |config|
	  config.omnibus.chef_version=:latest
	  config.chef_zero.chef_repo_path = "."
	  config.vm.box = "centos7"
	  config.vm.box_url = "https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
	  config.vm.provision :chef_client do |chef|
	    chef.custom_config_path = "chef_custom_config"
	    chef.run_list = []
	  end
	end

ここで Chef-Client の設定ファイルとして "chef_custom_config" を指定しています。カレントディレクトリに以下のようなファイルを作成します。これは SSH 関係の警告を出さないようにするための対処です。

	% more chef_custom_config

Chef::Config.ssl_verify_mode = :verify_peer
それでは仮想マシンが立ち上がるところからプロセスを確認するため、一度、作成済みの仮想マシンを破棄し、再度立ち上げます。

	% vagrant destroy
	% vagrant up

プラグインのインストールと設定ファイルの記述が正しく行われていれば、Chef Zero Server も起動し、ゲストに Chef のインストールが行われます。今回はクックブックを指定なかったため、Chef の run list が空である旨が記載されています。

	% vagrant up
	Starting Chef Zero at http://192.168.179.4:4000
	==> default: Installing Chef 12.0.3
	==> default: Thank you for installing Chef!
	==> default: Running provisioner: chef_client...
	==> default: Warning: Chef run list is empty. This may not be what you want.
	==> default: [2015-01-24T20:08:40-05:00] INFO: Chef Run c	omplete in 0.046189483 seconds

## クックブックを使った PostgreSQL のインストール

ここからようやく本題であるクックブックの作成にとりかかります。今回は以下の作業を自動化します。

* PostgreSQLの server、client、contrib パッケージのインストール
* postgres ユーザのパスワード設定
* sampledb データベースの作成
* コミュニティのクックブックの利用

クックブックは全て自分自分で作成する必要はなく、コミュニティの有志によって作成されたものを再利用することができます。クックブックの共有サイトである以下の URL にアクセスし、PostgreSQL をインストールできるクックブックを探してみましょう。

https://supermarket.chef.io/

PostgreSQL をインストールできるクックブックである postgres を見つけました。数十万件ダウンロードされ、最近もメンテナンスされているようですので、こちらを利用しましょう。

https://supermarket.chef.io/cookbooks/postgresql

## Berkshelf によるクックブックの依存関係解決

クックブックを利用する場合、注意する必要があるのが、クックブック間の依存関係です。これは一般的なソフトウェアのビルドと共通する問題です。あるクックブックが別のクックブックに依存している場合、目的のクックブックを利用するために依存先のクックブックを全て集める必要があります。さらに依存先のクックブックに依存先のクックブックがある場合、芋づる式に必要なクックブックが増えてしまいます。

この依存関係の問題を解決するのが Berkshelf です。Berkshelf の設定ファイルである Berksfile に必要なクックブックを記載しておけば、Berkshelf が必要なクックブックを自動的に集めてきてくれます。

カレントディレクトリに Berksfile というファイルを作成し、以下のように記述してください。

	% more Berksfile
	source "https://api.berkshelf.com"

	cookbook 'postgresql'

Berkshelf は Chef-DK に入っているため、すでに利用可能です。以下のコマンドを実行し、postgresql と依存先のクックブックをダウンロードしてください。

* 第1引数 vendor はクックブックをダウンロードすることを意味します。
* 第2引数の coobooks はクックブックのダウンロード先ディレクトリです。vagrant-chef-zero プラグインは Cookbooks というディレクトリに置かれたクックブックを Chef Zero Server にアップロードします。

	% berks vendor cookbooks

次に Vagrantfile を開いて、実行対象のクックブックとして postgresql を指定してください。vagrant provision を実行すると、実際に postgresql が Chef Zero Server にアップロードされた後、ゲスト側の Chef がそのクックブックを実行します。

以下では postgresql のクックブックのうち、server と client と contrib の3つのレシピを指定しています。さらに postgresql の属性として postgres ユーザのパスワードを指定しています。このようにクックブックで用意されている設定に対して、ユーザが期待する設定を上書きすることもできます。

	% vim Vagrantfile
	    config.vm.provision :chef_client do |chef|
	      chef.custom_config_path = "chef_custom_config"
	      chef.run_list = [
	          "postgresql::server",
	          "postgresql::client",
	          "postgresql::contrib"
 	     ]
 	     chef.json = {
	        :postgresql => {
	          :password => 'postgres'	
	        }
	      }
	    end

実際に PostgreSQL がインストールされているか確認しましょう。無事に 9.2.7 がインストールされ、postgres ユーザでログインできることが確認できました。

	% vagrant provision
	% vagrant ssh
	[vagrant@localhost ~]$ psql -h localhost -U postgres -W
	psql (9.2.7)
	postgres=#

## クックブックによるデータべースの構築

次に PostgreSQL データベース sampledb を作成します。ここでもコミュニティのクックブック database を利用して、DB の設定を行います。

https://supermarket.chef.io/cookbooks/database

ただし、 database は DB の設定を行うためのライブラリに相当するクックブックで、実際の設定作業は自分のレシピとして実装する必要があります。Chef の慣習ではコミュニティのクックブックと自作のクックブックを分けて配置することが一般的です。

それでカレントディレクトリ配下に site-cookbooks というディレクトリを作成し、PostgreSQL の設定を行うクックブックとして postgresql_config を作成します。

metadata.rb がクックブックの情報を記載する箇所です。クックブックの依存関係なども記載できます。

	% more site-cookbooks/postgresql_config/metadata.rb
	name             'postgresql_config'
	maintainer       'Yohei Onishi'
	maintainer_email 'yohei@example.co.jp'
	license          'Yohei Onishi All rights reserved'
	description      'Configures postgres'
	long_description 'Configures postgres'
	version          '0.1.0'

recipes ディレクトリはレシピの配置場所です。default.rb はデフォルトで実行されるレシピです。database に定義されている postgresql_database タスクを利用して実装しています。ここでは localhost に sampledb というデータベースを作成し、 postgres ユーザからアクセスできるようにしています。さらにアプリケーションで利用するテーブルを作成し、テストデータを1件挿入しています。

	%more site-cookbooks/postgresql_config/recipes/default.rb
	postgresql_database 'sampledb' do
  	connection(
    	:host     => '127.0.0.1',
    	:port     => 5432,
    	:username => 'postgres',
    	:password => node['postgresql']['password']['postgres']
	  )
  	template 'DEFAULT'
  	encoding 'DEFAULT'
  	tablespace 'DEFAULT'
  	connection_limit '-1'
  	owner 'postgres'
  	action :create
	end

	postgresql_database 'create databases' do
	  connection(
    	:host     => '127.0.0.1',
	    :port     => 5432,
	    :username => 'postgres',
	    :password => node['postgresql']['password']['postgres']
	  )
	  database_name 'book'
	  sql "create table book (
	         id         bigint not null,
	         title      varchar(255) not null,
	         author     varchar(255) not null,
	         publisher  varchar(255) not null,
	         constraint pk_book primary key (id));
	       create sequence book_seq;
	       insert into book values('test book', 'test author', 'test publisher');
      "
	  action :query
	end

すでに説明した通り、Chef Zero Server でクックブックを利用するために、berks コマンドで依存先のクックブックを含めて再度ダウンロードします。以下のように database および postgresql_config を追記してください。postgresql_config は自作のクックブックであるため、取得元を指定していることに注意してください。このように Berksfile を記載することで、自作のクックブックだけ site-cookbooks に配置して、構成管理対象にし、プロビジョニングするときだけコミュニティのクックブックを含めて cookbooks にコピーして利用することができます。

	% more Berksfile                                                                                       	source "https://api.berkshelf.com"

	#original cookbooks
	cookbook 'postgresql_config',  path: './site-cookbooks/	postgresql_config'

	#community cookbooks
	cookbook 'database'
	cookbook 'postgresql'

新たに利用するクックブックをダウンロードします。

	% berks vendor cookbooks

新たに追加したクックブックを実行するため、Vagarntfile の Chef Client 設定部分の run_list に database と postgresql_config を追加してください。

	  config.vm.provision :chef_client do |chef|
	    chef.custom_config_path = "chef_custom_config"
	    chef.run_list = [
	        "postgresql::server",
	 	    "postgresql::client",
	        "postgresql::contrib",
	        "database::postgresql",
	        "postgresql_config"
	    ]
	    chef.json = {
	      :postgresql => {
	      :password => 'postgres'
	      }
	    }
	  end

再度プロビジョンを行った後、SSH 接続して、PostgreSQL 上に sampledb データベースが作成されていること、book テーブルにデータが登録されているを確認してください。

	% vagrant provision
	% vagrant ssh
	[vagrant@localhost ~]$ psql -h localhost -U postgres -W
postgres=# \connect sampledb

データベース "sampledb" にユーザ"postgres"として接続しました。
	postgres=# \select * from book;

以上で、ゲスト側に PostgreSQL をインストールし、sampledb を作成し、さらに book テーブルも作成できました。

## クックブックを使った Web アプリケーションのインストールと起動

次に Play Framework （Java / Scala 向け Web フレームワーク）を使った Web アプリケーションのインストールおよび起動を行います。ここでは以下の一連の作業を Chef で自動化します。

* ソースコードを Github リポジトリから取得する。
* コマンドラインツール activator でソースコードをビルドし、アプリケーションを起動する。

（注意）本記事は Play Framework 自体の解説は行いません。Play Framework 自体については他の Web 記事や書籍を参照してください。

Play Framework 向けの Chef でもクックブックの最小構成として metadata.rb とレシピ (default.rb) を作成します。本クックブックは自作クックブックのため、 site-cookbooks ディレクトリ配下に作成します。

metadata.rb ではクックブックをインストールする際に必要な以下の2つクックブックへの依存関係を記述しています。

* git: git をインストールするクックブックです。本サンプルではソースコードを Github から取得する際に git を使います。
* java: Java をインストールするクックブックです。Play Framework はビルド・実行に Java が必要です。

	% more site-cookbooks/play-sample/metadata.rb
	name             'play-sample'
	maintainer       'Yohei Onishi'
	maintainer_email 'yohei@example.co.jp'
	license          'Yohei Onishi All rights reserved'
	description      'clone from repo'
	long_description 'clone from repo'
	version          '0.1.0'

	depends 'git'
	depends 'java'

次にデフォルトの Recipe（default.rb） では、ソースコードを Github リポジトリから取得し、activator コマンドを使って Play Framework を使ったアプリケーションをビルド・起動しています。 本 Recipe を見てお分かりの通り、Ruby の DSL として簡潔に記述できます。

	% more site-cookbooks/play-sample/recipes/default.rb
	git '/home/vagrant/play-sample' do
	  repository 'https://github.com/ogis-onishi/play-sample.git'
	  user 'vagrant'
	end
	bash "activator-run" do
	  user 'root'
	  cwd '/home/vagrant/play-sample/'
	  code <<-EOH
	    ./activator clean stage
	    target/universal/stage/bin/play-sample & 
	  EOH
	end

site-cookbooks に置いておくと Chef Zero Server にアップロードされないため、Berksfile に追記しておきます。

	cookbook 'play-sample',        path: './site-cookbooks/play-sample'

再度、berks vendor cookbooks を実行すると、依存先のクックブックが収集され、cookbooks ディレクトリに配置されます。

Chef Client のプロビジョン対象にするには chef.run_list に play-sample を追加しておきます。play-sample を実行する際に git と java が必要になりますが、 metadata.rb に依存関係を記述しているので、自動的に Chef がgit と java も実行対象にしてくれます。

    chef.run_list = [
        "postgresql::server",
        "postgresql::client",
        "postgresql::contrib",
        "database::postgresql",
        "postgresql_config",
        "play-sample"
    ]
java クックブックは yum で java のインストールを行います。本記事執筆時点で yum はまだ Java 8 のインストールに対応していないため、インストール対象の JDK バージョンとして 7 を指定します。これで Java 7 の最新バージョンがインストールされます。

    chef.json = {
      :postgresql => {
        :password => 'postgres'
      },
      :java => {
        :jdk_version => '7'
      }
    }
仮想マシンはすでに起動されているため、再度プロビジョンのみ行います。

	$ vagrant provision

プロビジョンが完了したら、一度、ゲスト OS にログインし、アプリケーションが起動していることを確認してください

	$ vagrant ssh
	$ curl localhost:9000
	（登録した書籍情報を出力した HTML が表示される）

サーバが起動し、Web アプリケーションが表示できていることを確認できれば完了です。

# おわりに

本記事では、Vagrant と Chef を使って以下の作業の自動化を行いました。

* 仮想マシンの起動
* ソフトウェアのインストール（PostgreSQL、git、Java など）
* Web アプリケーションのビルドと起動

一番最初にホスト側の設定（Vagrant と Chef のインストール）を行う必要がありますが、一度行ってしまうとゲスト OS の環境構築は自動できることがお分かりいただけたと思います。

今回はローカル PC 上で動作確認いただくため、VirtualBox を利用しました。次回はクラウドサービスで最も人気のある AWS 上で利用する方法について紹介します。
vi