こんにちは。
株式会社ロックオンの三原です。

前回ご紹介したサーバ構築自動化ツール「Chef」について、実際に利用して経験した内容をTips形式でご紹介します。

Role(Environment) vs Recipe

いざ、Chefを使って社内の手順書をレシピ化しようとすると、かなりの可能性で遭遇するのがこの問題です。

Roleとは、必要なRecipeをrun_listでまとめることで、各Nodeの役割を定義することができるChefのモデルです。なにも考えなければ、「ADEBiSのWEBサーバ」や「THREeのDBサーバ」といった形で、Roleを作っていきたいのですが…、ちょっと待ってください。Webで検索してみると「Beginner Chef Antipatterns」において、RoleではなくRecipeを使うべきだという主張されているではありませんか。
この主張の元になっている課題は、レシピ間の関係を、どうバージョン管理するかというものです。

Roleは、バージョン管理の仕組みを持っていないため、内部のRecipeが更新されていくと、影響範囲のコントロールが難しくなるという問題があります。
そこで、Roleに相当する役割のRecipeを作成し、Recipeの持っているバージョン管理の仕組みを使って、Recipeの影響範囲をコンロールするというのが、「Beginner Chef Antipatterns」で述べられている主張になります。
この主張は、Roleに近い構成要素を持つEnvironmentにも当てはまります。

今回は、この主張を採用し、役割に関するRecipeレシピ(Roleに相当)環境に関するRecipe(Environmentに相当)という形でレシピを作成しました。

前回の記事にも書いた、”THREeのバッチサーバ” × “開発環境”というのが、まさにこれに当たります。

とはいえ、この使い方には議論の余地があると私自身思っており、特定要素の表現に特化したモデル(RoleEnvironment)があるにも関わらず、汎用的なモデル(Recipe)で全て表現するというのは、特定のモデルに特化して便利に使えるツールが今後出てきた際に、上手く連携できない可能性があります。

例えば、Environment毎に環境を構築して、テスト等を並列に走らせてくれるツールなどは、上手く連携できない可能性があります。
この辺りは、現実の事象をChefのモデルにどう反映させるかの議論になるので、実際のChefの利用事例を調査しながら、良い形を目指していきたいと思います。

Chef以外で触る可能性がある設定ファイル

# This file is managed by Chef.
# Do NOT modify this file directly.

私はChefのtemplateファイルの冒頭には、必ず上記のコメントを入れるようにしています。
その理由は、Chefは動かすごとにtemplateファイルで上書きしてしまうため、「手動で設定ファイルを触るな」と明示的する必要があるからです。

しかし実際問題、Chef以外で設定を触れないサーバというと、中々導入しずらいのも事実です。初回構築におけるChefを使った構築コスト低下は歓迎されるので、落とし所として「初回構築はChefでして、運用は手動かChefで」という形で、徐々にChefに移行してもらう作戦となります。

そこで、Chefのtemplateを使わずに設定する方法を考えます。

ruby_block "edit postgresql.conf" do
  notifies :restart, 'service[postgresql-9.2]'
  block do

  _file = Chef::Util::FileEdit.new("/var/lib/pgsql/9.2/data/postgresql.conf")

  _file.insert_line_if_no_match('### BY CHEF RECIPE###', >> EOS
### BY CHEF RECIPE###
deadlock_timeout = 5min
effective_cache_size = 1GB
timezone = 'Japan'
lc_messages = 'ja_JP.UTF-8'
lc_monetary = 'ja_JP.UTF-8'
lc_numeric = 'ja_JP.UTF-8'
lc_time = 'ja_JP.UTF-8'
listen_addresses = '*'
log_destination = 'syslog'
log_min_duration_statement = 5s
max_connections = 512
shared_buffers = 1GB
standard_conforming_strings = off
work_mem = 32MB
### BY CHEF RECIPE ###
EOS
)
  _file.write_file
  end
end

上記は、Postgresqlを設定するレシピになります。

Postgresの設定ファイルは、デフォルト設定が基本コメントアウトされているため、ファイルの末尾に設定したい値を追記する対応をとっています。
また、chef初回の設定済みというマーカーを入れているため、2回目以降からは上記の設定は動きません。

クックブックのコンパイル時にResourceを実行させる方法

Chefの実行サイクルには、以下のフェイズが存在します。

  1. Chef Serverとの通信、認証処理
  2. Chef SErverからのクックブック、データ取得
  3. クックブックのコンパイル
  4. ノードの設定、収束(converge)
  5. 通知(Notification)の実行

通常、第4フェーズか第5フェーズでResourceは実行されるのですが、第3フェーズで無理やりResourceを実行させる方法をご紹介します。

そもそも、なぜそんなHow-toが必要だったのか?

それは、あるサードパーティレシピを使おうとした時に、そのサードパーティレシピはDebian系でのみテストされていたと思うのですが、Redhat系では存在しないパッケージをインストールして失敗するという問題にぶつかりました。

この問題を回避するためには、サードパーティレシピの第4フェーズより先に、代替のパッケージをインストールするResourceを実行する必要があったため、以下の様にResourceオブジェクトに対して、run_actionメソッドを実行することで、第3フェーズでResourceを実行しました。

# databasegem()
gem_package "pg" do
  action :nothing
  gem_binary "/opt/chef/embedded/bin/gem"
  options "-- --with-pg-config=/usr/pgsql-9.3/bin/pg_config"
end.run_action(:install)

求む、Chef上級者の方(一緒に悩んでいただける方でも大歓迎)

ここまで、実際にChefを導入する時に必要になった少し泥臭いTopicをご紹介させて頂きました。対処方法やコードが美しくないというご指摘もあるかと思います。

私自身、Chefに関してまだまだ勉強不足であり、もっと良いベストプラクティスがあるはずです。
しかし、初心者が現場にChefを導入する際には、泥臭いやり方を取らざる負えないこともあるかと思いますので、この記事が、その際の参考になれば幸いです。また、よりスマートなやり方をお持ちの方は、ぜひ私(FB)までご教授をよろしくお願いいたします。