VagrantとChefでRailsの開発環境を構築する

手元のMacBookにRailsの開発環境を構築した。手順と調べたことを記録しておく。

構築する環境について

開発用の仮想環境を作り、ゲストOSでRailsを動作させる。仮想環境の管理にはVagrantを使用する。ゲストOSの環境構築はChefを使用して自動化する。開発時のファイルの編集は、Vagrantの共有フォルダ機能を使用してホスト側で行うものとする。

ホスト側の環境はOSX Yosemite 10.10.2である。

ゲストOSにはCentOSを使用する。Rubyはrbenvで管理する。システムにインストールするgemはbundlerのみとし、railsを含めその他のgemはすべてbundlerで管理する。

環境を構築するに当たって、事前にウェブの関連情報を探してみた。ヒットする情報の量は多いが、内容がまちまちなようである。同じことをするにも複数のやり方があったり、ツールのバージョンによってやり方が異なったりするようである。今回は、できる限り現時点の最新の公式情報に則り、公式のツールを使うことを目標とする。

Vagrantのインストールと設定

VagrantとVirtualBoxをインストールする。VagrantはHomebrewで、VirtualBoxはHomebrew Caskでインストールできる。

brew install vagrant
brew cask install virtualbox

Vagrantのプラグインである「vagrant-omnibus」をインストールする。vagrant-omnibusは、ゲストOSのChefの状態を確認し、指定したバージョンのChefをゲストOSに自動的にインストールするか、または指定したバージョンにアップデートしてくれる。

vagrant plugin install vagrant-omnibus

プロジェクト用のディレクトリを作る。

mkdir project
cd project

vagrant initで、Vagrantfileを作成する。

vagrant init

Vagrantfileを編集する。Vagrantfileの設定方法は、Vagrantの公式ドキュメントを参考にする。

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.box = "chef/centos-6.5"

  config.vm.network "forwarded_port", guest: 3000, host: 4000

  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  config.omnibus.chef_version = :latest

  config.vm.provision "chef_solo" do |chef|
    chef.cookbooks_path = ["chef-repo/cookbooks", "chef-repo/site-cookbooks"]

    chef.add_recipe "nodejs"
    chef.add_recipe "rbenv::default"
    chef.add_recipe "rbenv::ruby_build"
    chef.add_recipe "sqlite"
    chef.add_recipe "vim"

    chef.add_recipe "base"
  end

end

config.vm.boxについて。どこからBoxを調達するのが良いかという点であるが、公式ドキュメントの解説では、ATLASのDiscover Vagrant Boxesページが案内されている。ATLASは、Vagrantの開発主体であるHashiCorpが運営するサービスであるらしい。また、config.vm.boxに、<ユーザ名>/<Box名>の形式で設定を記述した場合、同名のBoxがローカルに存在しなければ、自動的にATLASのサイトを検索して、該当するBoxをダウンロードするようになっている。

今回は、Discover Vagrant Boxesを「centos」で検索した時に最もダウンロード数が多い「chef/centos-6.5」を使うこととする。

config.omnibus.chef_versionは、vagrant-omnibusのための設定である。chef_version = :latestは、最新版のChefを表す。

config.vm.provision "chef_solo"以下に、Chef Soloによるプロビジョニングの設定を記述する。設定方法は、公式のドキュメントが詳しい。cookbooks_pathに適用するクックブックの保存先ディレクトリを指定する。add_recipeで実行するレシピを指定する。

Chef関連ツールのインストール

Chef関連のツールをインストールする。Chefは関連するツールが様々あり、多くはgemを使ってインストールすることができる。Chefの公式の案内によると、これからChefの利用を始める場合には、Chef Development Kit(ChefDK)を利用することが推奨されているようである。

ChefDKには、Chef、Berkshelf、Test Kitchen、ChefSpec、Foodcritic、Chef client、Knife、Ohai、Chef zeroが含まれている。それらのツール群と、それらのツール群が依存するRubyの処理系をパッケージにまとめたものが、Chef Development Kitであるらしい。

ChefDKは、Homebrew-Caskでインストールすることができる。

brew cask install chefdk

ChefDKには、knife soloが含まれていない。ChefDKのインストール後に、knife-soloを個別にインストールする。chef gemコマンドを使うことで、ChefDKの処理系に対してknife-soloをインストールすることができる。

chef gem install knife-solo

クックブックの準備

knife solo initでChef関連ファイルのひな形を作る。

knife solo init chef-repo
cd chef-repo

Berksfileを編集する。Berksfileは、サードパーティのクックブックを、依存関係を解決しつつまとめて管理するツールであるBerkshelfの設定ファイルである。Berkshelfで取得できるクックブックは、Chef Supermarketで探すことができる。

source "https://supermarket.chef.io"

