ActiveRecord::Enum 定義するとエラーで落ちることがある件
class Mail < ActiveRecord::Base enum flag: [:send, :receive] end
特に定義上おかしいところはないはずだが、 下記エラーが発生してしまう。
wrong number of arguments (3 for 0)
色々いじっていて気づく。
class Mail < ActiveRecord::Base enum flag: [:send] end
wrong number of arguments (2 for 0)
つまり、enum で定義するときに :send
使うと落ちる!
こうして解決しました。
class Mail < ActiveRecord::Base enum flag: [:sent, :received] end
Rails 4.2 へ ActiveAdmin を導入して、日本語化する方法
概要
Rails4.2 の環境へ ActiveAdmin を導入してみる。
有名な gem だと思われるが、業務で扱ったことがなかった。
業務で利用することになったので、導入方法や簡単な設定までをまとめておく。
前提
下記で Rails の環境が構築済みとする。
- Ruby2.2.2
- rvm 1.26.11
- bundler 1.10.5
- Rails 4.2.3
Gemfile の準備とインストール
Rails のカレントディレクトリへ移動。
# vim Gemfile gem 'activeadmin', '~> 1.0.0.pre1' gem 'activeadmin-translate' gem 'devise' gem 'devise-i18n'
インストールする。
bundle install --path vendor/bundle
ActiveAdmin のインストール
bundler で gem のインストールが済んだら、下記を実行。
必要なファイルが生成・追加される。
# bundle exec rails g active_admin:install invoke devise generate devise:install create config/initializers/devise.rb create config/locales/devise.en.yml invoke active_record create db/migrate/20150718125632_devise_create_admin_users.rb create app/models/admin_user.rb invoke rspec create spec/models/admin_user_spec.rb insert app/models/admin_user.rb route devise_for :admin_users gsub app/models/admin_user.rb gsub config/routes.rb insert db/migrate/20150718125633_devise_create_admin_users.rb create config/initializers/active_admin.rb create app/admin create app/admin/dashboard.rb create app/admin/admin_user.rb insert config/routes.rb generate active_admin:assets create app/assets/javascripts/active_admin.js.coffee create app/assets/stylesheets/active_admin.css.scss create db/migrate/20150718125633_create_active_admin_comments.rb
マイグレーションする。
# bundle exec rake db:migrate
管理画面を見てみる
Rails を起動する。
# bundle exec rails s -b 0.0.0.0
下記で管理画面へアクセスする。
http://192.168.33.10:3000/admin
初期状態のログイン用メールアドレスとパスワードは下記の通り。
- admin@example.com
- password
この時点で管理者ユーザの一覧/詳細表示、追加・編集・削除の機能が備わっている。
管理機能を追加
適当なモデルを作って、それを管理する機能を作ってみる。
モデルを作る
モデルを作って、マイグレートする。
# bundle exec rails g model name name:string date:date # bundle exec rake db:migrate
ActiveAdminでモデルを扱う
下記コマンドを実行すると、ファイルが作成される。
# bundle exec rails g active_admin:resource Name create app/admin/name.rb
作成されたファイルは下記のようになっている。
ActiveAdmin.register Name do permit_params :name, :date # この行は追加 end
Name
はモデルを指定しているが、上記の形式でファイルを作成すれば指定モデルの管理メニューを追加することができる。
これだけで指定モデルの一覧・詳細・編集(CRUD)の機能を利用できる。
ちなみに編集したいパラメータはpermit_params
で指定しなければいけない。
日本語化する前のスクリーンショット
ActiveAdmin の日本語化
設定ファイルの編集
日本語を利用するように下記ファイルに追加。
# vim config/application.rb ... config.time_zone = 'Tokyo' config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')] ...
各ja.yml の準備
よく利用されている yml があるので、ダウンロードして設置すること。
config/locale/ja.yml
config/locale/devise.ja.yml
別途、モデル向けの ja.yml を準備する。
# vim config/locales/model/ja.yml ja: activerecord: models: admin_user: 管理者 comment: コメント name: 名前 attributes: admin_user: email: メールアドレス current_sign_in_at: 最新ログイン日時 sign_in_count: ログイン回数 created_at: 作成日時 name: name: 名前 date: 日付 created_at: 作成日時 updated_at: 更新日時
もう一つ、active_admin 向けの ja.yml を準備する。
これを準備しないと自動生成されるボタンが日本語化されない。
# vim config/locales/active_admin/ja.yml --- ja: formtastic: :yes: 'はい' :no: 'いいえ' :create: '%{model}を作成' :update: '%{model}を更新' :submit: '%{model}を投稿' :cancel: '%{model}を中止' :reset: '%{model}を初期化' :required: '必須'
日本語化した後のスクリーンショット
エラーメッセージ
エラーメッセージを追加で表示する
モデルにバリデートを追加した場合、編集画面でエラーが表示される。
# app/models/name.rb class Name < ActiveRecord::Base validates :name, presence: true validates :date, presence: true end
画面の上部にエラーメッセージを表示する方法。
# app/admin/name.rb ... form do |f| # エラー表示枠を表示、シンボルで項目を指定 f.semantic_errors :name # モデルの入力項目を表示 f.inputs # 登録・更新などのボタンの表示 f.actions end ...
ドキュメント
Active Admin | The administration framework for Ruby on Rails
ActiveAdmin は一覧画面や編集画面でわりと自由にカスタマイズができる。
1つのモデルに対して管理機能を付けるのは今回紹介した通り簡単だが、 モデルにリレーションがある場合はそれぞれの画面で表示する項目や一覧画面の検索項目を変更・追加したい場合がある。
そんなときにもある程度の対応ができそう。
カスタマイズすることがあれば、また記事に残していこうと思う。
参考
Rails 非同期で処理を実行する方法(Sidekiq, Resque, Delayed Job, Active Job比較)
概要
Rails で WEB 画面からのキックでジョブをバックグラウンドで実行するときどうするか。
例えば、メール送信・画像変換・CSVアップロードによる大量SQL実行など。
そんなときはバックグラウンドで非同期にジョブを実行してくれる便利な gem がある。
ruby-rails.hatenadiary.com
このサイトで丁寧に説明してくれてます。
代表的だと言われている下記3つについて、それぞれ実装して使い心地を比較する。
- Sidekiq: mperham/sidekiq · GitHub
- Resque: resque/resque · GitHub
- Delayed Job: collectiveidea/delayed_job · GitHub
それと上記サイトで書かれている Active Job これの使い勝手も試してみる。
それぞれの gem について、もっと詳しく知りたい方は下記参照。
メリット・デメリットがそれぞれまとめられているし、ジョブを実行する際の違いも書かれてます。
準備
Rails を 4.2.1 へ
勉強用の Rails 環境は 4.1.8 なので、 Active Job が導入されたという 4.2.1 (現時点で最新)へアップデートする。
Gemfile を必要に応じて書き換え、下記を実行。
# bundle update ... Installing rails 4.2.1 (was 4.1.8) ...
redis をインストール・起動
Sidekiq, Resque はキューの管理に redis を使うので、インストールして起動しておく。
# brew install redis # redis-server
gem をインストール
Gemfile に下記を追加。
gem 'resque' gem 'sidekiq' gem 'delayed_job_active_record'
ActiveRecord でなくて、Mongoid を利用している場合は
gem 'delayed_job_mongoid'
をインストール。
# bundle install
実装
Sidekiq 編
準備
Sidekiq 向けに Redis 接続先の設定をする。
# vim config/initializers/sidekiq.rb
でファイル作成。
Sidekiq.configure_server do |config| config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' } end Sidekiq.configure_client do |config| config.redis = { url: 'redis://localhost:6379', namespace: 'sidekiq' } end # 環境が同居している場合は下記のように設定する # 設定しないと環境の区別がつかないので何の環境で実行されるかわからなくなる # namespace: "sidekiq_#{Rails.env}" # アプリ & 環境名を付けるといいっぽい
次は Sidekiq 起動時の設定。
# vim config/sidekiq.yml
でファイル作成。
--- :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log
Sidekiq 向けの Worker を実装
Worker クラスを定義し、perform メソッドに非同期で処理させたい実装を行う。
# vim app/workers/worker_for_sidekiq.rb
class WorkerForSidekiq include Sidekiq::Worker sidekiq_options queue: :sidekiq def perform(text) sleep 5 p "sidekiq: #{text}" end # ActiveRecord を扱う場合など、引数で全情報を与える必要がある # def perform(id, text) # hoge = Hoge.find(id) # hoge.update_attributes({text: text}) # end end
Sidekiq 起動
Sidekiq を起動する。
画面はそのままにしておく。
# bundle exec sidekiq -q sidekiq
-q オプションでsidekiq_options queue: :sidekiq
で定義したキュー名を指定。
キューに追加
コントローラーの実装が面倒なのでコンソールを立ち上げる。
# bundle exec rails c
コンソール上からキューに追加する。
[1] pry(main)> WorkerForSidekiq.perform_async("aiueo") => "358c09a3547d3d52dc1901ab" [2] pry(main)> WorkerForSidekiq.perform_async("kakikukeko") => "804d15dadef12e34a5beb1cd" [3] pry(main)> WorkerForSidekiq.perform_async("sasisuseso") => "ab4fda7f312442fe0fddb892"
確認
Sidekiq を立ち上げたコンソール側では、下記が非同期で処理されて表示されることが確認できる。
"sidekiq: aiueo" "sidekiq: kakikukeko" "sidekiq: sasisuseso"
# tail log/sidekiq.log
にも実行のログが残っていることが確認できる。
これで Sidekiq はおしまい。
参考
Resque 編
Resque 向け Redis 設定
# vim config/initializers/resque.rb
設定ファイルを作る。
Resque.redis = 'localhost:6379' Resque.redis.namespace = "resque" # 補足:アプリ & 環境 でネームスペースを決めた方がいいらしい # Resque.redis.namespace = "resque:app_name:#{Rails.env}"
Resque 向け Worker 起動用タスクを追加
# vim lib/tasks/resque.rake
でファイル追加。
require 'resque/tasks' task 'resque:setup' => :environment
Resque 向けの Worker を実装
Worker クラスを定義し、self.perform メソッドに非同期で処理させたい実装を行う。
class WorkerForResque @queue = :resque def self.perform(text) sleep 5 p "resque: #{text}" end end # Sidekiq と同じで必要情報は全て引数で perform メソッドに渡す必要あり
Resque 起動
Resque を起動する。
画面はそのままにしておく。
# QUEUE=resque bundle exec rake resque:work
QUEUE で指定する値は Worker の @queue へセットした値。
QUEUE=* と指定して実行すると全てのキューが実行対象となる。
キューに追加
コンソールを立ち上げる。
# bundle exec rails c
コンソール上からキューに追加。
[1] pry(main)> Resque.enqueue(WorkerForResque, "aiueo") => true [2] pry(main)> Resque.enqueue(WorkerForResque, "kakikukeko") => true [3] pry(main)> Resque.enqueue(WorkerForResque, "sasisuseso") => true
確認
Resque 起動のコンソールが下記のように表示されることを確認。
"resque: aiueo" "resque: kakikukeko" "resque: sasisuseso"
これで Resque の確認はおしまい。
参考
Delayed Job 編
特徴
Delayed Job では Sidekiq と Resque とは異なる方法でキューにジョブを追加する方法がある。
Delayed Job をインストールすると、Object と Module に delay メソッドが追加される。
Hoge.delay.send_mail
や hoge.delay.send_mail
などのようにすることで、
どんな処理でも非同期で実行できる。
またキューの管理は Redis を使わず、Rails と同じ DB (ORM)を利用する。
準備
キュー管理用のテーブルを追加して、反映する。
# bundle exec rails g delayed_job:active_record # bundle exec rake db:migrate
Delayed Job 向けの Worker を実装
class WorkerForDelayedJob def initialize(text) @text = text end def perform sleep 5 p "delayed_job: #{@text}" end end # perform メソッドに引数取れないっぽいのでこんな実装になった
Delayed Job 起動
コンソールを立ち上げて、コマンドを打ったらそのままの状態にしておく。
# QUEUE=delayed_job bundle exec rake jobs:work
キューに追加
コンソール起動。
# bundle exec rails c
コンソールからキューを追加。
[1] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("aiueo"), queue: "delayed_job") [2] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("kakikukeko"), queue: "delayed_job") [3] pry(main)> Delayed::Job.enqueue(WorkerForDelayedJob.new("sasisuseso"), queue: "delayed_job") [4] pry(main)> job = WorkerForDelayedJob.new("tatututeto") [5] pry(main)> job.delay(queue: "delayed_job").perform
Delayed Job 特有の delay
メソッドでのキュー追加も試してみる。
確認
Delayed Job を起動したコンソールを確認する。
実行ログに混じって、実行結果が下記のように表示されることを確認。
"delayed_job: aiueo" "delayed_job: kakikukeko" "delayed_job: sasisuseso" "delayed_job: tatututeto"
Delayed Job 編おしまい。
参考
RailsでAcitiveJobとDelayedJobを使ってバックグランド処理を行う - Rails Webook
Active Job を使ってみる
概要
Active Job で実装すれば、上記のような非同期処理の gem を意識する必要がなくなる。
対応している gem を確認するには下記ディレクトリを覗く。
https://github.com/rails/rails/tree/master/activejob/lib/active_job/queue_adapters
(美しい Adapter パターン)
Active Job で実装して、queue_adapter の指定をそれぞれ Sidekiq, Resque, Delayed Job にしてみて、 同じように非同期で動作するところまでを確認する。
ジョブを作成する
下記コマンドを実行。
# bundle exec rails g job hoge invoke test_unit create test/jobs/hoge_job_test.rb create app/jobs/hoge_job.rb
ジョブを実装する
# vim app/jobs/hoge_job.rb
でファイル開くと、下記の状態。
class HogeJob < ActiveJob::Base queue_as :default def perform(*args) # Do something later end end
それを下記のように実装。
class HogeJob < ActiveJob::Base queue_as :default def perform(text) sleep(5) p "ActiveJob: #{text}" end end
Sidekiq アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
module Hoge class Application < Rails::Application ... config.active_job.queue_adapter = :sidekiq ... end
Sidekiq 起動して、Rails コンソールからキュー登録
# bundle exec sidekiq ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("aiueo") Enqueued HogeJob (Job ID: b5bfa62b-11f6-4d16-848c-0a1aee98abb8) to Sidekiq(default) with arguments: "aiueo" => #<HogeJob:0x007ff5e35427f8 @arguments=["aiueo"], @job_id="b5bfa62b-11f6-4d16-848c-0a1aee98abb8", @queue_name="default">
確認
Sidekiq を起動したコンソールで下記が表示される。
"ActiveJob: aiueo"
Resque アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
...
config.active_job.queue_adapter = :resque
...
Resque 起動して、Rails コンソールからキュー登録
# QUEUE=* bundle exec rake resque:work ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("kakikukeko") Enqueued HogeJob (Job ID: f205128d-5941-4abd-a579-35b992bd06a2) to Resque(default) with arguments: "kakikukeko" => #<HogeJob:0x007fac1a9237b8 @arguments=["kakikukeko"], @job_id="f205128d-5941-4abd-a579-35b992bd06a2", @queue_name="default">
確認
Resque を起動したコンソールで下記が表示される。
"ActiveJob: kakikukeko"
Delayed Job アダプターで実行
アダプターの設定をする
# vim config/application.rb
でファイルを編集。
...
config.active_job.queue_adapter = :delayed_job
...
Delayed Job 起動して、Rails コンソールからキュー登録
# bundle exec rake jobs:work ↑立ち上げっぱなし ↓別コンソールで実行 # bundle exec rails c
[1] pry(main)> HogeJob.perform_later("sasisuseso") Enqueued HogeJob (Job ID: 052dc5e2-5f37-4947-a193-3b91783a84e1) to DelayedJob(default) with arguments: "sasisuseso" (0.1ms) begin transaction SQL (0.7ms) INSERT INTO "delayed_jobs" ("queue", "handler", "run_at", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["queue", "default"], ["handler", "--- !ruby/object:ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper\njob_data:\n job_class: HogeJob\n job_id: 052dc5e2-5f37-4947-a193-3b91783a84e1\n queue_name: default\n arguments:\n - sasisuseso\n"], ["run_at", "2015-06-05 02:04:21.435653"], ["created_at", "2015-06-05 02:04:21.435913"], ["updated_at", "2015-06-05 02:04:21.435913"]] (0.6ms) commit transaction => #<HogeJob:0x007f934b23b370 @arguments=["sasisuseso"], @job_id="052dc5e2-5f37-4947-a193-3b91783a84e1", @queue_name="default">
確認
Delayed Job を起動したコンソールで下記が表示される。
"ActiveJob: sasisuseso"
まとめ
Sidekiq, Resque, Delayed Job どれもコーディングするにあたってはほとんど大差ない。
それぞれ Web UI でキューの確認もできるし、ジョブの優先度付けなどもできる。
Delayed Job は既存のDBを利用するし、導入も使い方も一番簡単な印象。
delay メソッド拡張によって、どの Object に対しても、Hoge.delay.fuga
で非同期実行にできるのは楽。
小規模サイトで手っ取り早く非同期処理を実現したいなら、Delayed Job がいいと思う。
Redis が使える環境ならば、Sidekiq と Resque も選択肢に入る。
それぞれ下記の特徴があるのでインフラとの兼ね合いや好みか。
Rails 4.2 以上の環境であれば、とりあえず Active Job で実装しておくのがいいと思う。
アダプターの交換も今回実装したように簡単にできるので、 開発環境は Delayed Job で実装してしまって、 本番環境ではジョブキューを何で処理するか相談して決めれば良い。
正直、どれが優れているとか決められないので、
どんな処理をどれくらいの頻度・量で実行するか洗い出し、
下記サイトを参考にインフラ担当者と話し合うのがいいかと。
ジョブキュー処理のResqueとDelayed Jobの使い分けの方針などはありますか? - QA@IT
Rails 4.2 + Capistrano3 + Unicorn + Nginx でホットデプロイ環境構築
Capistrano3 + Unicorn + Nginx でのデプロイ環境構築を試してみる。
今回は Ubuntu 12 の環境で行う。
リモートのGITサーバの準備や外部にWEBサーバ等を準備するのが面倒なので、
全てローカルで完結するように試してみる。
準備
# rvm list known Ubuntu 12 のせいで rvm で Ruby 1.9.3 までしかインストールできないので思考錯誤。 rvmsudo(MultiUserモード)を使えるように設定 # sudo usermod -G rvm vagrant # vim ~/.bashrc umask 002 #読み込まれる位置に追加 # export rvmsudo_secure_path=1 rvm のバージョンを上げる # rvmsudo rvm get head # rvmsudo rvm reload # rvm list known # rvmsudo rvm install 2.1.5 エラーが出てしまう。 apt で利用するリポジトリにソースがないっぽ。 下記を見てみる。 # rvm help mount 直接ソースをマウントする。 # rvmsudo https_proxy=${https_proxy} rvm mount -r https://rvm.io/binaries/ubuntu/12.04/x86_64/ruby-2.1.5.tar.bz2 # rvm use ruby-2.1.5
Rails 4.2 をインストール
下記をワークディレクトリとして作業する。
Rails 4.2 をbundle install
する。
# cd /home/vagrant/local_proj # vim Gemfile
source 'https://rubygems.org' gem 'rails', '4.2.1'
# bundle install --path vendor/bundle
これでワークディレクトリ上でbundler
を通して、
Rails4.2を使えるようになった。
Rails のアプリ作成と必要Gemのインストール
テスト用のアプリを作る。
# cd /home/vagrant/local_proj # bundle exec rails new test_app
テスト用アプリに必要GEMをインストール。
# cd test_app # vim Gemfile
下記を最下部に追加。
# gem 'debugger' #ruby 2.1と相性が悪いのでコメントアウト gem 'therubyracer' # rake 実行で ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime.に対応するため gem 'unicorn' gem 'capistrano' gem 'capistrano-rails' gem 'rvm-capistrano' gem 'capistrano-bundler' gem 'capistrano3-unicorn' gem 'rvm1-capistrano3', require: false
# bundle install --path vendor/bundle
rake の実行でエラーが出るので、Ruby2.1.5向けにインストール。
# rvm use 2.1.5 # rvmsudo gem install 'rake'
DB初期化。
# bundle exec rake db:setup # ls db development.sqlite3 seeds.rb test.sqlite3
scaffold で動作確認するためのコントローラー等を用意しておく。
マイグレーションは production 環境以外で実行。
# bundle exec rails g scaffold test_dayo name:string # bundle exec rake db:migrate # RAILS_ENV=test bundle exec rake db:migrate
動作確認する。
ちなみに Rack 1.6以上からは-b 0.0.0.0
でオプション指定しないと、
ホストOSからゲストOSへのアクセスができない。(セキュリティ対策)
詳細は下記で。
» Rails4.2beta1をインストールして最初にはまったこと TECHSCORE BLOG
# bundle exec rails s -b 0.0.0.0 http://192.168.33.11:3000/test_dayos ↑へアクセスできることを確認。
Unicorn の設定
必要最低限の Nginx と連携を取る設定ファイルを準備する。
後々分かったことだが、pid ファイル等を current ディレクトリで管理してしまうと Unicorn のホットデプロイに支障が出るため、
production 環境では shared ディレクトリで pid ファイル等を管理するように切り分けた。
# vim config/unicorn.rb def rails_root File.expand_path('../../', __FILE__) end def rails_env_ ENV['RAILS_ENV'] || "development" end def shared_path "/home/deployer/local_proj/shared" end def path_by_rails_env if rails_env == "production" shared_path else rails_root end end worker_processes 2 working_directory rails_root listen "#{path_by_rails_env}/tmp/#{rails_env}_unicorn.sock" pid "#{path_by_rails_env}/tmp/#{rails_env}_unicorn.pid" stderr_path "#{rails_root}/log/#{rails_env}_unicorn_error.log" stdout_path "#{rails_root}/log/#{rails_env}_unicorn.log" preload_app true
GIT リポジトリをローカルに作成
git のリモートリポジトリをローカルで作成してみる。
# cd /home/vagrant # mkdir local_proj.git # git init --bare
ローカルのリモートリポジトリに push する。
# cd /home/vagrant/local_proj/test_app # git init # git remote add origin /home/vagrant/local_proj.git
.gitignore に下記を追加。
vendor/bundle *.swp
test_app を全部コミット、プッシュする。
# git add . # git commit -m "first commit" # git push origin master
Production 用環境準備
production 用に deployer ユーザを作成、ソースを持ってきて、セットアップする。
起動してアクセスしてみるも、シークレットキーでエラー。
# sudo adduser deployer # sudo usermod -G rvm deployer # su - deployer # cd ~ # git clone /home/vagrant/local_proj.git # cd local_proj # bundle install --path vendor/bundle # RAILS_ENV=production bundle exec rake db:setup # RAILS_ENV=production bundle exec rails s -b 0.0.0.0 http://192.168.33.11:3000/test_dayos へアクセスすると下記エラー ERROR RuntimeError: Missing `secret_token` and `secret_key_base` for 'production' environment, set these values in `config/secrets.yml`
シークレットキーを設定する。
# vim ~/.profile export SECRET_KEY_BASE=aiueo #追加
# source ~/.profile # RAILS_ENV=production bundle exec rails s -b 0.0.0.0 http://192.168.33.11:3000/test_dayos へアクセスできることを確認。 # rm -rf local_proj 一旦、production で動作確認できたので消す、あとで Capistrano からデプロイする。
Nginx のインストールと設定
# sudo aptitude install nginx
Unicorn と development 環境と連携する設定。
# vim /etc/nginx/conf.d/development.conf upstream development_unicorn { server unix:/home/vagrant/local_proj/test_app/tmp/development_unicorn.sock; } server { listen 8080; server_name test_app; root /home/vagrant/local_proj/test_app/public; access_log /var/log/nginx/development_test_app_access.log; error_log /var/log/nginx/development_test_app_error.log; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://development_unicorn; } }
Unicorn と production 環境と連携する設定。
各ディレクトリやファイルの指定は Capistrano でデプロイ実行後のディレクトリ構成を考慮。
# vim /etc/nginx/conf.d/production.conf upstream production_unicorn { server unix:/home/deployer/local_proj/shared/tmp/production_unicorn.sock; } server { listen 80; server_name test_app; root /home/deployer/local_proj/current/public; access_log /var/log/nginx/production_test_app_access.log; error_log /var/log/nginx/production_test_app_error.log; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://production_unicorn; } }
Nginx を起動しておく。
# sudo nginx
vagrant ユーザで development 環境を起動。
# bundle exec unicorn_rails -c config/unicorn.rb -E development http://192.168.33.11:8080/test_dayos へアクセスできることを確認する。
Capistrano の設定
vagrant ユーザ(development 環境)で設定していく。
# cd /home/vagrant/local_proj/test_app # bundle exec cap install STAGES=production
Capfile を編集。
コメントアウトを以下のように外して、require 'rvm1/capistrano3'
とrequire 'capistrano3/unicorn'
を追加。
# vim Capfile ... # require 'capistrano/rvm' # require 'capistrano/rbenv' # require 'capistrano/chruby' require 'rvm1/capistrano3' require 'capistrano/bundler' require 'capistrano/rails/assets' require 'capistrano/rails/migrations' # require 'capistrano/passenger' require 'capistrano3/unicorn' ...
config/deploy.rb を編集する。
下記のように変数をセット。
# vim config/deploy.rb ... set :application, 'test_app' set :repo_url, '/home/vagrant/local_proj.git' set :deploy_to, '/home/deployer/local_proj' # MultiUser の場合 :system, SingleUser の場合 :user set :rvm_type, :system set :rvm1_ruby_version, '2.1.5' # rvm-auto.sh を配置・実行するためのパス、書込権限に注意 set :rvm1_auto_script_path, "/tmp/#{fetch(:application)}" set :default_env, fetch(:default_env).merge!( { # MultiUser モードでインストールした bin/rvm がある場所を指定 "rvm_path" => "/usr/share/ruby-rvm/", http_proxy: "http://example:8080", https_proxy: "http://example:8080" } ) ... after 'deploy:publishing', 'deploy:restart' namespace :deploy do before :starting, 'deploy:mkdir' task :mkdir do on roles(:all) do # pid ファイル等を管理する tmp ディレクトリを作成しておく execute "mkdir -p #{shared_path}/tmp" end end task :restart do # deploy:publishing 処理後に Unicorn の再起動タスクを実行(ホットデプロイ invoke 'unicorn:restart' end ...
本番環境向けの設定。
# vim config/deploy/production.rb server 'localhost', user: 'deployer', roles: %w{web db} role :web, %{deployer@localhost} role :db, %{deployer@localhost} set :unicorn_roles, :web set :unicorn_pid, "#{shared_path}/tmp/production_unicorn.pid" set :unicorn_config_path, "/home/deployer/local_proj/current/config/unicorn.rb" set :default_env, fetch(:default_env).merge!( { secret_key_base: "aiueo", } ) set :unicorn_rack_env, -> { fetch(:rails_env) == "development" ? "development" : "production" } server 'localhost', user: 'deployer', roles: %w{web db}, ssh_options: { user: 'deployer', # overrides user setting above forward_agent: false, auth_methods: %w(password), password: 'deployer' }
Capistrano と Unicorn での設定ファイルを全てコミット、プッシュする。
# git add . # git commit -m "Add unicorn and capistrano setting file." # git push origin master
デプロイしてみる。
# bundle exec cap production deploy # ls /home/deployer/local_proj current/ releases/ repo/ shared/
http://192.168.33.11/test_dayos へアクセスできることを確認。
現状のデプロイだと、sqlite との関係上毎回DBがリセットされるため、 sqlite のファイルを shared 内に入れる。
# vim config/database.yml production: <<: *default database: /home/deployer/local_proj/shared/db/production.sqlite3
デプロイすると、shared 内に sqlite のファイルが作成された。
# git add . # git commit -m 'Modify database.yml' # git push origin master # bundle exec cap production deploy # ls /home/deployer/local_proj/shared/db production.sqlite3
せっかくなのでソースを更新してみる。
# bundle exec rails g migration AddBodyToTestDayo body:text # bundle exec rake db:migrate
コントローラー・モデル・ビューを修正(省略
デプロイしてみる、リロード連打しながらどうなるか。
ホットデプロイ感を味わえるか・・・
チカチカしてうざいけど、ダウンタイムが発生しないでデプロイできることが確認できた。
Unicorn のプロセス管理も capistrano3/unicorn
で簡単にできるようになっているし、
ホットデプロイの環境でサービスを運営してみたいと思った。
Rails4.2 を Nginx + Unicorn で動作させる
概要
Rails や Padrino の案件に関わってきたが、全て Apache + Passenger の構成で動作させていた。
最近では、Nginx + Unicorn で動作させているという話しをよく聞くので、環境作りを試してみる。
前提
- Rails4.2 をインストール済み
- bundler を使っている
Unicorn のインストール
Gemfile に下記を追加。
# vim Gemfile gem 'unicorn'
下記を実行し、インストール。
# bundle install
Nginx のインストール
下記は mac で HomeBrew 使った場合。
環境によって yum なり aptitude なり使い分ける。
# brew install nginx
Unicorn の設定
config/unicorn.rb を新規作成
Unicorn の設定ファイルを必要最低限の内容で新規作成。
# vim config/unicorn.rb rails_root = File.expand_path('../../', __FILE__) rails_env = ENV['RAILS_ENV'] || "development" worker_processes 2 working_directory rails_root #listen "#{rails_root}/tmp/#{rails_env}_unicorn.sock" listen 8080 pid "#{rails_root}/tmp/#{rails_env}_unicorn.pid" stderr_path "#{rails_root}/log/#{rails_env}_unicorn_error.log" stdout_path "#{rails_root}/log/#{rails_env}_unicorn.log"
一旦、この最低限の設定で Unicorn 単体で動作してみる。
わざわざ RAILS.env の値をファイル名に利用するようにしてみた。
動かすだけなら無駄だった・・・
# bundle exec unicorn_rails -c config/unicorn.rb -E development
下記にアクセスできることや、pid ファイル、log ファイルが作成されていることを確認する。
http://localhost:8080
config/unicorn.rb を編集
Nginx と連携するために少し修正。
# vim config/unicorn.rb ... listen "#{rails_root}/tmp/#{rails_env}_unicorn.sock" #listen 8080 ...
Nginx と Unicorn の通信に Unix Domain Socket を利用してみる。
ざっくり説明すると、Nginx + Unicorn を一つのサーバ内で構築する場合、
つまり、ネットワーク越しに通信しない場合は Unix Domain Socket を利用した方が速いようだ。
詳細は下記。
nginx - 調べなきゃ寝れない!と調べたら余計に寝れなくなったソケットの話 - Qiita
Unicorn 再起動
起動させていたのをctrl-c
で止めて、下記実行。
# bundle exec unicorn_rails -c config/unicorn.rb -E development
ちなみに-D
を付けると、バックグラウンドで実行される。
その場合、止めるときはプロセスIDを探してkill
する。
# bundle exec unicorn_rails -c config/unicorn.rb -E development -D
Nginx の設定
nginx.conf へ追記
mac で brew を使ってインストールした場合、 下記に設定ファイルがあるので開いて編集。
# vim /usr/local/etc/nginx/nginx.conf
http {
ブロック内に下記を追加する。
元から書かれているserver {
ブロックは削除またはコメントアウトすること。
http { ... upstream unicorn { # unicorn.rbで設定したunicorn.sockを指定 server unix:{RAILS_ROOT}/tmp/development_unicorn.sock; } server { listen 8080; server_name first_app; root {RAILS_ROOT}/public; access_log {LOG_PATH}/first_app_access.log; error_log {LOG_PATH}/first_app_error.log; try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://unicorn; } } ...
nginx の起動
# nginx
で起動する。
再起動は下記。
# nginx -s reload
下記にアクセスできることを確認する。
http://localhost:8080
nginx 用のログファイルにログが書かれていることを確認できれば終わり。
ホットデプロイの仕組み
Unicorn は下記サイトで説明された仕組みでホットデプロイを実現させるようだ。
unicornのpreload_app - motsatのブログ
Unicorn のプロセス A が Nginx とやり取りしてサイトが動いているとして、
SIGUSR2 を プロセス A に送ると、新しいRails環境を読み込んだプロセス B が誕生する。
プロセス A を終了させることで、Nginx とやり取りするのがプロセス B のみになり、ダウンタイムを発生させないで、アプリのリロードが行える。
コマンドライン上から再現できそうなのでやってみる。
Unicorn の設定編集
Unicorn の起動を止めて、設定ファイルを編集、下記を追加。
# vim config/unicorn.rb preload_app true # SIGUSR2 を送ってRails環境を新しく読み込ませるために必要なオプション
コマンドラインで再現する
# 起動中の Unicorn のプロセスIDを確認 # ps aux | grep unicorn tsuyaxchi 93067 0.1 0.0 2424588 408 s011 R+ 9:05PM 0:00.00 grep unicorn tsuyaxchi 92788 0.0 0.4 2576308 36896 s005 S+ 8:19PM 0:04.75 unicorn_rails worker[1] -c config/unicorn.rb -E development tsuyaxchi 92787 0.0 0.9 2572288 74912 s005 S+ 8:19PM 0:04.11 unicorn_rails worker[0] -c config/unicorn.rb -E development tsuyaxchi 92786 0.0 0.0 2481504 3700 s005 S+ 8:19PM 0:00.75 unicorn_rails master -c config/unicorn.rb -E development # マスタに対して SIGUSR2 を送る # kill -s USR2 92786 # プロセスを確認する # 92786 には master (old) と追記されている # 93092 には新しい master が誕生している # ps aux | grep unicorn tsuyaxchi 92788 0.0 0.0 2576308 3916 s005 S+ 8:19PM 0:04.80 unicorn_rails worker[1] -c config/unicorn.rb -E development tsuyaxchi 92787 0.0 0.0 2579392 3844 s005 S+ 8:19PM 0:04.58 unicorn_rails worker[0] -c config/unicorn.rb -E development tsuyaxchi 92786 0.0 0.1 2481504 4400 s005 S+ 8:19PM 0:00.75 unicorn_rails master (old) -c config/unicorn.rb -E development tsuyaxchi 93104 0.0 0.0 2423368 204 s011 R+ 9:10PM 0:00.00 grep unicorn tsuyaxchi 93100 0.0 0.1 2562604 7056 s005 S+ 9:10PM 0:00.01 unicorn_rails worker[1] -c config/unicorn.rb -E development tsuyaxchi 93099 0.0 0.1 2562604 7920 s005 S+ 9:10PM 0:00.01 unicorn_rails worker[0] -c config/unicorn.rb -E development tsuyaxchi 93092 0.0 1.2 2562604 99352 s005 S+ 9:10PM 0:03.32 unicorn_rails master -c config/unicorn.rb -E development # master(old) を終了させる # kill -s QUIT 92786 # ps aux | grep unicorn tsuyaxchi 93113 0.0 0.0 2432784 620 s011 S+ 9:12PM 0:00.00 grep unicorn tsuyaxchi 93100 0.0 1.0 2565808 84140 s005 S 9:10PM 0:00.54 unicorn_rails worker[1] -c config/unicorn.rb -E development tsuyaxchi 93099 0.0 0.9 2577340 76700 s005 S 9:10PM 0:01.04 unicorn_rails worker[0] -c config/unicorn.rb -E development tsuyaxchi 93092 0.0 0.4 2562604 32184 s005 S 9:10PM 0:03.32 unicorn_rails master -c config/unicorn.rb -E development
下記に問題なくアクセスできることを確認。
http://localhost:8080
まとめ
動作させるだけなら、非常に簡単。
ここから、パフォーマンスが出る設定値を探して行くのが、
楽しいところであり難しいところ。
あと、Unicorn はホットデプロイできる仕組みを持っているようだ。
Capistrano + Unicorn でホットデプロイする仕組みを知っておきたいので、 今度やろうと思う。
Unicornのダウンタイムなし再起動は考え無しに使うと危険 | ひげろぐ
RailsのデプロイとUnicornのトラブルシューティング | KRAY Inc