Ruby on Rails の Cookbook を確認する
ChefsMeetingNagoya Vol.1. ChefsMeetingNagoya Vol.2 までの話を踏まえて、もう一つお題を出しました。
お題は、Ruby on Rails をインストールする Cookbook です。
今回はこの Cookbook をどのように作ったか確認してみましょう。
Ruby on Rails とは
Ruby で書かれた Web アプリケーションフレームワークです。
http://rubyonrails.org/
国内外問わず様々な WEB サービスで利用されているフレームワークですので、聞いたことがある方が多いのではないでしょうか。
一般的に RoR, rails と略して表記されていますので、このページでも rails と表記しています。
インストール方法をおさらい
まずは CentOS6 で rails を手でインストールする方法をおさらいしておきます。
いろいろなインストール方法があるかと思いますが、今回はこの手順で構築した rails 環境を Recipe で定義することにします。
rbenv をインストール
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
ruby-build を rbenv のプラグインとしてインストール
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv で Ruby をインストール
$ sudo yum install -y gcc-c++ glibc-headers glibc-devel openssl-devel readline libyaml-devel readline-devel zlib zlib-devel $ rbenv install 2.1.0
インストールした Ruby を標準で使うように設定
$ rbenv global 2.1.0
gem で rails をインストール
$ gem install rails
Cookbook の一例の紹介と解説
以下、rails Cookbook の一例をポイント毎に分けながら紹介します。
完全な Cookbook は git リポジトリで管理されているので、こちら を参照してください。
Cookbook 全体の方針として、サンプルとして大規模になり過ぎないように、rails が動作する最低限の環境を構築することを目標としています。
ruby_block 'disable requiretty' do block { Chef::Util::FileEdit.new('/etc/sudoers').tap { |file| file.write_file if file.search_file_replace_line(/^Defaults[ \t]+requiretty/, '#Defaults requiretty') } } action :nothing end.run_action(:run) cmd_prefix = "sudo -iu #{node[cookbook_name]['user']}"
rbenv はインストールユーザーの環境変数をセットしないと動きません。
後々、execute Resource で rbenv コマンドを実行するときに、インストールユーザーの環境変数を適用するために sudo が必要になります。
sudo を実行するときに、そのままでは tty を要求されて sudo が失敗してしまうので、ruby_block Resource で tty 要求をしないように設定しています。
sudo の設定は /etc/sudoers の 'Defaults requiretty' という行をコメントアウトすれば OK です。
Chef ではファイルの一部を編集するライブラリ Chef::Util::FileEdit が提供されています。
search_file_replace_line メソッドを使えば、第1引数の正規表現に一致する行を第2引数の文字列に置換できるので、
これらを利用して 'Defaults requiretty' をコメントアウトしています。
最後に write_file メソッドを実行して、ファイルの編集を適用します。
次に出てくる Mixlib::ShellOut で sudo を使ってインストールユーザの環境変数を取得しますが、その実行タイミングは Recipe のコンパイル時です。
なので ruby_block Resource が収束する前に実行され失敗してしまいます。
そこで run_action メソッドを使って ruby_block Resource を Recipe のコンパイル時に実行しています。
Resourceはクラスとして定義されていて、ruby_block Resource も Chef::Resource::RubyBlock というクラスです。
その親クラスである Chef::Resource クラスのメソッドとして run_action メソッドは定義されています。
.run_action(:run) なしの場合
ruby_block 'disable requiretty' do block { Chef::Util::FileEdit.new('/etc/sudoers').tap { |file| file.write_file if file.search_file_replace_line(/^Defaults[ \t]+requiretty/, '#Defaults requiretty') } } action :run end
Mixlib::ShellOut の run_command はコンパイル時に実行されます。
ruby_block はまだ収束されていないので、sudo は失敗します。
.run_action(:run) ありの場合
ruby_block 'disable requiretty' do block { Chef::Util::FileEdit.new('/etc/sudoers').tap { |file| file.write_file if file.search_file_replace_line(/^Defaults[ \t]+requiretty/, '#Defaults requiretty') } } action :nothing end.run_action(:run) cmd_prefix = "sudo -iu #{node[cookbook_name]['user']}"
正確には収束時も ruby_block は評価されていますが、action Attribute が :noting なので実行はされません。
Recipe のプロビジョニングのフェーズに関しては Cookbook の作成(ChefsMeetingNagoya Vol.1)#基本的な用語 を参照してください。
sudo を使うコマンドはこのあと何回も出てくるので、変数化しておきます。
home = Mixlib::ShellOut.new("#{cmd_prefix} sh -c 'echo $HOME'").run_command.stdout.chomp
ここではインストールユーザーのホームディレクトリを取得しています。
Mixlib::ShellOut はコマンドを実行するための Chef のライブラリです。
`sudo -iu kitchen sh -c 'echo $HOME'` など、Ruby 標準の機能を使うこともできるのですが、Chef では Mixlib::ShellOut を使うことが推奨されています。
package 'git' rbenv = git File.join(home, '.rbenv') do user node[cookbook_name]['user'] group node[cookbook_name]['user'] repository node[cookbook_name]['rbenv']['repository'] action :checkout end
ここで git リポジトリから rbenv を clone しています。
Ruby のFile.join でパス文字列を安全に結合できるので、それを git Resource の destination Attribute に指定しています。
destination Attribute はデフォルトで name になります。
ruby_block 'add rbenv setting' do block { Chef::Util::FileEdit.new(File.join(home, '.bash_profile')).tap { |file| lines = [] lines << "export PATH=\"#{File.join(rbenv.destination, 'bin')}:$PATH\"" lines << 'eval "$(rbenv init -)"' lines.map { |line| [Regexp.escape(line), line] }.each { |regexp, line| file.write_file if file.insert_line_if_no_match(regexp, line) } } } not_if "#{cmd_prefix} which rbenv" end
ここで、インストールユーザの環境変数に rbenv を動作させるために必要な値をセットしています。
値をセットするためのファイルの編集には Chef::Util::FileEdit クラスを使用しています。
insert_line_if_no_match で第1引数の正規表現と各行が一致しない場合は、ファイルの末尾に第2引数を追記します。
write_file をして、実際にファイルを更新してます。
not_if ではユーザに対して rbenv がインストールされているかを確認するため、which コマンドを使用しています。
which はコマンドが見つからないときに 0 以外を返すので、この Resource は rbenv コマンドが見つからないときだけ収束されます。
package 'tar' plugins = directory File.join(rbenv.destination, 'plugins') do user node[cookbook_name]['user'] group node[cookbook_name]['user'] mode '0755' end git File.join(plugins.path, 'ruby-build') do user node[cookbook_name]['user'] group node[cookbook_name]['user'] repository node[cookbook_name]['ruby_build']['repository'] action :checkout end
ここで git リポジトリから ruby-build を clone しています。
ファイルやディレクトリを扱う Resouce で user Attribute を指定をするときは group Attribute も指定をします。
group Attribute も指定しないと、期待したパーミッションにならないことがあります。
%w{ gcc-c++ libxml2-devel libxslt-devel openssl-devel readline-devel zlib-devel }.each { |package_name| yum_package package_name do options '--enablerepo=centosplus' if platform?('centos') end }
ここでは、Ruby 実行環境のビルドに必要なパッケージをインストールしています。
openssl-devel は centos のデフォルトのリポジトリからではインストールできません。
そのためパッケージのインストール先が centos の場合に、centosplus リポジトリを指定するようにしています。
パッケージのインストール先のプラットフォームの判定には Chef の platform? メソッドを使用します。
また、platform_family? メソッドを使って、プラットフォームファミリーを判定することもできます。
ruby_version = node[cookbook_name]['ruby']['version'] execute "#{cmd_prefix} rbenv install #{ruby_version}" do not_if "#{cmd_prefix} rbenv versions | grep ' #{ruby_version} '" end execute "#{cmd_prefix} rbenv global #{ruby_version}" do not_if "#{cmd_prefix} test $(rbenv global) = #{ruby_version}" end
ここでは、rbenv を使って Ruby のビルドとインストールを行っています。
execute resouce はデフォルトで name が commnad Attribute にセットされ収束します。
package 'sqlite-devel' gem_package 'rails' do gem_binary File.join(rbenv.destination, 'shims/gem') version node[cookbook_name]['version'] options '--no-ri --no-rdoc' end
ここでは、gem を使って rails のインストールをしています。
また、rails プロジェクトの作成に必要なパッケージのインストールをしています。
gem_package Resource は gem_binary Attribute でインストールユーザの gem のパスを指定しています。
service 'iptables' do action :disable end
ここでは、iptables service を disable にしています。
本来 iptables を disable にするのはご法度ですが、今回は Recipe をなるべく小規模にするためにこうしています。
コメントの追加