apatheia.info

roundsmanを使ってcapistranoからchef-soloを実行する

Published on 30 Jul 2012

管理対象のサーバー台数が少ない場合など、chefのサーバーを運用するコストとベネフィットを天秤にかけてみて、ああこれどう考えても労力ペイできないな、でも設定ファイルを手動で管理するのはやだな、といったときにroundsmanを使うといいという話。

roundsmanは、chefのレシピを転送してchef-soloを実行するcapistrano向けライブラリ。アプリケーションのリリースタイミングに併せてインフラ設定の変更が必要になることは往々にしてあるので、capistranoを使ってデプロイとインフラ設定変更を一括適 用できるのは便利だ。

ここでは、Railsアプリを対象にroundsman適用までの作業を簡単にまとめる。

手順

まずは適当なRailsプロジェクトを作るところから。

PROJECT="my_fantastic_project"
rails new $PROJECT
cd $PROJECT

$EDITOR Gemfile
  # 追加
  gem roundsman, :require => false
  gem capistrano, :require => false

bundle install --path vendor/bundle

# capistranoのCapfile、config/deploy.rbを生成
bundle exec capify .

chefのcookbooksはconfig/cookbooksに配置する。場所は設定で変更可能。このディレクトリだけ別リポジトリにしておくと、ほかのプロジェクトでも転用できて便利なのでそうしてる。

config/deploy.rbを調整する。サーバーの種別ごとにデプロイを切り替えたいので、マルチステージを有効化。

$EDITOR config/deploy.rb

# 追加
# require roundsman/capistrano
# require capistrano/ext/multistage

サーバーグループの設定をconfig/deploy/*.rbに配置。これについては、capistrano/ext/multistageの説明を参照。

あとはconfig/deploy.rbでrecipeを実行するタスクを追加し、config/deploy/*.rbの中でattributeを設定していく。

config/deploy.rb:

    namespace :chef do
      set :care_about_ruby_version, false

      # 一括して適用
      task :default do
        roundsman.run_list fetch(:run_list)
      end

      # 個別にレシピ適用 (ex. nginx)
        namespace :nginx do
          task :install do
            roundsman.run_list "recipe[nginx]"
          end
        end

      end

githubにある設定方法の説明だと、config/ステージ名.rb に設定を書いている。

config/deploy/*.rb:

    set :nginx, :user => "nginx", "worker_process" => 1, …
    set :run_recipe, :user => "nginx", "worker_process" => 1, …

ただ、これだとattributesの管理がcapistranoの中にべったり書くことになってしまい、chef-soloを手で実行したいときとか面倒くさい。そのため、attributesの値はknifeやchef- soloで読めるようなjsonを作って、config/roles 以下で管理している。

roles ディレクトリはアプリのアップデートと関係なく更新していくことになるので、別リポジトリで管理した方がいい。

ファイル構成(抜粋)

  ├── Capify
  ├── Gemfile
  └── config
        ├── cookbooks
        ├── deploy
        └── roles

config/deploy.rb:

  # jsonファイルを取り込む関数を追加
  require active_support/core_ext/hash/deep_merge
  def load_role(*roles)
    json = {}
    roles.each do |role|
      json_path = "#{File.dirname(__FILE__)}/roles/#{role}.json"
      json.deep_merge! JSON.load(File.new(json_path))
    end
    json.each {|k,v| set (k.to_sym), v }
 end

config/deploy/*.rb:

  # 読み込みたいjsonファイルを指定
  load_role "web"

config/roles/*.json:

 例: config/roles/web.json
  {
     "nginx" : {
      "user" : "nginx",
      "worker_processes" : 1,
    …
     "run_list" : [ "recipe[nginx]", ... ]
  }

以上で準備が整った。これで実行できるようになる。

# 一括適用
bundle exec cap ステージ名 chef

# cookbook を指定して適用
bundle exec cap ステージ名 chef:nginx