22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Cloudn上にCapistrano3を使ってNginx+unicorn+Railsアプリをデプロイしてみた

Last updated at Posted at 2014-08-29

今回はRailsアプリawesome_events(書籍"パーフェクトRuby on Rails"で写経したやつ)をCloudn上で動かすようにします。

capistrano設定についてはQiita記事を参考に構築しましたが、つまったところがあるので自分メモとして整理しておきます。

参考:

Cloudn上でRailsが動く環境を構築

Cloudn上に新しいセキュリティグループtestappを作成

1.ネットワークの"セキュリティグループの追加"から新しいグループtestappを追加します。

2.testappを開き、次のポート設定を追加します。

受信規則にport:22 0.0.0.0./0
受信規則にport:443 0.0.0.0./0
受信規則にport:80 0.0.0.0./0

理由はわかってませんが、セキュリティグループの状態によっては、初期パスワードでログインできませんでした。
Permission denied, please try again.と表示されました)
新規でセキュリティグループを作ればログインできました。

Cloudn上に新しい仮想サーバーtestappを作成

1."仮想サーバーの追加"から新しいインスタンスを作成します。

ISO またはテンプレートの選択:テンプレート
テンプレート:Ubuntu Server v14.04 64bit
仮想サーバープラン:t1.micro
データディスク:設定しない
セキュリティグループ:testapp
名前:testapp

2.初期パスワードをメモします。
1でインスタンスを作成すると、最後にダイアログが表示され、初期パスワードが表示されます。
それをメモしておきます。

3.仮想サーバーにログインします。
仮想サーバー一覧から作成したtestappを選択し、NICタブからIPアドレスを確認します。

そのアドレスに対しsshでログインします。(Ubuntuインスタンスの初期ユーザーはubuntu)

ssh ubuntu@xxx.xxx.xxx.xxx

パスワードは先ほどメモしたものです。

もしWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!と表示された場合は、~/.ssh/known_hostsから該当するIPアドレスの行を削除します

4.パスワードを変更します

passwd

仮想サーバーにrubyの環境を構築

chef等で一気にやるべきと思いますが、自前のDockerfileの手順を手動で行いました。
rubyのバージョン管理にはrbenvを使います。

sudo apt-get update
sudo apt-get install -y build-essential wget curl git
sudo apt-get install -y zlib1g-dev libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt-dev
sudo apt-get install -y sqlite3 libsqlite3-dev
sudo apt-get clean

sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

git clone https://github.com/sstephenson/ruby-build.git .ruby-build
sudo .ruby-build/install.sh
rm -fr .ruby-build

sudo ruby-build 2.1.2 /usr/local

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
rbenv install 2.1.2
rbenv global 2.1.2

gem update --system
gem install bundler --no-rdoc --no-ri
rbenv rehash

RailsアプリにCapistrano設定を追加

###deploy用のstaging環境用の設定を作成します。

database.ymlにstaging設定を追加します。

config/database.yml
:
staging:
  <<: *default
  database: db/staging.sqlite3
:

config/environments/development.rbをコピーしてconfig/environments/staging.rbを作成します。
(staging環境の位置づけによってはproduction.rbからコピーした方が良いと思います)

config/environments/staging.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports and disable caching.
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = false

  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true

  # Adds additional error checking when serving assets at runtime.
  # Checks for improperly declared sprockets dependencies.
  # Raises helpful error messages.
  config.assets.raise_runtime_errors = true

  # Raises error for missing translations
  # config.action_view.raise_on_missing_translations = true
end

config/secrets.ymlにsecret_keyを追加します。
keyの値(xxxxxxxxxxxxxxの部分)はdevelopmentと同じ値を入れました。

config/secrets.yml
:
staging:
  secret_key_base: xxxxxxxxxxxxxx
  <<: *default_twitter
:

###JavaScriptエンジンの設定を追加します。
ターゲットがUbuntuなのでGemfileのtherubyracerを有効にします。

Gemfile
:
gem 'therubyracer',  platforms: :ruby
:

bundle installを実行します。

###unicornの設定を追加します。

Gemfileのunicornの設定を有効にします。

Gemfile
:
gem 'unicorn'
:

bundle installを実行します。

config/unicorn/staging.rbを作成します。

