Shred IT!!!!

IT全般について試したこと・勉強したことを綴ったり、趣味について語るブログ

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

初期状態のログイン用メールアドレスとパスワードは下記の通り。

この時点で管理者ユーザの一覧/詳細表示、追加・編集・削除の機能が備わっている。

管理機能を追加


適当なモデルを作って、それを管理する機能を作ってみる。

モデルを作る

モデルを作って、マイグレートする。

# 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で指定しなければいけない。

日本語化する前のスクリーンショット

f:id:jetglass:20150726104314p:plain

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 があるので、ダウンロードして設置すること。

別途、モデル向けの 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: '必須'
日本語化した後のスクリーンショット

f:id:jetglass:20150726104415p:plain

エラーメッセージ


エラーメッセージを追加で表示する

モデルにバリデートを追加した場合、編集画面でエラーが表示される。

# app/models/name.rb
class Name < ActiveRecord::Base
  validates :name, presence: true
  validates :date, presence: true
end

f:id:jetglass:20150726110146p:plain


画面の上部にエラーメッセージを表示する方法。

# app/admin/name.rb
...
  form do |f|
    # エラー表示枠を表示、シンボルで項目を指定
    f.semantic_errors :name
    # モデルの入力項目を表示
    f.inputs
    # 登録・更新などのボタンの表示
    f.actions
  end
...

f:id:jetglass:20150726110206p:plain

ドキュメント

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つについて、それぞれ実装して使い心地を比較する。

それと上記サイトで書かれている Active Job これの使い勝手も試してみる。

それぞれの gem について、もっと詳しく知りたい方は下記参照。
メリット・デメリットがそれぞれまとめられているし、ジョブを実行する際の違いも書かれてます。

qa.atmarkit.co.jp

stackoverflow.com

準備


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_mailhoge.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 も選択肢に入る。
それぞれ下記の特徴があるのでインフラとの兼ね合いや好みか。

  • Sidekiq
  • Resque
    • fork して 1プロセス毎実行
    • メモリリークの心配はないが、fork のコストがかかる

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'
  }

CapistranoUnicorn での設定ファイルを全てコミット、プッシュする。

# 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

コントローラー・モデル・ビューを修正(省略

デプロイしてみる、リロード連打しながらどうなるか。
ホットデプロイ感を味わえるか・・・

f:id:jetglass:20150626162930g:plain

チカチカしてうざいけど、ダウンタイムが発生しないでデプロイできることが確認できた。


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 へ追記

macbrew を使ってインストールした場合、 下記に設定ファイルがあるので開いて編集。

# 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

参考


Nginx+UnicornでRailsを動かすon Mac - Qiita

rails4 + unicorn + nginx - Qiita