itamaeを使って開発環境を構築してみる

# はじめに
新しい人がチームに入った場合に、最初にやってもらうこととして開発環境構築があると思います。で、プロダクトが最初の頃はまだそれほどコード量も多くなく、使っているミドルウェアも少ないので環境構築もそれほど苦ではないと思いますが、月日が経ちコード量も増え、使っているミドルウェアが多くなってくると、その分環境を新しく作るのが大変になってきます。
そういう時に[Chef](https://www.chef.io/)や[Ansible](http://www.ansible.com/)、そして今回紹介する[itamae](https://github.com/itamae-kitchen/itamae)が役に立つと思います。

筆者もChef（正確にはChef Solo）を使っていた時期もありました。ただChef Soloが終了となり、Chef Zeroの切り替えてということになりました。そこでChef Zeroを使おうとやってみたのですが乗り換えが結構大変だったので、これを機に別にものを使おうと思い検討た結果itamaeを使うことにしました。

理由は
* ライトに使える
* 覚えることが少ない
* sshが使える(Chefとの大きな違い)
といった点からです。

この記事ではitamaeを使って環境構築をする例を紹介しようと思います。

# 環境
- ruby2.2
- github
- elasticsearch2
- redis
- java 1.8
- itamae 1.9
- vagrant

# レシピ
今回やることは次の項目をやっていきます。
1. ruby、javaのインストール
2. elasticsearch、redis等ミドルウェアのインストール
3. githubから対象リポジトリを取得

## レシピの構成
先日[itamae meet up](http://itamae.connpass.com/event/22857/)が開催され、その時にフォルダ構成の話も出ました。今回はその時紹介されたような構成に変更するのが間に合わなかったので若干「むむむ！！」というところがあるかもしれませんが、そこはご了承いただければと思います。
簡単にいうと以下のようにフォルダを分けています。

```
- recipes
  - elasticsearch
  - redis
  app.rb
- nodes
- templates

```

## nodeの設定
nodeの設定はyaml形式でも書けますが、今回はjsonの形式で書かせていただきます。

```nodes/app.json
{
  "rbenv": {
    "user": "vagrant",
    "versions": ["2.2.1", "2.2.2"],
    "global": "2.2.2",
    "gems": ["bundler", "rails"]
  },
  "elasticsearch": {
    "cluster_name": "nakamura-elasticsearch",
    "version": "2.1.0",
    "index_shards_num": "5",
    "index_replicas_num": "1"
  },
  "redis": {
    "version": "3.0.4"
  },
  "git": {
    "git_user": "#{ENV['git_user']}",
    "git_password": "#{ENV['git_password']}",
    "repository": "hoge/fuga_repo",
    "app_path": "rails_app"
    }
}
```

## ruby、javaのインストール
今回rubyのインストールは公開されているプラグインを使います。javaはpackageで提供されているものを使うことにします。java以外にもgitとか後々使うので、ここで一緒にインストールしてしまいます。

```Gemfile
gem 'itamae-plugin-recipe-rbenv'
```

```recipes/app.rb
%w( yum wget java-1.8.0-openjdk git ).each do |pkg|
  package pkg do
    action :install
  end
end

# rubyをinstall
include_recipe "rbenv::user"
```

## elasticsearch、redis等ミドルウェアのインストール
続いてミドルウェアを入れます。
mysqlやelasticsearch、redisのレシピは別のファイルで書いて、cookbooks/app.rbの中ではそれをinclude_recipeすることにします。
elasticsarchのレシピは次のようになります

```recipes/elasticsearch/elasticsearch.rb
if  /\A1.*/ =~ node[:elasticsearch][:version]
  include_recipe 'version/1_x.rb'
else
  include_recipe 'version/2_x.rb'
end
```

バージョン毎にレシピを分けています。

```recipes/elasticsearch/elasticsearch1.rb
%w( yum java-1.8.0-openjdk wget ).each do |pkg|
  package pkg do
    action :install
  end
end

execute 'rm elasticsearch.tar.gz' do
  only_if "test -e ~/elaticsearch.tar.gz"
end

execute 'elasticsearch file get' do
  command "wget https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-#{node[:elasticsearch][:version]}.tar.gz -O elasticsearch.tar.gz"
end

execute 'file unzip' do
  command 'tar -zxf elasticsearch.tar.gz'
end

execute 'sudo rm -R /usr/local/share/elasticsearch' do
  only_if 'ls /usr/local/share/elasticsearch'
end

execute 'sudo mv elasticsearch-* /usr/local/share/elasticsearch' do
  not_if 'ls /usr/local/share/elasticsearch'
end

execute 'sudo chmod -R 755 /usr/local/share/elasticsearch' do
  only_if 'ls /usr/local/share/elasticsearch'
end

execute 'mkdir /usr/local/share/elasticsearch/plugins' do
  not_if 'ls /usr/local/share/elasticsearch/plugins'
end

# elasticsearch.ymlをコピー
template "/usr/local/share/elasticsearch/config/elasticsearch.yml" do
  path "/usr/local/share/elasticsearch/config/elasticsearch.yml"
  source "../../../templates/elasticsearch/config/elasticsearch_yml.erb"
  variables({cluster_name: 'nakamura-elasticsearch', index_shards_num: "5", index_replicas_num: "1"})
end

# プラグイン
execute 'bin/plugin --remove mobz/elasticsearch-head' do
  only_if 'cd /usr/local/share/elasticsearch/plugins/head'
  cwd '/usr/local/share/elasticsearch'
end

execute 'bin/plugin -install mobz/elasticsearch-head' do
  cwd '/usr/local/share/elasticsearch'
end

execute 'bin/plugin --remove elasticsearch/marvel/latest' do
  only_if 'cd /usr/local/share/elasticsearch/plugins/mavel'
  cwd '/usr/local/share/elasticsearch'
end

execute 'bin/plugin -install elasticsearch/marvel/latest' do
  cwd '/usr/local/share/elasticsearch'
end

execute "bin/plugin --remove elasticsearch/elasticsearch-analysis-kuromoji/#{node['elasticsearch']['plugin']['kuromoji']['version']}" do
  only_if 'cd /usr/local/share/elasticsearch/plugins/analysis-kuromoji'
  cwd '/usr/local/share/elasticsearch'
end

execute "bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/#{node['elasticsearch']['plugin']['kuromoji']['version']}" do
  cwd '/usr/local/share/elasticsearch'
end
```

version2を使った場合

```recipes/elasticsearch/version/2_x.rb
%w( yum java-1.8.0-openjdk wget ).each do |pkg|
  package pkg do
    action :install
  end
end

execute 'rm elasticsearch.tar.gz' do
  only_if "test -e ~/elaticsearch.tar.gz"
end

execute 'elasticsearch file get' do
  command "wget https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/#{node[:elasticsearch][:version]}/elasticsearch-#{node[:elasticsearch][:version]}.tar.gz -O elasticsearch.tar.gz"
end

execute 'file unzip' do
  command 'tar -zxf elasticsearch.tar.gz'
end

execute 'sudo rm -R /usr/local/share/elasticsearch' do
  only_if 'ls /usr/local/share/elasticsearch'
end

execute 'sudo mv elasticsearch-* /usr/local/share/elasticsearch' do
  not_if 'ls /usr/local/share/elasticsearch'
end

execute 'sudo chmod -R 755 /usr/local/share/elasticsearch' do
  only_if 'ls /usr/local/share/elasticsearch'
end

execute 'mkdir /usr/local/share/elasticsearch/plugins' do
  not_if 'ls /usr/local/share/elasticsearch/plugins'
end

# プラグイン
## HEAD
execute 'bin/plugin remove mobz/elasticsearch-head' do
  only_if 'cd /usr/local/share/elasticsearch/plugins/head'
  cwd '/usr/local/share/elasticsearch'
end

execute 'bin/plugin install mobz/elasticsearch-head' do
  cwd '/usr/local/share/elasticsearch'
end

## kuromoji
execute "bin/plugin remove analysis-kuromoji" do
  only_if 'cd /usr/local/share/elasticsearch/plugins/analysis-kuromoji'
  cwd '/usr/local/share/elasticsearch'
end

execute "bin/plugin install analysis-kuromoji" do
  cwd '/usr/local/share/elasticsearch'
end

# elasticsearch.yml
template "/usr/local/share/elasticsearch/config/elasticsearch.yml" do
  path "/usr/local/share/elasticsearch/config/elasticsearch.yml"
  source "../../../templates/elasticsearch/config/elasticsearch2_yml.erb"
  variables({cluster_name: 'nakamura-elasticsearch', index_shards_num: "5", index_replicas_num: "1"})
end

execute "bin/elasticsearch -d" do
  cwd '/usr/local/share/elasticsearch'
end
```
elasticsearchはバージョン1系と2系で変わったところがあるので、今回レシピも分けて書くようにしました。どちらを使うかはnodeの中の値で変わるようにしました。

続いてredisです。

```recipes/redis/redis.rb
%w( yum wget ).each do |pkg|
  package pkg do
    action :install
  end
end

execute "rm redis-#{node['redis']['version']}.tar.gz" do
  only_if "test -e redis-#{node['redis']['version']}.tar.gz"
end

execute 'redis file get' do
  command "wget http://download.redis.io/releases/redis-#{node['redis']['version']}.tar.gz"
end

execute 'unzip' do
  command "tar -zxf redis-#{node['redis']['version']}.tar.gz"
end

execute 'make' do
  cwd "redis-#{node['redis']['version']}"
end
```

有効なプラグインがあれば、それに乗り換えようと思っています。Chefに比べてitamaeはまだプラグインが十分でないので。若干力技な感じがしつつ、取り敢えずということで。

## githubから対象リポジトリを取得
次にgithubからレシピは次のようになります。

```recipes/app.rb
# アプリケーションの配置場所を作成
directory "#{node[:git][:app_path]}" do
  mode "755"
end

git "#{node[:git][:app_path]}" do
  repository "https://#{node[:git][:git_user]}:#{node[:git][:git_password]}@github.com/#{node[:git][:repository]}.git"
end
```

実行は次のようにやればできます。
ローカルの場合

```
bundle exec itamae local -j [nodeファイルを指定] -h [ホストを指定] [レシピファイルを指定]
```

SSH経由で他のマシンに構築する場合

```
bundle exec itamae ssh -j [nodeファイルを指定] -h [ホストを指定] [レシピファイルを指定]
```

vagrantを使ってVM上に構築する場合

```
bundle exec itamae ssh -j [nodeファイルを指定] -h [ホストを指定] --vagrant [レシピファイルを指定]
```

以上で必要なミドルウィエアのインストール等をitamaeで自動化してみました。これにより後はitamaeさえ動けば、環境構築が楽にできますね！

## 補足
先日のitamae meetup1で発表があった事の共有になるのですが、レシピの実行に「どこにどのくらいの時間がかかっているのかを見たい」という場合は、itamaeが最新のバージョンであれば、<code>--profile profile.json</code>とprofileオプションを実行時に指定するとプロファイリングの結果がjson形式で出力されます。あまりにも実行が遅い、何故だろうって思ったら使ってみると良いなと思いました。


## 複数のマシンに対してレシピをそれぞれ役割に応じて適用したい場合
rundockというgemを使うと簡単にできます。どのマシンに何を適用するかをyml形式のファイルに記述し実行時に、そのファイルを指定すると各マシンに必要な設定を入れることができます。ロール単位でレシピを記述し、それをrundockで指定します。

### rundockのインストール
Gemfileに追加してbundle installでインストールします。

```Gemfile
# rundocによる複数ホストの実行
gem 'rundock'
gem 'rundock-plugin-operation-itamae'

```

### rundock.yml
rundockの設定の例です。dev-el2とdev-el4というVM上に作った2つのローカル環境にそれぞれミドルウェアをitamaeのレシピを指定してインストールしています。このようにしておけば、各環境ごとにyamlファイルを作成し、その中で必要なレシピを指定するように書いておけば、どの環境にどのバージョンの何が入っているのかが一目瞭然になります。

```
- target: dev-el2
  itamae:
    - cookbooks/redis_elasticsearch.rb
    - node_json: nodes/redis_elasticsearch.json
    - log_level: info
    - sudo: true
- target: dev-el4
  itamae:
    - recipes/redis/redis.rb
    - node_json: nodes/redis.json
    - log_level: info
    - sudo: true
---
dev-el2:
  host: 127.0.0.1
  ssh_opts:
    port: 2200
    user: vagrant
    key: /usr/local/project/itamae_recipe/.vagrant/machines/el4/virtualbox/private_key
dev-el4:
  host: 127.0.0.1
  ssh_opts:
    port: 2222
    user: vagrant
    key: /usr/local/project/itamae_recipe/.vagrant/machines/el2/virtualbox/private_key
---
```

まだレシピの書き方やフォルダ構成など見直す点が多々あると思っていますが、他の方々の何かの役に立つ事が出来たら幸いです。


## 参考
- [itamae meetup](https://speakerdeck.com/ryotarai/opening-itamae-meetup-number-1)