まちクエスト 運営ブログ

まちクエストからのお知らせや開発秘話などをお届けしていきます

VagrantとChefSoloを使って、まちクエストの開発環境構築を自動化

はじめまして。@nushu123と申します。
某IT系企業でネットワークエンジニアとしてBGPを触ったり、営業としてクラウドサービスに関わったりしていました。 最近はwebサービス開発に興味があり、@jishihaさんの下でお手伝いをさせていただくことになりました。

まずは、VagrantとChefSoloを使って、まちクエストの開発環境構築の自動化に取り組みました。 今回は、VagrantとChefSoloの主な操作手順と、ハマったポイントについて紹介したいと思います。

1. まちクエスト開発環境構築の自動化のポイント

  • コマンド一発で、開発環境deployができる事
    • 今までは各自のローカルPCで環境を準備していました。環境に依存するエラーが発生した場合、トラブルシュートに時間を要していました。
    • まさにvagrantの出番といったところです
  • 開発環境deploy時に、サンプルdataの登録もあわせて実施したい
    • Railsのdb/seeds.rbファイルでサンプルデータを定義し、rake db:seedコマンドで対応することにしました
    • rake db:seedコマンドはChefのbashリソースでを実行します。
      (今回の説明では、seed.rbおよびbashろソースの説明は割愛します)
  • 開発メンバー間で共有できる事
    • すでにgithubで共有しているrepositoryに対して、VagrantfileとChef関連ファイルを追加する事で対応できます。

実際に開発メンバーに使ってもらい、vagrant upコマンド一発で無事起動できることが確認できました。 現在、まちクエストのrepositoryにmergeしてもらっています。

2. Vagarnt、ChefSoloって何?

Vagrant(ベイグラント)はVirtualBoxのフロントエンドに相当するツールです。vagrantコマンドなどを使ってコマンドラインから簡単に新しい仮想マシンを削除したり、不要なVMを削除したりできます。
オープンソースとして公開されています。各種ドキュメントは下記で公開されています。
http://www.vagrantup.com/

VagrantVM作成後のソフトウェア構築の自動化にも対応しており、その場合はshellやChefSolo,Puppet,Ansibleなどのdeploy toolと連携します。今回は、ChefSoloを使用します。Chef自体に興味があったためです。

Chef(シェフ)は Ruby製のサーバ構成管理ツールです。ファイルに記述した設定内容に応じて自動的にユーザーの作成やパッケージのインストール、設定ファイルの編集などを行います。Chefではサーバ・クライアント式のモデルで、クライアント側からサーバ側に情報を問い合わせるPULL型のシステムアーキテクチャとなります。knifeコマンドを使って操作します。大規模構成ならばこれでいいのですが、小規模もしくは1台で試しに触っている人にとっては、サーバの構築は煩わしく感じられ、それがChefの敷居の高さになっていました。
そこで、Chefのスタンドアロン版であるChefSoloの登場です。1台のマシンでChefの動作が完結します。knife soloコマンドを使って操作します。

Chefに関するドキュメントは下記ですが、量が多く挫折するので、今はskipする事にします。慣れてきたら見ることにしましょう。
http://docs.opscode.com/

各ソフトウェアの構成手順をまとめたものがcookbookです。下記サイトでは、様々なcookbookが公開されています。これらのcookbookはマルチOS対応になっているものが多いので、簡単に試す事ができて便利です。ただし、これらのcookbookは動作を保証するものではないので注意が必要です。 Chefに慣れてきたら、cookbookを自作にも挑戦してみましょう。
http://community.opscode.com/cookbooks

3. 環境説明、install

今回はMBAで環境を用意します。

4. Vagrant実行方法

ここでは簡単なVagrantの使い方について説明します。

適当なディレクトリを作成し、移動

$ mkdir vagrant-test
$ cd vagrant-test

仮想マシンのイメージとして、precise64(Ubuntu12.04 64bit版)を指定し、初期化を実施。Vagrantfileが作成される。

$ vagrant init precise64 http://files.vagrantup.com/precise64.box

作成したVagrantfileをもとに、仮想マシンを作成、起動

$ vagrant up

起動した仮想マシンに対してsshアクセス

$ vagrant ssh

仮想マシンを停止

$ vagrant halt

仮想マシンを削除

$ vagrant destroy

5. Vagrant+ChefSolo実行方法

上記の手順で、仮想マシンの作成とsshアクセスまで確認できたと思います。 しかし、これだと単純に仮想マシンを起動しただけなので、VirtualBoxを使っているのと変わりません。 いよいよVagrantの真価発揮です。

今回は、下記対応手順について説明します。 * rubyバージョン管理システム(rvm)をinstallし、ruby-1.9.3-p448とbundlerをinstall。 公開されているcookbookを使用 * MySQLapacheをinstall。 こちらは、chefのpackageリソースを使って、apt-getでinstallします。

一般的にはまずは公開されているcookbookを使ってchefに慣れた後、自分でcookbookを作成する事が多いようです。 公開されているcookbookはマルチOS対応で便利な反面、オーバーヘッドが多いので、自分が必要とするものだけを記述して見通しをよくした方が良さそうです。