config/unicorn/staging.rb
# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.

# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
# user "unprivileged_user", "unprivileged_group"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
app_path = '/var/www/awesome_events'
app_shared_path = "#{app_path}/shared"
working_directory "#{app_path}/current/" # available in 0.94.0+

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "#{app_shared_path}/tmp/sockets/unicorn.sock"
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid "#{app_shared_path}/tmp/pids/unicorn.pid"

 By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "#{app_shared_path}/log/unicorn.stderr.log"
stdout_path "#{app_shared_path}/log/unicorn.stdout.log"

# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

# Enable this flag to have unicorn test client connections by writing the
# beginning of the HTTP headers before calling the application.  This
# prevents calling the application for connections that have disconnected
# while queued.  This is only guaranteed to detect clients on the same
# host unicorn runs on, and unlikely to detect disconnects even on a
# fast LAN.
check_client_connection false

before_fork do |server, worker|
  ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', ENV['RAILS_ROOT'])

  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # The following is only recommended for memory/DB-constrained
  # installations.  It is not needed if your system can house
  # twice as many worker_processes as you have configured.
  #
  # # This allows a new master process to incrementally
  # # phase out the old master process with SIGTTOU to avoid a
  # # thundering herd (especially in the "preload_app false" case)
  # # when doing a transparent upgrade.  The last worker spawned
  # # will then kill off the old master process with a SIGQUIT.
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
  #
  # Throttle the master from forking too quickly by sleeping.  Due
  # to the implementation of standard Unix signal handlers, this
  # helps (but does not completely) prevent identical, repeated signals
  # from being lost when the receiving process is busy.
  # sleep 1
end

after_fork do |server, worker|
  # per-process listener ports for debugging/admin/migrations
  # addr = "127.0.0.1:#{9293 + worker.nr}"
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)

  # the following is *required* for Rails + "preload_app true",
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

config/unicorn/staging.rbは、~/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/unicorn-4.8.3/examples/unicorn.conf.rbをコピーして作りました。

path等の設定だけではcap実行時にunicornのreloadでこけてしまいました。
そこは次の2つの対応で解決しました。

ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', ENV['RAILS_ROOT'])

の行を追加。

old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

の行を有効化。

###capistrano関連を追加します。

Gemfileにcapistranoを追加します。

Gemfile
group :development, :test do
  gem 'capistrano', :require => false
  gem 'capistrano-rails', :require => false
  gem 'capistrano-rbenv', :require => false
  gem 'capistrano-bundler', :require => false
end

bundle installを実行します。

bundle exec cap installを実行し、capistrano関連ファイルを生成します。

Capfileを修正します。

Capfile
# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

# Includes tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#
# require 'capistrano/rvm'
require 'capistrano/rbenv'
set :rbenv_type, :user
set :rbenv_ruby, '2.1.2'
# require 'capistrano/chruby'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

最後の行はもともとtasks/.rbとなっていたので、tasks/.capに変更しています。
(そのまま使う場合は、後述のlib/capistrano/tasks/unicorn.caplib/capistrano/tasks/unicorn.rbという名前にすれば良いでしょう)

config/deploy.rbを修正します。
URL等は自分の環境に置き換えてください。

config/deploy.rb
# config valid only for Capistrano 3.1
lock '3.2.1'

set :application, 'awesome_events'
set :repo_url, 'https://github.com/t-oginogin/awesome_eventes.git'

# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call
set :branch, :master

# Default deploy_to directory is /var/www/my_app
set :deploy_to, '/var/www/awesome_events'

# Default value for :scm is :git
set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
set :log_level, :debug

# Default value for :pty is false
set :pty, true

# Default value for :linked_files is []
# set :linked_files, %w{config/database.yml}

# Default value for linked_dirs is []
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/assets}

