Chef Solo から Chef Client Local Mode にすんなりと移行などできなかった

[Chef Solo が Deprecated 扱い](http://qiita.com/1000k/items/1217c66d592089931891)されてもう1年以上経つので、そろそろ Chef Client Local Mode モードに乗り換えようかと思ったのですが、予想以上に手こずってしまいました。嵌ったところと解決方法をメモしておきます。


検証バージョン
----
- Chef Client 12.4.3
- CentOS release 6.6


そもそも Chef Client Local Mode って？
----
Chef Solo がローカルの設定ファイルを直接コンパイルしてプロビジョニングするのに対して、Chef Client Local Mode はローカルに揮発性の Chef Server を作り、そこに knife を使って node, cookbook, role 等各種ファイルを登録し、プロビジョニングを行います。

詳しくは [Chef Solo ではなく Chef Client Local Mode を使おう - Qiita](http://qiita.com/1000k/items/1217c66d592089931891) の「Chef Client Local Mode (旧 Chef Zero) って？」を参照してください。


Chef Solo の時の構成
----
Chef Solo の頃は、以下のようなディレクトリ構成で各ファイルを管理していました。

```
/var/chef/chef-repo/
  cookbooks/
  data_bags/
  environments/
  nodes/
  roles/
  site-cookbooks/
```

また、`chef-solo` コマンド実行時にいちいち設定ファイルなどを引数で渡さなくて済むよう、以下のように設定ファイルを配置していました。

`/etc/chef/solo.rb`

```ruby
cookbook_path [
  "/var/chef/chef-repo/cookbooks",
  "/var/chef/chef-repo/site-cookbooks"
]
json_attribs "/var/chef/chef-repo/nodes/{ノード名}.json"
log_location "/var/log/chef-solo.log"
log_level :info
```

これで、`sudo chef-solo` を実行するだけでそのノードに応じた run_list が実行されます。かなり楽でした。


Chef Client Local Mode に変更してハマったところ
----
### JSON じゃないと role が読み込まれない

単純に `sudo chef-client -z` (`-z` は Local Mode で動作させるオプション) を実行すれば動くかと思いきや、以下のようなエラーが出て止まってしまいました。

```
$ sudo chef-client -z

[2015-10-02T13:46:40+09:00] WARN: No config file found or specified on command line, using command line options.
Starting Chef Client, version 12.4.3
[2015-10-02T13:46:42+09:00] ERROR: Role work (included by 'top level') is in the runlist but does not exist. Skipping expand.

================================================================================
Error expanding the run_list:
================================================================================

Missing Role(s) in Run List:
----------------------------
* work included by 'top level'

Original Run List
-----------------
* role[work]


Running handlers:
[2015-10-02T13:46:42+09:00] ERROR: Running exception handlers
Running handlers complete
[2015-10-02T13:46:42+09:00] ERROR: Exception handlers complete
Chef Client failed. 0 resources updated in 2.136252085 seconds
[2015-10-02T13:46:42+09:00] FATAL: Stacktrace dumped to /root/.chef/local-mode-cache/cache/chef-stacktrace.out
[2015-10-02T13:46:42+09:00] ERROR: The expanded run list includes nonexistent roles: work
[2015-10-02T13:46:44+09:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
```

今回 Chef を実行しているサーバーでは `work` というロールを指定していたのですが、なぜかそれが発見できないというエラーが出てしましました。

このサーバーの node JSON ファイルは以下のような中身。Chef Solo の頃はこれで問題なく実行できていました。

```json
{
  "run_list": [
    "role[work]"
  ]
}
```

いろいろ試行錯誤してみた結果、`roles/` 配下のファイルを `.rb` ではなく `.json` で書き換えたら動くようになりました。[Chef 公式ブログの "chef-client -z: From Zero To Chef In 8.5 Seconds" という記事](https://www.chef.io/blog/2013/10/31/chef-client-z-from-zero-to-chef-in-8-5-seconds/) を見ていたら、data_bags, nodes, roles などのファイルが全て `.json` で終わっていたので気付きました。

「Chef Client の場合は JSON ファイルじゃなくちゃダメ」という仕様でもあるのでしょうか？私の検索力が足りず、残念ながら発見できませんでした。

### 作業ディレクトリにキャッシュが作成される

`sudo chef-client -z` を実行すると、そのコマンドを叩いたディレクトリに `local-mode-cache` というディレクトリが作成されます。この中にはレシピ内でダウンロードする一時ファイルなどが全てキャッシュされています。このため、作業ディレクトリを変えると Chef は「キャッシュが無いからもう一度ダウンロードしよう」という動きをしてしまいます。

これでは鬱陶しいので、`/etc/chef/client.rb` で `file_cache_path` を指定しておくといいでしょう。

### node JSON ファイルが強烈に書き換えられる

プロビジョニング前のもともとの `nodes/{ノード名}.json` は、以下のような内容でした。

```json
{
  "run_list": [
    "role[work]"
  ]
}
```

ところがプロビジョニング後は、以下のように書き換えられてしまいます。

```json
{
  "name": "{ノード名}",
  "normal": {
    "tags": [

    ],
    "tz": "Asia/Tokyo",
    ...
    (猛烈に長いJSON)
  }
}
```

このせいで、Chef リポジトリを Git などで管理している場合、`git pull` しようとするとしばしばコンフリクトします。また、この JSON を使って Chef Solo を実行しようとしても解釈できないらしく、エラーになります。

これについてはまだどう対処するべきか決まっていません。いいアイディアがあればコメントください。


正しくファイルが読み込まれているか確認する方法
----
cookbooks や roles が正しく読み込まれているかどうかは、`knife list` をローカルモードで実行すれば確認できます。(Chef Client Local Mode は内部で Knife を呼んでいるので)

```
$ sudo knife cookbook list -z

build-essential   2.2.3
chef_handler      1.2.0
dmg               2.2.2
...

$ sudo knife role list -z

work
```


まとめ
----
最終的に、各種設定ファイルは以下のようになりました。

```ruby
# /var/chef/chef-repo/.chef/knife.rb

cookbook_path [
  "/var/chef/chef-repo/cookbooks",
  "/var/chef/chef-repo/site-cookbooks"
]
```

```ruby
# /etc/chef/client.rb

role_path "/var/chef/chef-repo/roles"
json_attribs "/var/chef/chef-repo/nodes/{ノード名}.json"
log_location "/var/log/chef-client.log"
log_level :info
file_cache_path "/var/chef/cache"
```

これで `sudo chef-client -z` を叩くだけでプロビジョニングできるようになりました。いつ Chef Solo が無くなっても安心です。


でもこれ、Chef Solo の代替と呼ぶには複雑すぎるような気がします。Deprecated にしない方がいいと思うのですが…。


参考
----
- [Chef-SoloからChef-Clientローカルモードへの移行 #opschef_ja #getchef_ja - クリエーションライン株式会社](http://www.creationline.com/lab/6380)
  - Chef Solo と Local Mode の違いが図付きでわかりやすく解説されています。
- [knife.rb — Chef Docs](https://docs.chef.io/config_rb_knife.html)
- [client.rb — Chef Docs](https://docs.chef.io/config_rb_client.html)
- [chef-client -z: From Zero To Chef In 8.5 Seconds | Chef Blog](https://www.chef.io/blog/2013/10/31/chef-client-z-from-zero-to-chef-in-8-5-seconds/)