4で作成してディレクトリで、bunle initでGemfileを生成。その後、Gemfileを編集。今回は、berkshelfとknife-soloを追加します。(bundle initはGemfileを生成するだけなので、いきなりvi Gemfileとしても問題ないはず) どちらも現時点の最新版がinstallされます。(berkshelf 2.0.10とknife-solo 0.4.0)
chefではknifeコマンドを使って操作するのですが、chef soloではknifeコマンドのサブコマンドのknife soloコマンドを使います。 次のbundle installで、knifeとknife soloコマンドがinstallされます。

$ bundle init
$ vi Gemfile
source "https://rubygems.org"
gem "berkshelf"
gem "knife-solo"

bundle installを実行

$ bundle install --path vendor/bundle

berks initを実行し、Berksfileを生成。GemfileとVagrantfileのOverwriteするか聞かれますが、nでskipします。

$ bundle exec berks init
      create  Berksfile
      create  Thorfile
      create  .gitignore
         run  git init from "."
    conflict  Gemfile
Overwrite xxx/vagrant-test/Gemfile? (enter "h" for help) [Ynaqdh] n
        skip  Gemfile
    conflict  Vagrantfile
Overwrite xxx/vagrant-test/Vagrantfile? (enter "h" for help) [Ynaqdh] n
        skip  Vagrantfile
Successfully initialized

今回は使用しないので、Thorfileを削除します。って今回Thor始めて知りました。 あと、.gitignoreのGemfile.lock行を消しておきます。この辺りは好みの問題かもしれませんが。。。

$ rm Thorfile
$ vi .gitignore
- snip -
Gemfile.lock   # この行を削除
- snip -

Berkfileを編集し、サードパーティのrvm cookbookを定義

$ vi Berksfile 
site :opscode
cookbook 'apt'
cookbook 'rvm', github: 'fnichol/chef-rvm'

knife soloコマンドでchef用にディレクトリを初期化。cook-books、rolesディレクトリ等が作成されます。

$ bundle exec knife solo init .
$ tree -L 2
.
├── Berksfile
├── Gemfile
├── Gemfile.lock
├── Vagrantfile
├── cookbooks
├── data_bags
├── environments
├── nodes
├── roles
├── site-cookbooks
└── vendor
    └── bundle

8 directories, 4 files

サードパーティのcookbookをinstall

$ bundle exec berks install --path cookbooks

自分のcookbookを作成して、recipeを作成。

$ bundle exec knife cookbook create myapp -o site-cookbooks/
$ vi site-cookbooks/myapp/recipes/default.rb
%w{apache2 mysql-server}.each do |each|
  package "#{each}" do
    action :install
  end
end

service "apache2" do
  action[ :enable, :start]
  supports :status => true, :restart => true, :reload => true
end

service "mysql" do
  action[ :enable, :start]
  supports :status => true, :restart => true, :reload => true
end

Berksfileに自分のcookbookを追加

$ vi Berksfile 
site :opscode
cookbook 'apt'
cookbook 'rvm', github: 'fnichol/chef-rvm'
cookbook 'myapp', path: './site-cookbooks/myapp'   # この行を追加

Vagrantfileのprovisionに作成したrecipeを記載 あわせて、config.vm.network行を追加しておきます。(後でapacheの動作確認で使います)

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

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "precise64"
  config.vm.box_url = "http://files.vagrantup.com/precise64.box"
  config.vm.network :forwarded_port, guest: 80, host: 8080

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
    chef.add_recipe "rvm::system"
    chef.add_recipe "rvm::vagrant"
    chef.add_recipe "myapp"                        # myapp::default を省略した記載方法

    chef.json = {
      "rvm" => {
        "default_ruby" => "ruby-1.9.3-p448",
        "global_gems" => [
          {"name" => "bundler"}
        ]
      }
    }
  end
end

それでは、このVagrantfileのprovisionを実際に反映させましょう。下記コマンドで、Vagrnatfileの上述のprovision部分を読み込みます。

$ vagrant provision

もしくは、下記のように、step4で作成した仮想マシンを一旦削除し、あらためて仮想マシンを作成してもいいです。
vagrant1.3.0以降では、最初のvagrant up実行時にprovision部分の読み込みを実行します。(詳細は後述)

$ vagrant destroy  
$ vagrant up  

仮想マシン起動が、ssh仮想マシンにアクセス。

$ vagrant ssh  

仮想マシン上のrubyのバージョンを確認し、ruby-1.9.3-p448になっている事を確認します。

$ ruby -v
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]

MySQLアクセスにもアクセスしてみましょう。

$ service mysql status
$ mysql -uroot

apacheが起動している事を確認しましょう。

$ service apache2 status

ローカルPCのブラウザからアクセスできることを確認

http://localhost:8080

6. ハマりポイント

  • 自分で作成したcookbook使用時、BerksfileとVagarantfileの書き方

Berksfileでpath:を記載するのがポイントです。

