Ruby
serverspec
itamae

Itamae + rbenvでCentOSにRuby環境を構築

More than 1 year has passed since last update.

Itamaeとは?

  • クックパッド社@ryot_a_raiさん作のプロビジョニングツール
  • RubyDSLで表現できるChefをライトにしたもの
  • 先日クックパッド社で開催されたInfrastructure as Code 現状確認会内でv1.0.0がリリースされたそう(Serverspecもv2.0.0がリリースされたそう)
  • クックパッド社では既に一部のプロダクションで利用しているらしい
  • 詳しくはryot_a_raiさんが発表されたスライドにて↓

itamae-infra-as-code-xian-zhuang-que-ren-hui.jpg
From Itamae - Infra as Code 現状確認会

前準備

  • VagrantなどでCentOSの環境を用意しておく
  • ローカルでbundlerを使えるようにしておく

ゴール

  • Ruby2.1.1(確認用)と2.1.2がインストールされている
  • グローバルは2.1.2とする
  • bundlerがインストールされている
  • 確認はServerspecで

プロビジョニング

Itamaeを使う準備をする

Itamae用のディレクトリ作成(特に決められた構成はない)

$ mkdir -p itamae/recipes/remote_files itamae/nodes
$ cd itamae

必要なGemをインストール

itamae/Gemfile
source 'https://rubygems.org'

gem 'itamae'
gem 'rake'
gem 'serverspec'
$ bundle install --path=vendor/bundle

レシピを書く

itamae/recipes/ruby_build.rb
package "epel-release"
package "gcc"
package "openssl-devel"
package "libyaml-devel"
package "readline-devel"
package "zlib-devel"
package "git"

RBENV_DIR = "/usr/local/rbenv"
RBENV_SCRIPT = "/etc/profile.d/rbenv.sh"

git RBENV_DIR do
  repository "git://github.com/sstephenson/rbenv.git"
end

remote_file RBENV_SCRIPT do
  source "remote_files/rbenv.sh"
end

execute "set owner and mode for #{RBENV_SCRIPT} " do
  command "chown root: #{RBENV_SCRIPT}; chmod 644 #{RBENV_SCRIPT}"
  user "root"
end

execute "mkdir #{RBENV_DIR}/plugins" do
  not_if "test -d #{RBENV_DIR}/plugins"
end

git "#{RBENV_DIR}/plugins/ruby-build" do
  repository "git://github.com/sstephenson/ruby-build.git"
end

node["rbenv"]["versions"].each do |version|
  execute "install ruby #{version}" do
    command "source #{RBENV_SCRIPT}; rbenv install #{version}"
    not_if "source #{RBENV_SCRIPT}; rbenv versions | grep #{version}"
  end
end

execute "set global ruby #{node["rbenv"]["global"]}" do
  command "source #{RBENV_SCRIPT}; rbenv global #{node["rbenv"]["global"]}; rbenv rehash"
  not_if "source #{RBENV_SCRIPT}; rbenv global | grep #{node["rbenv"]["global"]}"
end

node["rbenv"]["gems"].each do |gem|
  execute "gem install #{gem}" do
    command "source #{RBENV_SCRIPT}; gem install #{gem}; rbenv rehash"
    not_if "source #{RBENV_SCRIPT}; gem list | grep #{gem}"
  end
end
itamae/recipes/remote_files/rbenv.sh
export RBENV_ROOT="/usr/local/rbenv"
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init --no-rehash -)"

ノードを定義する

itamae/nodes/node.json
{
  "rbenv": {
    "versions": ["2.1.1", "2.1.2"],
    "global": "2.1.2",
    "gems": ["bundler"]
  }
}

実行する

$ bundle exec itamae ssh -h centos -u vagrant -j nodes/node.json recipes/ruby_build.rb

期待通りプロビジョニングされているかServerspecで確認する

specを書く

itamae/spec/centos/ruby_buld_spec.rb
require 'spec_helper'

