前回ブログ再開宣言をしてから3週間たってしまった…
ということで、再開第一弾いきます.
今回は、弊社で最近よく使われているサーバーのセットアップ方法をご紹介します.
前提
プロビジョニングツールにはitamae使ってます.
軽量なChefって感じで、困ったらソース読める規模なので安心. 機能も十分. Chefも昔使ってけど、ウチの規模だとオーバーだった.
仕組み
Capistranoを使って、各サーバーに対してItamae sshを実行している.
ROLES=web,app bundle exec itamae ssh ./ops/itamae/bootstrap.rb \ --node-json xxx.json \ --host xxx --user xxx
Itamae側の話
bootstrap.rb
はItamaeのエントリーポイントで、
環境変数ROLES
によって各サーバーの役割(WEBサーバーとか)に応じたレシピを読ませている.
# boostrap.rb include_recipe './helper.rb' (ENV['ROLES'] || '').split(',').each do |role| begin include_role role rescue nil end end
# roles/web.rb include_cookbook 'nginx' include_cookbook 'unicorn'
# roles/worker.rb include_cookbook 'unicorn'
# helper.rb module RecipeHelper def include_cookbook(name) cookbook, recipe = name.split('::') recipe ||= 'default' include_recipe(::File.expand_path("../cookbooks/#{cookbook}/#{recipe}", __FILE__)) end def include_role(name) include_recipe(::File.expand_path("../roles/#{name}", __FILE__)) end end Itamae::Recipe::EvalContext.include(RecipeHelper)
ちなみItamaeの構成はベストプラクティスを参考にして、こんな感じ.
├── bootstrap.rb ├── cookbooks │ ├── app │ ├── apt │ ├── elasticsearch │ ├── java │ ├── nginx │ └── ... ├── helper.rb └── roles ├── base.rb ├── search.rb ├── web.rb └── worker.rb
特に、Itamae側には特別なことはしてないはずなので、 今回説明しているCapistranoからの実行が嫌になっても簡単に別の仕組みに乗り換えられる.
Capistrano側の話
Capistrano側では、先ほど説明したbootstrap.rb
をitamae ssh
で実行させればよい.
これね↓
ROLES=web,app bundle exec itamae ssh ./ops/itamae/bootstrap.rb \ --node-json xxx.json \ --host xxx --user xxx
その際のポイントが3点あって、
- Role情報を渡してやる
- SSH情報を渡してやる
- 環境(本番/ステージング)で異なるノード情報を渡してやる
Role情報
これは簡単.
普通にCapistranoつかってれば、こんな感じにサーバーを定義していると思う.
このroles
オプションがそのまま、itamaeに環境変数ROLES
で渡した値になる.
# config/deploy/production.rb server "example.com", user: "deploy", roles: %w{app db web} server "example.com", user: "user_name", roles: %w{web app}, ssh_options: { user: "user_name" keys: %w(/home/user_name/.ssh/id_rsa), forward_agent: false, auth_methods: %w(publickey password) # password: "please use keys" }
SSH情報
これも同じ. 上のサーバー定義から持ってこれる.(やり方は、下にコード置いたからそれで)
環境毎のノード情報
config/deploy/production.rb
とかで、set :itamae_vars, { foo: 1 }
とかして定義して、
毎実行ごと、これをJSONファイルに書き出してitamaeに渡してやる.
itamaeのフォルダー群の中にnodes/production.json
とかつくって入れてもいいけど、
各環境の設定値は一箇所config/deploy/production.rb
にまとまってたほうが管理しやすい.
あと、今回は書かないけど、暗号化された秘密情報を展開して、とかしたいので node-jsonは動的に作りたい.
コード
コメントを入れておいたので、雰囲気だけでもつたわれば.
# lib/capistrano/tasks/itamae.rake namespace :itamae do desc 'Itamae plan(dry-run)' task :plan do run_itamae(dry_run: true) end desc 'Itamae apply' task :apply do run_itamae(dry_run: false) end def run_itamae(dry_run: true) # ノード情報が書かれたJSONファイルを生成 node_json = itamae_var_file.path on roles(:all) do |server| # itamae自体の実行はlocal run_locally do # SSH情報をitamaeのオプションにつめる options = itamae_ssh_options(server) options << "--color" options << "--dry-run" if dry_run options << "--node-json #{node_json}" # Role情報を環境変数につめる with(ROLES: server.roles.to_a.join(',')) do output = capture :bundle, :exec, :itamae, :ssh, './ops/itamae/bootstrap.rb', *options info server info server.roles.to_a.join(',') puts output puts "\n" end end end end # 設定ファイル(config/deploy/production.rb)で定義したitamae_varsをJSONファイルに書き出す def itamae_var_file Tempfile.open(['itamae_vars', '.json']) do |fp| fp.write JSON.pretty_generate(fetch(:itamae_vars)) fp end end # サーバー定義から、itamae sshで使える形にSSH情報をもってくる def itamae_ssh_options(server) ssh_options = fetch(:ssh_options) || {} ssh_options.merge!(server.ssh_options || {}) ssh_options[:user] ||= server.user ssh_options[:port] ||= server.port ssh_options[:keys] ||= server.keys ssh_options[:key] = ssh_options[:keys] && ssh_options[:keys].first options = [] options << "--host #{server.hostname}" options << "--user #{ssh_options[:user]}" if ssh_options[:user] options << "--port #{ssh_options[:port]}" if ssh_options[:port] options << "--key #{ssh_options[:key]}" if ssh_options[:key] options end end
設定ファイルは、例えばこんな感じ.
set :itamae_vars, { unicorn: { processes: 2 }, sidekiq: { concurrency: 10 } }
使い方はこう.
./bin/cap production itamae:plan ./bin/cap production itamae:apply
以上です.
後半雑になってしまったので、質問あればコメントください.