今回は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設定を追加します。
:
staging:
<<: *default
database: db/staging.sqlite3
:
config/environments/development.rbをコピーしてconfig/environments/staging.rbを作成します。
(staging環境の位置づけによってはproduction.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と同じ値を入れました。
:
staging:
secret_key_base: xxxxxxxxxxxxxx
<<: *default_twitter
:
###JavaScriptエンジンの設定を追加します。
ターゲットがUbuntuなのでGemfileのtherubyracerを有効にします。
:
gem 'therubyracer', platforms: :ruby
:
bundle install
を実行します。
###unicornの設定を追加します。
Gemfileのunicornの設定を有効にします。
:
gem 'unicorn'
:
bundle install
を実行します。
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を追加します。
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を修正します。
# 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.cap
をlib/capistrano/tasks/unicorn.rb
という名前にすれば良いでしょう)
config/deploy.rbを修正します。
URL等は自分の環境に置き換えてください。
# 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を修正します。
ターゲットサーバーのアドレス、ログイン情報は環境変数から取得するようにしています。
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で通信する想定です。
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で通信させます。
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