genonymous

GenestreamのTechブログ

Capistrano3 + Rails4 + Unicorn + Nginx + EC2でサーバー構築!

みなさん、こんにちは。
入社2ヶ月目の佐野です。
今回はRailsで本番環境への以降の際、ハマった点が多かったのでみなさんと共有したいと思います。
間違っている点などがあればご指摘いただけると幸いです。

概要

EC2へデプロイし、とりあえず繋がるまで
情報がバラバラで調べるのにかなり時間が取られてしまったので、一連の流れをまとめました

このブログの対象者

Railsでのデプロイが初めての方

使用した環境

Rails 4.1.0 
Ruby 2.0.0-p576
rbenv 0.4.0 
Capistrano 3.2.1 
unicorn 4.8.3 
Nginx 1.6.2 
Mysql 5.5.40
EC2 Amazon Linux AMI 2014.09.1 (HVM)(プロダクションマシン) 
Git(リポジトリマシン)

デプロイ手順(サーバー側)

EC2にてインスタンスの作成

先にサーバー側の設定をします。
AWSサーバーにてEC2のインスタンスを作成します。

  • Amazon Web Servicesへログイン
  • EC2を選択
  • 左のサイドメニューのInstancesを選択
  • 真ん中のCreate InstanceのLaunch Instanceをクリック
  • Amazon Linux AMI 2014.09.1 (HVM)を選択
  • 使用するマシンの性能がずらずらと出てきます。

今回は初めてなので上から2番目のGeneral purposet2 smallを選択しました

  • NextConfigureInstanceDetailで次へ
  • 何もしないでNextAddStrageへ
  • 何もしないでNextTagInstanceへ
  • ここでインスタンスの名前を決めます(マシンの名前なので任意でOKです)
  • Configure Security Groupでファイアーウォールの設定をします。

最低でもMYSQL,SSH,HTTP,HTTPSの四つは許可しないとサーバーとの接続ができません。 
すでに設定してある方はそちらを使い、あとは自分の利用したい環境や必要に応じて
Customで作成しましょう。

  • 次に確認画面が出ますので良ければLaunchをクリック
  • キーペアの作成のモーダル画面が出ます

今使っているものを利用するか、新しく作るかが選択できますが、初めてなのでCreate new key pair
キーペアの名前を任意でつけてDownload Key Pairで自分のPCにダウンロードします。

  • Launch Instanceをクリックして完了

インスタンス作成直後は初期化処理が入るので数分待つ必要があります。
初期化が終わったらstatusrunningに変わります。
最後にElasticIPの設定をします。

  • 左メニューのElastic IPsを選択
  • 左上のAllocate New Addressをクリック
  • Yes Allocate

これで作成完了です。
「Instance」の一覧から、さきほど作ったインスタンスのPublic IPなどが確認できるのでコピーしておきます。

ローカルからEC2インスタンスへのログイン

次にローカルからEC2へログインできるようにします
インスタンス作成時にダウンロードしたキー(自分がつけた名前.pemのファイル)を他から見られないように
.sshディレクトリに移動します。
ファイルの権限を変更

chmod 400  ~/.ssh/自分のつけた名前.pem
ssh -i  ~/.ssh/自分のつけた名前.pem ec2-user@設定したElastic IP

これでPermission Denied(publock key)となるならchmod 600に変更する
これらのコマンドで以下の画面が出ればOK

__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|

初期設定ではec2-userというユーザーが作成されています。
これを自分で作成したユーザーに変更した方が良いのでユーザーを作成します。

ユーザー作成(EC2上での操作)

-rootユーザーしか作成ができないのでrootへ変更

$ sudo -su
useradd 任意の名前
$ cp -arp /home/ec2-user/.ssh /home/任意の名前/
$ chown -R 任意の名前. /home/任意の名前/.ssh
$ passwd 任意の名前

-sudo権限を付与する

$ sudo visudo
任意の名前 ALL=(ALL) ALL を追記
exitしてec2-userへ戻り作成したユーザーで入れるか確認

$ su - 作成したユーザー名
パスワード入力でログインできればOK

必要なものを順番にインストール

これらのコマンドで大体必要なものが入る
$ sudo yum update
$ sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison git

これからインストールするものは必ずローカル環境のバージョンと合わせるようにする