$ vi Berksfile
-snip-
cookbook 'myapp', path: './site-cookbooks/myapp'

$ vi Vagrantfile
-snip-
config.vm.provision :chef_solo do |chef|
  chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
-snip-
  chef.add_recipe "myapp"
-snip-
end
  • rvm cookbook使用時、BerksfileとVagarantfileの書き方

コメント部分がポイントです。

$ vi Berksfile 
site :opscode
cookbook 'apt'                              # この行を忘れずに
cookbook 'rvm', github: 'fnichol/chef-rvm'
- snip -

$ vi Vagrantfile
- snip -
config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
- snip -
    chef.add_recipe "rvm::system"
    chef.add_recipe "rvm::vagrant"           # この行を忘れずに
- snip -
    chef.json = {
      "rvm" => {
      "default_ruby" => "ruby-1.9.3-p448",
      "global_gems" => [
        {"name" => "bundler"}
      ]
    }
  }
end

特にBerksfileでcookbook 'apt'を記載しなかった場合、下記エラーが発生しました。

- snip -
[2013-11-04T08:25:53+00:00] INFO: execute[install system-wide RVM] ran successfully

================================================================================
Error executing action `install` on resource 'package[libxml2-dev]'
================================================================================

Chef::Exceptions::Exec
----------------------
apt-get -q -y install libxml2-dev=2.7.8.dfsg-5.1ubuntu4.1 returned 100, expected 0
- snip -
  • 初めてvagrant up実行の注意ポイント

下記errorが発生します。

FATAL: ArgumentError: You must specify at least one cookbook repo path

その場合下記コマンドを実施すると正常起動しました。

vagrant reload
vagrant provision

これは~/.berkshelfが存在しない場合、vagrant up実施時に発生する事象です。
berkshelf3.0で修正済と記載がありますが、berkshelf3.0.0.beta2で試したところ、依然NGでした。
https://github.com/RiotGames/vagrant-berkshelf/issues/78

  • Vagrant synced_folder(nfs有効)の不具合対応

MacOSで、synced_folder(nfs有効)で起動失敗する場合、/etc/exportsを削除後、touchコマンドで/etc/exportsを再作成し、nfsd停止、起動をするとsynced_folderが正常起動します。
http://www.1x1.jp/blog/2013/08/vagrant_synced_folder_with_nfs.html

nfsを使ったほうがdiskアクセスの性能が良いです。(但し、nfs機能はMacOSのみサポート) http://docs.vagrantup.com/v2/synced-folders/nfs.html

また、vagrant up実行途中でnfs mountのpwを聞かれたら、ローカルPCのrootのPWを入力してください。 もしrootユーザを設定していない場合は、下記を参考にrootを有効にして下さい。 http://support.apple.com/kb/ht1528?viewlocale=ja_JP&locale=ja_JP

  • Vagrant 1.3.0からvagrant up動作変更

vagrant upコマンドでVagrantfileのprovision部分が実行されるのは最初の1回目だけです。
(vagrant halt実行後)2回目以降の仮想マシンの起動でprovision部分を実行したい場合、下記コマンドを実行する必要があります。
(ChefSoloを使っている場合、Chefの冪等性の動作のため、基本的に毎回provision部分を実行する必要性はないはずですが、まちクエストは事情により、毎回provisionで特定のrecipeを実行する必要性があるのです)

$ vagrant up --provision

https://github.com/mitchellh/vagrant/blob/master/CHANGELOG.md

  • bundler 1.4.0.pre.1以降、bundle installの並列処理ができるようになっています

Bundlerで並列処理??bundle installを爆速で処理する方法。
今回のまちクエストの開発環境構築では、あまり効果がなかったのとpreということもあり、利用は見合わせました。
ただし、bundle installの時間短縮をしたい場合も出てくると思うので、覚えておくといいです。

7. サンプル

上記で説明したファイルをgithubに保存しますのでご参照下さい。
Github Vagrant-test

8. まとめ

  • まちクエストの開発環境Vagrant化の経験をもとに、Vagrant+ChefSoloの主な操作方法およびハマりポイントについて説明しました。
  • 今回説明で用いたソフトウェアおよびバージョンは、実際のまちクエストで使用しているソフトウェア、バージョンとは異なる部分があります。ご了承ください。
  • 今後の課題
    • 今回まちクエストのアプリケーション部分の自動化については、chefのbashリソースを用いて、shellスクリプトを実行しています。
      最近になって、chefのdeploy resourceを知ったのですが、本当はdeploy resourceを使って自動化した方がよさげです。
      また、まちクエストではcapistranoを使って管理しているので、deploy resourceとの親和性もよさそうです。
      http://docs.opscode.com/resource_deploy.html

9. 参考情報

今っぽい Vagrant + Chef Solo チュートリアル
Berkshelfベースにvagrant, chef(knife-solo)環境を簡単に構築する方法
dotinstall Vagrant入門
入門Chef Solo - Infrastructure as Code
dotinstall Chef入門
RubyistMagazineChef でサーバ管理を楽チンにしよう! (第 1 回)

© Machiquest