cookbook 'nodejs', '~> 2.2.0'
cookbook 'rbenv', '~> 1.7.1'
cookbook 'sqlite', '~> 1.1.0'
cookbook 'vim', '~> 1.1.2'

berks vendorで、必要なクックブックをcookbooksディレクトリ配下に取得する。

berks vendor cookbooks

knife cookbook createで、新しいクックブックのひな形を、site-cookbooks配下に作成する。

knife cookbook create base -o site-cookbooks

新しく作成したクックブックのメタデータ(site-cookbooks/base/metadata.rb)とレシピファイル(site-cookbooks/base/recipes/default.rb)を編集する。

メタデータには、依存するクックブックをdependsで指定する。今回は、rbenvクックブックが提供するリソースを使用するため、depends 'rbenv'を指定する。

# metadata.rb
depends 'rbenv'
rbenv_ruby "2.1.5" do
  ruby_version "2.1.5"
  global true
end

rbenv_gem "bundler" do
  ruby_version "2.1.5"
  version "1.8.2"
end

service 'iptables' do
  action [:disable, :stop]
end

Chef Supermarketのrbenvクックブックは、rbenvを自動的にインストールしてくれる。また、カスタムクックブックでruby本体とgemをインストールするための、rbenv_rubyリソースと、rbenv_gemリソースを提供してくれる。詳しい使い方はChef Supermarketのrbenvのページで解説されている。rbenv_rubyを使って、Ruby 2.1.5をグローバルにインストールし、rbenv_gemを使って、bundlerをインストールする。

chef/centos-6.5のBoxは、デフォルトではssh以外のほとんどの通信を遮断するように設定されている。ホストのブラウザからゲストOSのRailsにアクセスするために、ここではiptablesを無効にすることとする。

vagrant upからrails sの実行まで

事前の準備が完了したので、ここでvagrant upする。

cd ../
vagrant up

初回のvagrant up時に、自動的にプロヴィジョニングが実行される。起動とプロヴィジョニングが完了したら、ゲストOSにログインし、プロヴィジョニングが正しく完了していることを確認する。

vagrant ssh
# 以下はゲストOSでの操作
sqlite3 -version
node -v
rbenv -v
ruby -v
bundler -v

次にRailsアプリを作成するが、その際にプロジェクトのディレクトリとして/vagrantを使用する。/vagrantは、ホスト側のVagrantfileが存在するディレクトリと自動的に共有設定されている。そのため、/vagrantに保存したファイルは、ホスト側から編集することが可能になる。

/vagrantディレクトリに移動して、Gemfileを作る。

cd /vagrant
bundle init

Gemfileを編集する。

source 'https://rubygems.org'
gem 'rails', '4.2.0'

bundle installを実行する。

bundle install --path vendor/bundle

rails newを実行する。Gemfileを上書きするか確認されるので、Yesを選択する。

bundle exec rails new .

rails sで動作確認用のサーバを起動する。Rails 4.2で、ゲストOSのRailsにホスト側から接続する場合、-b 0.0.0.0を指定しなければ接続することが出来ない。Rails 4.2のリリースノートによると、Rails 4.2からrails serverのデフォルトホストが0.0.0.0からlocalhostに変更されたことが原因であるらしい。

bundle exec rails s -b 0.0.0.0

ホスト側のブラウザでhttp://localhost:4000にアクセスする(Vagrantのポートフォワードでホストの4000をゲストの3000にフォワードしているため)。Railsのトップ画面が表示される。

以上で開発環境の構築は完了である。以降、rails grails sなどRailsのタスク実行、マイグレーション、Gemfileを更新した時のbundle install、テストの実行は、ゲストOS側で実行する。また、バージョン管理はホスト側のプロジェクトディレクトリでgit initして、基本的にはホスト側で行う。

ホスト側でもrails sできるか、試しに実行してみたが、already initialized constant APP_PATHというワーニングが発生して、起動することは出来なかった。ゲストOS側、ホスト側を意識せずに、どちらでもRailsのタスクが実行できたら便利なのにと思う。

その他の話題:vagrant provisionが失敗する

vagrantのプロヴィジョン機能について。

私の環境では、初回のvagrant up時のプロヴィジョンは成功するが、2回目以降のvagrant provisionvagrant reload --provisionは、cookbookがゲストOSに正しく転送されず、必ずCookbook (hoge) not foundのエラーが発生する現象が起きている。

本来、cookbooks_pathで指定したパスが共有フォルダに設定されなければいけないところが、初回のvagrant up時以外は、該当のパスが共有フォルダに指定されないためにエラーが発生しているらしい。このエラーが発生した場合、.vagrantディレクトリ以下の.vagrant/machines/default/virtualbox/synced_foldersを削除して、vagrant reload --provisionを実行すると、正常にプロヴィジョンすることができる。

ひとまず上記の方法で回避はできているが、根本的な原因と解決策は不明である。誰かわかる人がいたら教えてください。