rbenvを入れる
cd
git clone git://github.com/sstephenson/ruby-build.git
cd ruby-build
sudo ./install.sh
cd
git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bashrc
exec $SHELL -l
rbenv install -l

自分が開発で使ったrubyのバージョンを指定しないとダメ
rbenv install 2.0.0-p576;rbenv rehash
rbenv global 2.0.0-p576

ちゃんと入ったか確認
$ git --version
git version 2.1.0
$ ruby -v
ruby 2.0.0−p576
$ which gem
~/.rbenv/shims/gem
$ which ruby
~/.rbenv/shims/ruby

bundlerを入れてRailsをインストール
bundlerもバージョン合わせる(最新を使えばほぼ問題無し)
$ gem install bundler --no-rdoc --no-ri
Successfully installed bundler-1.7.9
$ sudo rbenv rehash
$ gem install rails -v 4.1.0
$ rbenv rehash
$ rails -v
Rails 4.1.0
$ which rails
~/.rbenv/shims/rails

Mysqlを入れる
$ sudo yum install mysql-server mysql-devel
$ sudo service mysqld start
$ mysqladmin -u root password '新しいパスワード'
$ mysql -u root -p

ログインできるか確認
ログインできればそのまま次に設定を行う

Mysqlの設定(ユーザー、データベース作成)

mysql> GRANT ALL PRIVILEGES ON *.* TO '任意のユーザー名'@'設定したElasticIP' IDENTIFIED BY 'パスワード決める' WITH GRANT OPTION;
mysql> FLUSH PRIVILEGES;
作れたか確認
mysql> select Host, User, Password from mysql.user;
データベース作成
mysql> CREATE DATABASE 任意のデータベース名 CHARACTER SET utf8;
作れたか確認
mysql> show databases;
終了
mysql> quit

アプリをデプロイするディレクトリを作成
現在のディレクトリ確認
$ pwd
/home/ec2-user
デプロイは/var/www以下に作成することにしました。
$ mkdir -p var/www/あなたのアプリ名

Nginxを入れる
$ sudo yum -y install nginx
起動確認
$ sudo /etc/init.d/nginx start
設定ファイルを編集
$ sudo vi /etc/nginx/nginx.conf

events {
    worker_connections  2048;
}
#通信情報
http {
     #最新のアプリはcapistranoがcurrentに最新のデプロイしたアプリのシンボリックリンクが貼られるのでそこを指定すればOK
    root  /var/www/あなたのアプリ名/current;
    #unicornに必要
    #http{}の中に記述しないと動かない
    upstream unicorn-server {
        server unix:/var/www/あなたのアプリ名/shared/tmp/sockets/unicorn.sock
        fail_timeout=0;
    }
    #unicorn-serverという名前は任意でOK。プロキシで設定する名前と同じなら大丈夫
#サーバー情報
    server {

        listen 80;
        client_max_body_size 4G;
        server_name あなたのEC2のpublic IP;
        keepalive_timeout 80;
     #ログ関係は設定しないとデバッグが不可なので必須(下記はデフォルトのフォルダを指定しているので作成する必要はない)
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        root  /var/www/あたなのアプリ名/current;
#本番環境ではrailsのpublic以下のassetが使用されるのでそこを指定
        location ~ ^/assets/ {
            include /etc/nginx/mime.types;
            root    /var/www/あなたのアプリ名/current/public;
        }
       location / {
            proxy_pass http://unicorn-server;#ここのunicorn-serverという名前をupstreamと合わせる必要がある
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
        }
#エラー画面の場所(railsのディレクトリを指定する)
        error_page   500 502 503 504  /500.html;
        location = /500.html {
            root /var/www/teamhacker/current/public;
        }
    }
}
これが終わったら:wqで保存して再起動
$ sudo service nginx restart
stop,start [OK]が出ればOK
出なかった場合は設定が間違っているのでログを見ながら直す

デプロイ手順(ローカル側)

ここまで終わったら次はローカル側の設定をします。
$ exit
でEC2サーバーからログアウトします。

デプロイするために追加したGem

group :production, :staging do
  gem 'unicorn'
end
group :development do
  gem 'capistrano', '~> 3.2.1'
  gem 'capistrano-rails',   '~> 1.1', require: false
  gem 'capistrano-bundler', '~> 1.1', require: false
  gem 'capistrano-rbenv', '~> 2.0', require: false
  gem 'capistrano3-unicorn'
