OpsWorks + Rails でロールモデル的な役割分担をインスタンスにもたせるBK
OpsWorks + Rails
OpsWorksでRailsアプリを運用しようとした場合、通常の方法では、すべてのRailsアプリインスタンスが同じ設定になり、たとえば
といったことができません。
これを実現するために、標準で提供される「Rails App Server」の他に、カスタムLayerを作成し、その組み合わせでインスタンスごとの機能を作ることにしました。
参考
OpsWorksで複数RailsAppLayerを構築する - Qiita
こちらの記事を参考に、というより、これに加えて自分なりの工夫を加えた版になります。
カスタムCookbook
- Layerごとのセットアップ、デプロイ処理
- デプロイ時にwebサーバーの再起動が不要なケース
のため、カスタムCookbookを作成します。
各インスタンス共通の処理
- opsworks/recipes/setup.rb
- opsworks/recipes/deploy.rb
# # Cookbook Name:: opsworks # Recipe:: setup # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # # 以下は設定例 #package 'nodejs' #package 'npm' #nodejs_npm 'bower' #yum_package 'fontconfig' #yum_package 'freetype' #nodejs_npm 'phantomjs' # ImageMagick #include_recipe "imagemagick" #include_recipe "imagemagick::devel"
# # Cookbook Name:: opsworks # Recipe:: deploy # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # node[:deploy].each do |application, deploy| next unless deploy[:application_type] == 'rails' # Rails App Web Server Layer が含まれていない場合 unicornを止めておく # (そもそも起動しないようにしたいが難しいようなので…) is_rails_web = node[:opsworks][:instance][:layers].select { |layer| layer.match(/\Arails-app-web(-.+)?\z/) }.any? unless is_rails_web execute 'stop unicorn and stop nginx' do command "sleep #{deploy[:sleep_before_restart]} && \ #{deploy[:deploy_to]}/shared/scripts/unicorn stop" notifies :stop, "service[nginx]" action :run end end end
Cronを実行するインスタンスでのみ必要な処理
- opsworks/recipes/setup_cron.rb
- opsworks/recipes/deploy_cron.rb
# # Cookbook Name:: opsworks # Recipe:: setup_cron # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # # これは別にGemfileで入れても良いかも gem_package 'whenever' do action :install version '0.9.4' options '--no-user-install' end
# # Cookbook Name:: opsworks # Recipe:: deploy_cron # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # # whenverでcronを登録 node[:deploy].each do |application, deploy| next unless deploy[:application_type] == 'rails' execute 'whenever' do command "bundle exec whenever --update-cron #{application}_#{deploy[:rails_env]}" environment 'RAILS_ENV' => deploy[:rails_env] cwd "#{deploy[:deploy_to]}/current" user deploy[:user] group deploy[:group] end end
なお、この場合使用するschedule.rb
は以下のような感じになります。
set :output, 'log/cron.log' set :environment, ENV['RAILS_ENV'] if ENV['RAILS_ENV'] env :PATH, ENV['PATH'] job_type :runner, "cd :path && bundle exec ruby bin/rails runner -e :environment ':task' :output" every 5.minutes do runner 'Video.accept_all!' end
バックグラウンドタスクを実行するインスタンスでのみ必要な処理
- opsworks/recipes/setup_worker.rb
- opsworks/recipes/deploy_worker.rb
# # Cookbook Name:: opsworks # Recipe:: setup_worker # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # # God (system) # bundleによるユーザースペースではイベントシステムが利用できないなど問題があるので必ずシステムで入れる gem_package 'god' do action :install version '0.13.6' options '--no-user-install' end
# # Cookbook Name:: opsworks # Recipe:: deploy_worker # # Copyright 2015, Yuichi Takeuchi # # All rights reserved - Do Not Redistribute # # config/god.rbがあればgodを使う node[:deploy].each do |application, deploy| next unless deploy[:application_type] == 'rails' execute 'god' do only_if { File.exists?("#{deploy[:deploy_to]}/config/god.rb") } command "god terminate && god -c #{deploy[:deploy_to]}/config/god.rb -l #{deploy[:deploy_to]}/log/god.log" environment 'RAILS_ENV' => deploy[:rails_env] cwd "#{deploy[:deploy_to]}/current" end end
デプロイ時にwebサーバーの再起動が不要なケース
WebサーバLayerが含まれていないインスタンスでは再起動が不要なので、デプロイ時の挙動を変更
### # This is the place to override the deploy cookbook's default attributes. # # Do not edit THIS file directly. Instead, create # "deploy/attributes/customize.rb" in your cookbook repository and # put the overrides in YOUR customize.rb file. ### # The following shows how to override the deploy user and shell: # #normal[:opsworks][:deploy_user][:shell] = '/bin/zsh' #normal[:opsworks][:deploy_user][:user] = 'deploy' # Web用Layerが含まれていない場合は restart 不要 unless node[:opsworks][:instance][:layers].select{|layer| layer.match(/\Arails-app-web(-.+)?\z/) }.any? normal[:opsworks][:rails_stack][:restart_command] = nil normal[:opsworks][:rails_stack][:needs_reload] = false end
Stack
普通に作成すればOK
Use custom Chef cookbooks
で先に作成したCookbookを使用するように設定します。
また、僕はカスタムCookbookに必要な依存レシピをBerkshelfで管理しているので、それを使うようにしておきます。
Layer
Railsのコードベースが必要なもので共通の Rails App Server に加えて、カスタムLayerとして cron 用の Rails Cron Server 、ワーカー用の Rails Worker Server を作成しました。
各Layerのshortnameは「rails-app-*」というルールで設定しています。これは、Chefレシピ中でLayerを判定して処理を振り分ける際などに役立ちます。
- Rails App Server rails-app-server
- OpsWorks側ではじめから用意されているレイヤーをそのまま使います。
- 通常のRailsアプリケーションですが、ELBの設定は行いません。
- Rails App Web Server rails-app-web-server
- このレイヤーを設定したインスタンスのみ、Webサーバーの起動とELBの登録を行うことにします。
- Rails App Cron Server rails-app-cron-server
- このレイヤーを設定したインスタンスのみ、CRONの登録と実行をすることにします。
- Rails App Worker Server rails-app-worker-server
- このレイヤーを設定したインスタンスのみ、ActiveJob等のワーカーを実行することにします。
以下、それぞれのLayerの設定内容
Rails App Server
Layer作成で App Server -> Rails App Server を選択して作成します。
Recipes
Custom Chef Recipes で必要なものを追加
- opsworks::setup
- opsworks::deploy
Network
Elasic Load Balancer がデフォルトで設定されるので外しておく
Rails Web Server
Layer type Custom
Name Rails Web Server
Short name rails-app-web-server
Network
Elasic Load Balancer を設定
Rails Cron Server
Short name rails-app-cron-server
Recipes
Custom Chef Recipes で必要なものを追加
- opsworks::setup_cron
- opsworks::deploy_cron
Rails Worker Server
Short name rails-app-worker-server
Recipes
Custom Chef Recipes で必要なものを追加
- opsworks::setup_worker
- opsworks::deploy_worker
App
Ruby on Railsアプリケーションとして普通に作成します。
Instances
Rails App Serverを作成後、起動前にそのインスタンスの役割を作成したカスタムレイヤーを組み合わせて設定してやります。
例
たとえば以下のような構成で起動したいときのLayerの割り振りを考えてみます。
- 5台のインスタンスにRailsのソースコードを配置
- 2台を常時起動し、Webアプリサーバ(Nginx+Unicorn)及びバックグラウンドタスク用のWorkerを起動
- 常時起動の1台にCronを設定
- 過負荷時に2台のWebアプリサーバを追加で起動
- (夜間処理でバックグラウンドタスクがたくさん発行されるという想定で)バックグラウンドタスク実行専用のインスタンスを時間指定で起動
Rails App Server
- rails-app-1 (24/7)
- rails-app-2 (24/7)
- rails-app-3 (Load-based)
- rails-app-4 (Load-based)
- rails-app-5 (Time-based)
Rails App Web Server
Rails App Cron Server
- rails-app-1 (24/7)