大規模にchefを使い倒すためのcookbook pattern

# 概要
チームや組織、プロダクトをまたいでcookbookを使っていく場合に使えるcookbookの書き分け方を紹介します。
要はプロダクト毎にサーバに対する要求は変わりうるのでサービス毎にcookbookを分けるんだけど、会社全体として組織プロダクトをまたいで共通化しておきたい部分は共通のcookbookを使いましょうという話です。
こういう分け方をしたらよい、という話もします。

# 組織やプロダクトの成長とcookbook
小さな組織やチーム内でchefを導入した場合、cookbookを書くのは大抵の場合一人だと思います。
扱うサーバの種類もそんなに多くないので、apacheのインストールやphpの設定を一つのrecipeにベタ書きしても人間が管理できるボリュームに収まると思います。
というか、変に分割するよりもベタ書きのほうがrecipeの見通しがいいと思います。

ところが、組織が大きくなって人が増えてプロダクトが増えてきた場合、サーバのバリエーションも増えてくると思います。例えば、phpじゃなくてscalaを使うとか、apacheの設定が違うとか、新しくreverse proxyを使うとか。
もちろんプロダクト毎にイチから全く違うcookbookを書くのもありだと思いますが、セキュリティの問題とかパフォーマンスのようにプロダクトの本質ではない部分まで毎回作りなおさないといけないのは時間の無駄だし、cookbookを書く人もプロダクト毎に変わるだろうから対応も品質もバラバラになります。

そこで、「共通して使う部分」と「プロダクト毎に用意する部分」に分けるという考え方は自然と出てくると思います。しかし、cookbookは自由度が高く一言分けるといっても色々な分け方が考えられます。
例えばcookbookは共通なんだけどプロダクト毎にrecipeを分けるとか、プロダクトが違えばcookbookごと分けてしまうとか。
さらに、分け方を決めたとしても、それをchefを使う組織全体で共通の認識としておかないと結局バラバラになってしまいます。
こういった状況下で、どういった方針でcookbookを書き分けたらよいかについて紹介します。

## 元ネタ
The Environment Cookbook Pattern
http://blog.vialstudios.com/the-environment-cookbook-pattern/
ここから先は、ほぼこのblogの内容をかいつまんで説明します。詳細を知りたい場合は原文を読んでください。

# The Environment Cookbook Pattern
cookbookをその役割に沿って以下の5種類（レイヤ）に分け、各レイヤ間の依存を限定します。
1. Library cookbook
2. Application cookbook
3. Wrapper cookbook
4. Base cookbook
5. Environment cookbook

## Library cookbook
他のcookbookの機能を拡張したり、共通のユーティリティメソッドを提供したりするためのcookbook

- 基本的にrecipeは持たない (つまり、このcookbook単体ではサーバに対して何も行えない)
- LWRPを提供して共通機能を抽象化する
- librariesを使って、他のcookbookから使えるrubyのclass/moduleを提供する
- 他のLibrary cookbookやApplication cookbookには依存してもよいが、Wrapper/Environment cookbookには依存してはいけない

nginxやapacheのcookbookがLibrary cookbookではないかと言われるがそうではない。
なぜなら、それらはサーバにソフトウェアをインストールしているから。

Library cookbookの例は上の元ネタのページに紹介されているので割愛します。

## Application cookbook
ソフトウェアをインストールするためのcookbook

- mysql, apache2などのプロダクトに依存しないソフトウェアを入れるためのcookbook
- 1ソフトウェア1cookbook
- recipeを一つ以上持ち、上記ソフトウェアの本体やモジュールを追加で入れるためのrecipeで構成される
- attributeを外から設定することで、インストールするソフトウェアの設定を変更することができる
- Library Cookbookと他のApplication cookbookには依存してもよいが、他はダメ

cookbookの例は元ネタのページで紹介されているので割愛します。

## Wrapper cookbook
プロダクトの仕様に従ってApplication cookbookをwrapするcookbook

- Application cookbookをwrapし、プロダクトの仕様に従ってattributeを設定し、wrapしたcookbookをinclude_recipeする
- Application cookbookにのみ依存できる。(Library cookbook, 他のWrapper cookbook, Environment cookbookには依存してはいけない）
- cookbookの名前は、{組織名}-{wrapされたcookbook}か{プロダクト名}-{wrapされたcookbook}になる
- プロダクトの仕様が記述されているため、community cookbookのサイトで公開されることはない。（公開されているcookbookはLibrary/Application cookbookのどちらかのはず）

## Base cookbook
組織で共通に使うcookbook

- 1組織1cookbookまで
- MOTDとかbashの代わりにzshを使うとか、その組織で使うサーバに共通の内容を記述する
- 何でもかんでもbase cookbookに書けてしまうので、書く際には本当にbase cookbookに書くべき内容なのか慎重な吟味すること
- Library cookbook, Application cookbook, Wrapper cookbookに依存できる。Environment cookbookには依存してはいけない
- cookbook名は{組織名}-baseとなる

## Environment cookbook
実際にサーバで実行されるcookbook

- Wrapper cookbookやBase cookbookを組み合わせて、最終的に各サーバ上で実際に動くことになるcookbook
- Berksfile.lockをSCMで管理しなければならない唯一のcookbook
- このcookbookのドキュメントとmetadata.rbは最優先で書かなくてはならない
 - recipesにはrun_listに書いていいrecipeだけ書くこと
 - attributesも同様にcookbookの外側から変更していいattributeだけ書くこと
- berks installコマンドを使ってBerksfile.lockを生成し、これをversion controlにcommitしておくこと
 - このlockファイルを使って、本番サーバで使う全cookbookのバージョンを管理します
 - 最新のApplication cookbookがリリースされていたとしても、プロダクトとしてきちんとテストができたversionのcookbookだけを使うようにします（Gemfile.lockと同じような使い方です）

元ネタのページで紹介されている例を見ると、1プロダクト1Environment cookbookで、サーバの種類（web server, database server, proxy serverなど）ごとにrecipeを分けて用意しているようです。

## その他
元ネタのページでは、さらにberks packageやberks applyを使って、これらのcookbookをどうやってサーバに適用するかについても書かれています。