Chef-soloからItamaeに完全移行した話

※2016/04/24 追記
昨年末にItamae meetupで話した時のスライドリンクを追記しました。
Databag > itamae-secret の話やConsul連携の話が追加されています。 
http://www.slideshare.net/tsuyoshitorii5/itamae-meetup-vol1public

<br>


現在自分が運用管理しているChef-soloプロビジョニングの仕組み [^1] を
Itamaeに移行した時のお話をしようと思います。
管理規模としては大規模ではなく、小〜中規模的なところかと思います。
（ロールによってレシピ切り分けたり、環境毎にレシピ用意したりなど…）

<br>

## 最初に: Itamaeについて
https://github.com/itamae-kitchen/itamae
**軽量なChef** と考えればよいでしょう。
Chefの複雑さを取り除き、必要十分な部分のみ機能として提供しています。
[こちらの資料](https://speakerdeck.com/ryotarai/itamae-infra-as-code-xian-zhuang-que-ren-hui) を見ると大体把握できると思います。

Chefのようにディレクトリ構造の規約に縛られることもありませんし、
リモートサーバにItamaeをインストールする必要もありません。
ローカルでItamaeを gem install して レシピファイルを１枚用意するだけで
すぐに動かすことができます。
このシンプルさは何事にも代えがたい！

### Itamae Quick Start

```bash
# install
gem install itamae

# 適当なレシピ作る
echo "package 'sl'" > recipe.rb

# レシピ実行(ローカルホスト)
itamae local recipe.rb
# レシピ実行(リモートホスト)
itamae ssh -u hogeuser -h xxx.xxx.xxx.xxx recipe.rb
# attributeを定義したjsonを指定して実行
itamae local -j node.json recipe.rb
# レシピを複数つなげて実行
itamae local recipe01.rb recipe02.rb recipe03.rb

# dry-run
itamae local --dry-run recipe.rb
# ohai情報を参照する場合
itamae local --ohai recipe.rb
```

<br>

## Itamae移行モチベーションについて
旧環境では vagrant-aws + berkshelf + chef-solo でプロビジョニングしていましたが
以下のような不満点がありました。

1. 遅い
2. chef-zeroに移行しないといけないらしい

### 1. 遅い
これはVagrant-awsを通してプロビジョニングしていることもありますが、とにかく遅い。 [^2]
berkshelf実行してrsyncが転送してchef-solo自体の実行までにもタイムラグが結構ありましたし、
プロビジョニング自体もそれなりに時間がかかっていました。
### 2. chef-zero に移行しないといけないらしい
chef-soloは終焉を迎え、今後はchef-zeroを使う必要があるようです。
いやもちろん移行しようと思えば出来るんですけど
なんかもう面倒になってきてしまい…
俺はただ、書いたレシピをサーバ上で実行したいだけなんだ！！  

...その他、コミュニティクックブックを使いまくっていたのを直したかったりと
細かいところを挙げるとまだまだ理由はあるのですが、
大きくはこの２つを解消したいと思いItamae移行を考えました。 
(もちろんAnsibleも検討しましたが、レシピをyamlに全書きかえするのは移行コストに雲泥の差があるので除外…)


<br>

## 移行するにあたって注意したところ
1. プロビジョニングフローは変えない
2. ある程度ディレクトリルールを設ける

### 1. プロビジョニングフローは変えない
現在、プロビジョニングは以下のようなフローで行っています。[^1]
これを変えずにItamae化することを考えました。
※以下rakeコマンドはvagrantコマンドをラップしている感じです　

0. 独自のconfigファイルにvagrantのvm情報を書く
1. nodes/＜vm名＞.json というファイルを作る
2. rake aws:up vm=＜vm名＞ でVM起動
3. rake aws:provision vm=＜vm名＞ でプロビジョニング実行(chef-solo)
4. rake aws:spec vm=＜vm名＞ でテスト
5. rake aws:create_ami vm=＜vm名＞ でAMI作成

### 2. ある程度ディレクトリ構造ルールを設ける
これはAnsibleなどでも同じ問題が発生すると思いますが、
ディレクトリ構造に制限が無いため
好き勝手にレシピ作ると構造がカオスになり
結局メンテナンスコストが増大する問題が発生すると考えられます。
そこで自分のプロダクトでは以下のようにディレクトリ構造のルールを決めることにしました。

※2015/5/25 メモ追記
[itamaeベストプラクティス](https://github.com/itamae-kitchen/itamae/wiki/Best-Practice)がWikiに記載されているのでそちらもご参照下さい、。


#### プロジェクト全体
割とchefを踏襲している感じ。

```
PROJECT_ROOT/
  cookbooks/    # クックブック群
  nodes/        # vm毎のnode.json
  roles/        # ロール群
  entrypoint.rb # Itamaeから実行されるレシピのエントリポイント(後程説明)
  Gemfile       # Itamaeプラグインなどの依存が書かれたGemfile
```

#### クックブック毎
実はここは割とゆるめの規約にしてあって、
レシピ数行で済むようなものはattributesやtemplatesも作らずに
１ファイルで済ませたりすることもあります。
ファイルが増えてきて見通しが悪くなってきたら以下のようにする感じですね。

```
<cookbook_name>
  attributes.rb   # attributesをまとめたファイル
  xxx_recipe.rb   # なんかレシピ
  yyy_recipe.rb   # なんかレシピ
  templates/      # テンプレートファイル
    xxx.conf.erb
```

この構成にした場合、
手動でincludeしておかないと属性情報が利用できないため
recipeファイルの先頭でattribute.rbをincludeする必要があります。
１行増えるだけだしまあこのくらいなら許容範囲かと。

```
include_recipe './attribute.rb'
...
```

また、attributeファイルを分けていると
他のレシピから属性だけ参照したい時にも利用することができますね。
（例えば、プロジェクトレシピからconsulのbinパスを取得したい場合など）

<br>

## 移行後の構成
サンプルプロジェクトをこちらに置いてみました。ご参考ください。
https://github.com/toritori0318/itamae-sample-project

ポイントを幾つか説明します。

### [VagrantからshellプロビジョナーでItamae実行](https://github.com/toritori0318/itamae-sample-project/blob/master/vagrant_provision.sh)
最初は[vagrant-itamae](https://github.com/chiastolite/vagrant-itamae)を検討し利用してみたのですが、
itamaeのオプションをすべて利用できなかったりと若干利用しづらい部分があったため
現在はレシピをsyncした後でItamaeをサーバにインストールし
サーバ上でItamaeコマンドをlocal実行するようにしています。
結果として汎用性が上がったのと、
(副作用ですが)サーバ上で直接Itamaeを実行しているため
プロビジョニングの時間が大幅に改善しました。

### [entrypoint.rb](https://github.com/toritori0318/itamae-sample-project/blob/master/entrypoint.rb)
chef-soloのnodes.jsonで「recipes」を指定して
レシピを実行することをItamaeでも実現したかったので
このようなファイルを用意しました。
以下のような数行の小さなスクリプトです。

```rb
node["recipes"] = node["recipes"] || []

# レシピを順番にinclude_recipeするだけ
node["recipes"].each do |recipe|
  include_recipe recipe
end
```

### attributeファイルについて
デフォルト値はレシピファイルのattributeに定義しておき、
かつnode.json側で指定した時はそちらを優先するような書き方をしています。
attirbuteファイル側でいちいちこの書き方をしないといけないので
もうすこし上手くやりたいかもしれない。。
※2015/2/23追記 [コメント](http://qiita.com/toritori0318/items/00ea2a75c8321aaf9ef6#comment-e0cda068241101b1fffe)にてアドバイス頂きました！

```ruby
# "td_agent" 項目を初期化
node["td_agent"] = node["td_agent"] || {}

# node['td_agent']['includes']が入力済みであればそちらを優先
node['td_agent']['includes'] = node['td_agent']['includes'] || []
...
```

### プロジェクト全体で共通化しているレシピはプラグイン化する
全体で共通化しているレシピはプラグインとして作り、
プライベートリポジトリなどにおいておくと良いでしょう。
するとプロジェクト毎のレシピの見通しがよくなります。

[Gemfile](https://github.com/toritori0318/itamae-sample-project/blob/master/Gemfile)に以下のような記述をしておいて、bundlerで管理します。

```rb
# 独自のプライベートリポジトリに置かれた共通レシピなど
gem 'itamae-plugin-recipe-company-base', :git => 'git://hogehoge.com/fuga/itamae-plugin-recipe-company-base.git'
```

その他でも共通モジュール化しておきたいレシピは
プラグインとしてリポジトリに置いておくと良いと思います

### 基本的には include_recipe でなんでも出来る
include_recipeを使うと
attribute読み込みやroleのようにレシピをまとめる事もできます。
chefで行っていたことは大抵まかなえるでしょう。

<br>

## 移行コストについて

コミュニティクックブックを多用していたにも関わらず、
思ったより工数はかかりませんでした。
実はあまり難しいことはしていなかったということですね:)
ただし、便利に使っていたクックブックに関してはItamaeでも使いたかったので
一部はプラグインにしてgithubにあげています [^4]
https://github.com/toritori0318/itamae-plugin-recipe-supervisor
https://github.com/toritori0318/itamae-plugin-recipe-consul

コミュニティクックブックを利用せずにレシピを書いているプロジェクトであれば
かなり楽に移行できるのではないかな〜と思います。

<br>

## 移行後改善したところ
※以下、改善された点をあげていますが
cookbook自体のスリムアップも行っているためchefだけが要因というわけではありません。
参考程度にお願い致します。
### 導入の敷居が低くなった
gem install itamae でインストールできますし、レシピの実行も簡単〜♪
### 軽くなった／速くなった
明らかにプロビジョニングにかかる時間が改善されました。
またメモリ利用率も改善され t2.smallインスタンスが必要だったプロビジョニングが
t2.microインスタンス でも問題なく動くようになりました。
### レシピを追いやすくなった
これはコミュニティクックブックを使わなくなったというところもありますが
レシピの構造がそれほど複雑ではなくなったので追いやすくなったように感じました。
あと、Itamaeではあまり複雑なリソースを使わずにレシピを書く必要があるため
相対的に読みやすくなったというのもあるかもしれません。
(package/execute/template/serviceでほとんど事足りる)
### ツールのコードを追いやすくなった
Itamae自体のコード量はそれほど多くないため、何か問題があっても追いやすいです。

<br>

## 移行後不満なところ
### Chefサーバーに相当する機能がない
ありません。
### エコシステム周り
Chefと比べるとまだそれほどweb上には情報も多くはありませんし
Itamae用プラグイン(レシピ)もまだまだこれかなかなという感じです。
この辺りはユーザが増えてくると改善されてくるはずなので
みなさんどんどん利用すると良いですね:)
### DataBag機能がない
今のところDatabagのような仕組みはありません。
が、単にリポジトリ内に秘密のものを置きたくない、
ということであれば環境変数を使う方法があるようです。
http://qiita.com/nownabe/items/8576c17f864aeefa03d0
とはいえ機能として欲しいところではありますね。

<br>

## まとめ
Itamae、 導入もラクですし小回りが効くということで扱いやすい印象でした。
小／中規模であればプロビジョニングツールの候補としてオススメです。
ユーザが増えてより改善されると良いですね！！

<br>

## おまけ：ChefとItamaeの差分メモ
### リソース全般: modeの書き方
mode は stringである必要がある。

```ruby
# Chef
template 'a' do
  mode 0644  # 数値でも文字列でもOK
end

# Itamae
template 'a' do
  mode '644' # 文字列限定
end
```

### リソース全般: only_if/not_ifの書き方
ブロックではなく、文字列を指定する必要がある。

```ruby
# Chef
template "a" do
  not_if { File.exists?("/tmp/a.conf") }  # ブロックでも文字列でもOK
end

# Itamae
template "a" do
  not_if "test -e /tmp/a.conf" # 文字列のみOK
end
```

### リソース全般: notifiesのタイミング指定
:delayed ではなく :delay になっている。
（デフォルト指定がdelayなので、typoしてても実質無害ではあるが…）

```ruby
# Chef
template "a" do
  notifies :restart, "service[httpd]", :delayed
end

# Itamae
template "a" do
  notifies :restart, "service[httpd]", :delay
end
```

### directory: recursive属性が無い
```ruby
# recursive付けなくてもOK
directory "/tmp/hoge/fuga" do
  action :create
end
```

### 同じリソースでも出来る事が異なる
というか異なる部分はかなり多いので
きちんとドキュメントを読んだほうが良いでしょう。

  - 例えばユーザーリソース
    - [Chef:User](https://docs.chef.io/resource_user.html)
    - [Itamae:User](http://itamae.kitchen/docs.html#user-resource)

  - 例えばグループリソース
    - [Chef:Group](https://docs.chef.io/resource_group.html#attributes)
    - [Itamae:Group](http://itamae.kitchen/docs.html#group-resource)


<br>

## Ref

- [Github:Itamae](https://github.com/itamae-kitchen/itamae)
- [ドキュメント:Wiki](https://github.com/itamae-kitchen/itamae/wiki)
- [ドキュメント:Web](http://itamae.kitchen/docs.html)
- [スライド:Itamae - Infra as Code 現状確認会](https://speakerdeck.com/ryotarai/itamae-infra-as-code-xian-zhuang-que-ren-hui)

<br>

[^1]: この辺りのブログにも書いてます ( http://d.hatena.ne.jp/toritori0318/20130916/1379355060 ) ( http://d.hatena.ne.jp/toritori0318/20140829/1409296637 )
[^2]: vagrant-cachierも利用済み。
[^4]: API変える予定ありますしアルファ版ということでご容赦ください:(