describe command('source /etc/profile.d/rbenv.sh; which rbenv') do
  let(:disable_sudo) { true }
  its(:stdout) { should match %r{/usr/local/rbenv/bin/rbenv} }
end

describe file('/etc/profile.d/rbenv.sh') do
  it { should be_file }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should be_mode 644 }
  its(:content) { should match(/^export RBENV_ROOT="\/usr\/local\/rbenv"$/) }
  its(:content) { should match(/^export PATH="\${RBENV_ROOT}\/bin:\${PATH}"$/) }
  its(:content) { should match(/^eval "\$\(rbenv init --no-rehash -\)"$/) }
end

describe file('/usr/local/rbenv/plugins') do
  it { should be_directory }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should be_mode 755 }
end

describe file('/usr/local/rbenv/plugins/ruby-build') do
  it { should be_directory }
  it { should be_owned_by 'root' }
  it { should be_grouped_into 'root' }
  it { should be_mode 755 }
end

%w(2.1.1 2.1.2).each do |versoin|
  describe command("source /etc/profile.d/rbenv.sh; rbenv versions | grep #{versoin}") do
    let(:disable_sudo) { true }
    its(:stdout) { should match(/#{Regexp.escape(versoin)}/) }
  end
end

describe command('source /etc/profile.d/rbenv.sh; rbenv global') do
  let(:disable_sudo) { true }
  its(:stdout) { should match(/2.1.2/) }
end

specを実行する

$ bundle exec rake spec spec/centos/ruby_buld_spec.rb
実行結果
Command "source /etc/profile.d/rbenv.sh; which rbenv"
  stdout
    should match /\/usr\/local\/rbenv\/bin\/rbenv/

File "/etc/profile.d/rbenv.sh"
  should be file
  should be owned by "root"
  should be grouped into "root"
  should be mode 644
  content
    should match /^export RBENV_ROOT="\/usr\/local\/rbenv"$/
  content
    should match /^export PATH="\${RBENV_ROOT}\/bin:\${PATH}"$/
  content
    should match /^eval "\$\(rbenv init --no-rehash -\)"$/

File "/usr/local/rbenv/plugins"
  should be directory
  should be owned by "root"
  should be grouped into "root"
  should be mode 755

File "/usr/local/rbenv/plugins/ruby-build"
  should be directory
  should be owned by "root"
  should be grouped into "root"
  should be mode 755

Command "source /etc/profile.d/rbenv.sh; rbenv versions | grep 2.1.1"
  stdout
    should match /2\.1\.1/

Command "source /etc/profile.d/rbenv.sh; rbenv versions | grep 2.1.2"
  stdout
    should match /2\.1\.2/

Command "source /etc/profile.d/rbenv.sh; rbenv global"
  stdout
    should match /2.1.2/

Finished in 1.81 seconds (files took 0.50842 seconds to load)
19 examples, 0 failures

期待通りプロビジョニングされていることが確認できました! ItamaeServerspecステキ!!

所感

  • スライドにも記載があったが、実行にかかる時間はChefよりも速い
  • 構成がシンプルで登場人物も少ないので学習コストは非常に低いと感じた
  • リソースやアトリビュートはChefと近いものが多いので、Chefの知識は活かせる
  • chef-soloをchef-zeroに移行...とかいう話も無縁
  • ChefのDataBagに相当する機能が使えるようになるとさらに嬉しい
  • 業務で導入したいのでまずは色々と使ってみる

こんな方は是非試してみるべき(個人的な見解)

  • PuppetやChef、Ansible等プロビジョニングのツールに触れたことがない方
  • Chefを導入したものの、高機能過ぎてちょっと手に余ると思っている方
  • 上記理由でChefからAnsibleに乗り換えたけど、やっぱりRubyDSLで書きたい方
  • Chefの有識者がおらず気づいたら自分一人で保守してる(Chefおじさん的な存在になっている)方

謝辞

参考サイト

追記

Itamaeで提供されているリソースをまとめたものも記事にしましたので、こちらもご参考になれば幸いです。
Itamaeチートシート