# Default value for default_env is {}
set :default_env, { path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH" }

# Default value for keep_releases is 5
set :keep_releases, 5

namespace :deploy do

  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end

  after :publishing, :restart

end

config/deploy/staging.rbを修正します。

ターゲットサーバーのアドレス、ログイン情報は環境変数から取得するようにしています。

config/deploy/staging.rb
set :stage, :staging
#
# Simple Role Syntax
# ==================
# Supports bulk-adding hosts to roles, the primary server in each group
# is considered to be the first unless any hosts have the primary
# property set.  Don't declare `role :all`, it's a meta role.

role :app, %W{#{ENV['AWESOME_USER']}@#{ENV['AWESOME_ADDRESS']}}
role :web, %W{#{ENV['AWESOME_USER']}@#{ENV['AWESOME_ADDRESS']}}
role :db,  %W{#{ENV['AWESOME_USER']}@#{ENV['AWESOME_ADDRESS']}}


# Extended Server Syntax
# ======================
# This can be used to drop a more detailed server definition into the
# server list. The second argument is a, or duck-types, Hash and is
# used to set extended properties on the server.

server "#{ENV['AWESOME_ADDRESS']}", user: "#{ENV['AWESOME_USER']}", roles: %w{web app db}

# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start).
#
# Global options
# --------------
set :ssh_options, {
#    keys: %w(/home/rlisowski/.ssh/id_rsa),
#    forward_agent: false,
  auth_methods: %w(password),
  password: ENV['AWESOME_PASS']
}
#
# And/or per server (overrides global)
# ------------------------------------
# server 'example.com',
#   user: 'user_name',
#   roles: %w{web app},
#   ssh_options: {
#     user: 'user_name', # overrides user setting above
#     keys: %w(/home/user_name/.ssh/id_rsa),
#     forward_agent: false,
#     auth_methods: %w(publickey password)
#     # password: 'please use keys'
#   }

lib/capistrano/tasks/unicorn.capを作成します。
Nginxとはport8080で通信する想定です。

lib/capistrano/tasks/unicorn.cap
namespace :unicorn do
  task :environment do
    set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn/#{fetch(:rails_env)}.rb"
  end

  def start_unicorn
    within current_path do
      execute :bundle, :exec, :unicorn_rails, "-c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -p 8080 -D"
    end
  end

  def stop_unicorn
    execute :kill, "-s QUIT $(< #{fetch(:unicorn_pid)})"
  end

  def reload_unicorn
    execute :kill, "-s USR2 $(< #{fetch(:unicorn_pid)})"
  end

  def force_stop_unicorn
    execute :kill, "$(< #{fetch(:unicorn_pid)})"
  end

  desc "Start unicorn server"
  task :start => :environment do
    on roles(:app) do
      start_unicorn
    end
  end

  desc "Stop unicorn server gracefully"
  task :stop => :environment do
    on roles(:app) do
      stop_unicorn
    end
  end

  desc "Restart unicorn server gracefully"
  task :restart => :environment do
    on roles(:app) do
      if test("[ -f #{fetch(:unicorn_pid)} ]")
        reload_unicorn
      else
        start_unicorn
      end
    end
  end

  desc "Stop unicorn server immediately"
  task :force_stop => :environment do
    on roles(:app) do
      force_stop_unicorn
    end
  end
end

###ターゲットサーバー上でNginxを設定します。
本当はここも自動化しておきたいところですが、今回は手動で設定しました。
jenkinsのインストールが参考になります)

sudo aptitude -y install nginx
cd /etc/nginx/sites-available
sudo rm default ../sites-enabled/default

/etc/nginx/sites-availableにawesome_eventsを配置します。
unicornとはport8080で通信させます。

/etc/nginx/sites-available/awesome_events
upstream app_server {
    server 127.0.0.1:8080 fail_timeout=0;
}

server {
    listen 80;
    listen [::]:80 default ipv6only=on;
    server_name awesome_events;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://app_server;
            break;
        }
    }
}
sudo ln -s /etc/nginx/sites-available/awesome_events /etc/nginx/sites-enabled/
sudo service nginx restart

###deploy場所を作っておきます。

sudo mkdir /var/www
sudo chown ubuntu /var/www
sudo chgrp ubuntu /var/www
sudo chmod +rwx /var/www

##ローカルマシンからdepoyを実行します。

ローカルマシンに戻って、capコマンドでデプロイします。
いくつか環境変数で設定するようにしているので、忘れず設定します。

bundle exec cap staging deploy AWESOME_ADDRESS=xxx.xxx.xxx.xxx AWESOME_USER=ubuntu  AWESOME_PASS=your_password

無事デプロイできました。

ソースはGitHubに置いています。
https://github.com/t-oginogin/awesome_eventes

22
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?