end
bundle exec bundle install
Capistranoの設定ファイルを生成
bundle exec cap install STAGES=staging,production

Capfileを編集

require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rails'
require 'capistrano/rbenv'

Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

Deploy.rbを編集

set :application, "あなたのアプリ名"
set :repo_url, 'あなたのgitのレポジトリ名'#gitからコードをcloneする
set :branch, 'master' #マージ前なら他のブランチでも設定可能
set :deploy_to, '/var/www/EC2で作ったディレクトリ名'
set :keep_releases, 5 #接続を確保して同時接続を高速化するための設定。アクセス数に応じて変える必要あり
set :rbenv_type, :user
set :rbenv_ruby, '2.0.0-p576'     #rubyのバージョン間違えないように!
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
set :rbenv_roles, :all
set :linked_dirs, %w{bin log tmp/backup tmp/pids tmp/cache tmp/sockets vendor/bundle}

namespace :deploy do

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
       execute :touch, release_path.join('tmp/restart.txt')
    end
  end

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
  after :finishing, 'deploy:cleanup'
end

config/unicorn.rbの編集

application = 'reserve-hacker'

worker_processes 2 #EC2で作ったAmazonLinuxのCPU数より少し大きく
app_path = "/var/www/EC2で作ったディレクトリ名"
#標準だとsharedに作成される
#ここが一番重要
#Nginxのupstreamで設定した「server unix:/var/www/あなたのアプリ名/shared/tmp/sockets/unicorn.sock」の場所と合わせる!!
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid "#{app_path}/current/tmp/unicorn.pid"

#何秒でタイムアウトするか
timeout 60

#ダウンタムをなくす
preload_app true

stdout_path "#{app_path}/current/log/production.log"# 標準出力ログ出力先
stderr_path "#{app_path}/current/log/production.log"# 標準エラー出力ログ出力先

GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true

config/deploy/producution.rbの編集

set :stage, :production
set :rails_env, 'production'
server 'EC2のElasticIP', user: 'EC2で作成したユーザー名(rootのユーザー。Mysqlとは違うよ)', 
roles: %w{web app db}  #何のサーバーに処理をさせるのか。今回は同じサーバーで全部動かすのでweb app db全て指定
#sshでEC2に入るのに必要
set :ssh_options, {
   keys: [File.expand_path('~/.ssh/EC2で任意でつけてダウンロードしたキー名.pem)')]
}

database.ymlの編集

#ここをEC2のサーバー側で作ったMysqlのユーザー名、パスワード、ホスト名を書く
production:
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: EC2で作ったmysqlユーザー
  password: EC2で作ったユーザーのパスワード
  host: EC2ElasticIP
  database: あなたが作ったデータベース名

もともとMysqlで開発していたのであればrailsのdatabase名をコピーしてからEC2に入って作った方が早いです。
そちらの方が間違いが少ないです。
名前とかで間違えると後から大変なのできちんと確認しながら設定するようにしてください。

ここまで来たらあとは実際にデプロイしてみて動くかどうか確認してみましょう!
デプロイ実行コマンド

bundle exec cap production deploy

なお今回はstagingとproductionを分けないで、いきなりproductionでデプロイしてます。
stagingでデプロイする場合は

bundle exec cap staging deploy

となります。
config/deploy/prodcution.rbの内容をconfig/deploy/staging.rbにコピーすることも忘れないようにしてください。

長くなりましたがお付き合いいただきありがとうございました。
私の場合はprecompile等のエラーが出たのでCSS等は反映されていませんがなんとか動きました。
その辺りのエラーも解消できれば追記したいと思います。
みなさんのデプロイにかける時間が短縮することを願っております。

最後にデプロイでよく使うコマンドの一覧を書いておきます。
お役に立てば幸いです。

ステージング環境へデプロイ
$ bundle exec cap staging deploy
ステージング環境へデプロイ
$ bundle exec cap production deploy
unicornが起動しているか確認
$ ps aux | grep unicorn
unicornをデーモンで起動
$ bundle exec unicorn -c config/unicorn.rb -D
サーバー側でのnginxの設定ファイルを編集
$ sudo vim /etc/nginx/nginx.conf
サーバー側でのnginxのエラーログを確認
$ sudo vim /var/log/nginx/error.log
サーバー側でのrailsのエラーログを確認
$ vi /var/www/あなたのアプリ名/current/log/production.rb