ページ ツリー
メタデータの末尾にスキップ
メタデータの先頭に移動

Ruby on Rails の Cookbook を確認する

ChefsMeetingNagoya Vol.1ChefsMeetingNagoya Vol.2 までの話を踏まえて、もう一つお題を出しました。
お題は、Ruby on Rails をインストールする Cookbook です。 
今回はこの Cookbook をどのように作ったか確認してみましょう。 

 

Ruby on Rails とは

Ruby で書かれた Web アプリケーションフレームワークです。
http://rubyonrails.org/

国内外問わず様々な WEB サービスで利用されているフレームワークですので、聞いたことがある方が多いのではないでしょうか。 
一般的に RoR, rails と略して表記されていますので、このページでも rails と表記しています。

 

インストール方法をおさらい

まずは CentOS6 で rails を手でインストールする方法をおさらいしておきます。
いろいろなインストール方法があるかと思いますが、今回はこの手順で構築した rails 環境を Recipe で定義することにします。

 

  1. 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



  2. ruby-build  を rbenv のプラグインとしてインストール

    $ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build



  3. 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 



  4. インストールした Ruby を標準で使うように設定

    $ rbenv global 2.1.0



  5. 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 をなるべく小規模にするためにこうしています。

  • ラベルがありません
コメントを書く…