Shred IT!!!!

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 で簡単にできるようになっているし、 ホットデプロイの環境でサービスを運営してみたいと思った。