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

  • 152
    いいね
  • 5
    コメント

*追記
2015/8/14
RubyとRailsを最新バージョンに変更しました。(動作確認済み)
unicornが起動しない問題があったので追加しました。(ディレクトリの作成し忘れ)
分かりやすいように画像を追加しました。
コマンドの間違いを修正しました。

みなさん、こんにちは。佐野です。
今回はRailsで本番環境へデプロイする際、初めてでさっぱり分からずハマった点が多かったのでみなさんと共有したいと思います。
まだ未熟なので間違っている点などがあればご指摘いただけると幸いです。

概要

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

このブログの対象者

Railsでの初デプロイで死にかけの方

使用した環境

  • Rails 4.2.3 
  • Ruby 2.2.2
  • rbenv 0.4.0 
  • Capistrano 3.2.1 
  • unicorn 4.8.3 
  • Nginx 1.6.2 
  • Mysql 5.5.40
  • EC2 Amazon Linux AMI (HVM) 
  • Git

具体的なデプロイを始める前に

ほとんどの初心者がつまずく理由は
「そもそもインフラの知識がない」
ことが原因だと思います。
僕はCapistranoやNginxの公式サイトを読んだり、さくらVPSやEC2を個人で使って、何回かデプロイするうちに大分理解ができました。
時間を取って、インフラの知識の本を一冊くらいは読んでみた方が良いと思います。
概念や基礎知識を付けてから実際に作業をすると理解のスピードが全然違います。
そこで最初にデプロイの仕組みとCapistranoの仕様について解説することにします。

デプロイとは?

実際にサービスを利用可能にすることです。
テスト環境や本番環境でサービスを動かして運用や保守をしていく上で、最低でも5つのマシンが必要になります。
1、作業マシン
2、プロダクションマシン
3、ステージングマシンy
4、データベースマシン
5、リポジトリマシン
の5つです。
これらはほぼどんなサービスでも必須になります。

作業マシンは普段皆さんが開発を行っているパソコンのことですね。
プロダクションマシンとステージングマシンは実際にWebサーバーを動作させてアプリやサービスを稼働させるマシンです。
プロダクションマシンは「本番用」でステージングマシンは「テスト用」です。

データベースマシンはデータを保存するマシンです。
プロダクションマシンやステージングマシンを兼用で使いますが、最近はAWSのRDSのサービスを使ってデーターベースを切り分けるパターンが増えています。
あとからマシンを増やすことが簡単、バックアップなどが自動化されるので管理コストが下がるなどのメリットがあります。
慣れてきたらRDSなどのサービスを使ってみましょう。
AWSには他にも便利なサービスがたくさんあるので色々使ってみることをおすすめします。
最後のリポジトリマシンは

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

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

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

 1、Amazon Web Servicesへログイン(アカウントがない人はまず新規で申し込みしておいてください)
 alt
 alt

 2、EC2を選択
 alt

 
 3、真ん中あたりのLaunch Instanceを選択
 alt 

 4、Amazon Linux AMI 2014.09.1 (HVM)を選択
 alt

 5、使用するマシンの性能がずらずらと出てきます。
  デフォルトが無料キャンペーン対象の一番性能が低いものになっているのですがただのホームページではないので若干厳しい。
  初めて構築する、またはテストで使うだけなら無料キャンペーン対象のデフォルトのままでOKでしょう。
  今回は本番で公開するサービスなので上から2番目のGeneral purposet2 smallを選択しました。
 alt
 

 6、NextConfigureInstanceDetail
  何もしないでNextAddStrageへ
 7、NextAddStrage
  何もしないでNextTagInstanceへ
 8、NextTagInstance
  ここでインスタンスの名前を決めます。
  マシンの名前なので何でも良いですが、テスト環境などを用意する場合が多いので、分かり易いようにサービス名+環境名(production,stagingなど)で名前をつけた方が良いでしょう。
 alt

 9、Configure Security Groupでファイアーウォールの設定をします。
  最低でもMYSQL,SSH,HTTP,HTTPSの4つは、基本となるのでとりあえず追加しておきました。 
  すでに設定してあるものがあったらそちらを使い、あとは自分の利用したい環境や必要に応じてCustomで作成しましょう。
 alt

 10、次に確認画面が出ますので良ければLaunchをクリック
 alt

 11、キーペアの作成のモーダル画面が出ます
  今使っているものを利用するか、新しく作るかが選択できますが、初めてなので新規作成。
 キーペアの名前を任意でつけてDownload Key Pairで自分のPCにダウンロードします。
 ダウンロードが終わったら、Launch Instanceをクリックして完了
 alt

あとはインスタンス画面に戻って、インスタンス初期化を待つだけです。
初期化は数分待つ必要があります。
初期化が終わったらstatusrunningに変わります。

最後にElasticIPの設定をします。

インスタンスに割り当てされているIPアドレスは、サーバーを再起動したりすると変わってしまいます。
変わってしまうといろいろと面倒なのでElasticIPを設定し固定にしておいた方がいいでしょう。 

 1、左メニューのElastic IPsを選択
  alt 

 2、Allocate New Addressをクリック
  alt
  alt 

 3、Associate Addressをクリック
  alt 

 4、インスタンスと紐付ける。
  alt

 
これで作成完了です。

「Instance」の一覧から、さきほど作ったインスタンスのPublic IPがElastincIPに変更に
なっていることを確認できればOK。

ローカルから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
作成したユーザーで入れるかする確認
$ su - 作成したユーザー名
パスワード入力でログインできればOK!
ユーザーが作成できたら、ローカルから簡単にsshでログインできるように設定しましょう。
PCの/.sshディレクトリ以下にconfigファイルがあると思います。
これに以下の内容を追加します。

Host インスタンスのIPアドレス
 Hostname インスタンスのIPアドレス
 User サーバーで作ったユーザー
 Port 22
 PasswordAuthentication no
 IdentityFile ~/.ssh/ダウンロードしたファイル名.pem
 IdentitiesOnly yes
 ForwardAgent yes

そして保存します。
そうしたら
$ ssh -i ~/.ssh/自分のつけた名前.pem ec2-user@設定したElastic IP
と長ったらしいコマンドを打たなくても
$ ssh あなたが作成したユーザー名@ElasticIPアドレス
でサーバーにログインできるようになります。

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

これらのコマンドで大体必要なものが入る
$ 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を入れる

$ 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
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
以下のコマンドが使えればOK。
$ rbenv install -l

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

ちゃんと入ったか確認

$ git --version
git version 2.2.2
$ ruby -v
ruby 2.2.2
$ which gem
~/.rbenv/shims/gem
$ which ruby
~/.rbenv/shims/ruby

bundlerを入れてRailsをインストール
bundlerもバージョン合わせる(最新を使えばほぼ問題無し)
$ gem install bundler --no-rdoc --no-ri
Successfully installed bundler-1.10.6
$ sudo rbenv rehash
$ gem install rails -v 4.2.3
$ rbenv rehash
$ rails -v
Rails 4.2.3
$ 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の設定(ユーザー、データベース作成)

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/あなたが作ったユーザー名
デプロイは初めてなら/var/www以下に作成した方がいいでしょう。 
任意でOKですが、Capistranoがデフォルトを/var/wwwとしているからです。
まずは必要なディレクトリを作成します。
$ mkdir -p /var/www/あなたのアプリ名
ログインユーザーにディレクトリの所有権を与えます。
$ chown -R あなたが作ったユーザー名 /var/www/あなたのアプリ名
僕はここで間違って/var以下に所有権を与えてしまい、sshでログインができない事象に陥りました。
エラーで検索しましたが直らなかったので、インスタンスを最初から作り直すはめになりました(泣)
気をつけてください。

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

「あなたのアプリ名」はunicorn.rbに書いたapp_path = "/var/www/#{あなたのアプリ名}"と一緒にするようにしましょう。

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 あなたのEC2public 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/あなたのアプリ名/current/public;
        }
    }
}

編集が終わったら:wqで保存して再起動
$ sudo service nginx restart
stop,start [OK]が出ればOK

出なかった場合は設定が間違っているのでterminalのエラーを見て直す

ここまで終わったら最初にインスタンスを作ったAWSのEC2のconsole画面に戻りこのインスタンスのAMIを作成しておきましょう。

これを作っておけばこの段階から新しくやり直しができる。

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

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

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

Gemfile
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の設定ファイルを生成
4 bundle exec cap install STAGES=staging,production

Capfileを編集

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を編集

config/deploy.rb
set :application, "あなたのアプリ名"
set :repo_url, 'あなたのgitのレポジトリ名'#gitからコードをcloneする
set :branch, 'master' #マージ前なら他のブランチでも設定可能
set :deploy_to, '/var/www/EC2で作ったディレクトリ名'
set :keep_releases, 5 #何個アプリを確保しておくか。この場合はデプロイした最新のアプリ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}

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end

config/unicorn.rbの編集

config/unicorn.rb
application = 'reserve-hacker'

worker_processes 2 #EC2のインスタンスの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の編集

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の編集

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

ここまで来たらあとは実際に一度デプロイしてみてみます!
が、本番に初めてあげる場合はsecret_key_baseが設定されていないので
$ bundle exec rake secret
で作成して、環境変数に追加しておきましょう。

それではいざデプロイ!!

デプロイ実行コマンド
$ bundle exec cap production deploy

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

stagingなどテスト環境を用意する場合は

$ bundle exec cap staging deploy

となります。
一般的に使われるのはstagingとかtestっていう環境名であるが、stagingの部分はファイル名を合わせれば好きな名前が付けれる。
例えば、deoloy/sano.rbファイルを用意して設定すれば
$ bundle exec cap sano deploy
なんかも可能。
複数台を同時に設定する場合の参考にしてください。

おそらく、これでデプロイしても動きません。
理由は、unicorn.rbに設定したerrorログのディレクトリが存在しないからです。
デプロイ中に赤文字でエラーが出てたはずです。
サーバーに入って
$ cd /var/www/あなたのアプリ名/shared/log
といって
$ mkdir unicorn
とディレクトリを作ってあげましょう。
そうしないとエラーログが見れないのでデバッグが困難になります。
というか動きません。
最初から作ればいいはなしですが、capistranoが足りないディレクトリをデフォルトで作ってくれるので一旦デプロイしました。

ディレクトリを作ったらもう一度デプロイコマンドします。
これでちゃんとunicornが起動するはず。
デプロイが成功したのに起動しなかった場合は、さっき作ったディレクトリの中のunicornのエラーログを見てみましょう。

長くなりましたがお読みいただきありがとうございました。

私の場合はprecompile等のエラーが出たのでCSS等は反映されていませんがなんとか動きました。

その辺りのエラーも解消できれば追記したいと思います。

みなさんがデプロイで精神がやられている時間が少しでも短縮することを願っております。

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

お役に立てば幸いです。

NginxにBASIC認証をかける場合

$ sudo yum install httpd-tools
$ cd /etc/nginx
$ sudo htpasswd -c .htpasswd ユーザー名
New password: パスワード
Re-type new password:もう一回入力
Nginxの設定ファイルを編集する
$ sudo vim /etc/nginx/nginx.conf
以下の2行を追加する
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
ディレクトリごとに認証する場合は

location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}

と、location以下に書く。
サイト全体にかける場合はもう一つ外のディレクトリのserver{}の中に書く。
情報は多々あるので詳細は省略します。

ステージング環境へデプロイ

$ bundle exec cap staging deploy

プロダクションマシン環境へデプロイ

$ bundle exec cap production deploy

unicornが起動しているか確認

$ ps aux | grep unicorn

本番環境でunicornを手動でデーモン起動

$ bundle exec unicorn -E production -c config/unicorn.rb -D

unicornのmasterプロセスのIDを特定する

$ sudo pgrep -f 'unicorn_rails master'

unicornの起動を停止

$ kill -QUIT 'cat /var/www/あなたが作ったディレクト名名/current/tmp/pids/unicorn.pid'

サーバー側でのnginxの設定ファイルを編集

$ sudo vim /etc/nginx/nginx.conf

サーバー側でのnginxのエラーログを確認

$ sudo vim /var/log/nginx/error.log

サーバー側でのrailsのエラーログを確認

$ vi /var/www/あなたが作ったディレクトリ名/current/log/production.rb

ハマったエラー

capistranoのdeployコマンドを実行した時にdeviseのシークレットキーでエラーが出る

以下のコードを追加して解決した。
secret_token.rbに書く方がセキュリティが高いのでオススメですが、環境変数で指定すると
エラーが直らなかったので直接書いた。

config/initializer/devise.rb
config.secret_key = 'terminalのエラーで追加するように言われたキー'

SHKit::Runner::ExecuteError: Exception.....

bundle error
サーバー側とのrails側とのパス指定、ruby、rails、bundlerなどの各バージョンが相違している場合に起こる。
自分は過去に開発途中で一部のGemをバージョンダウンしていたのでGemfile.lockと相違しており発生した。
gitのレポジトリが常にサーバー側と一致していないといけない

permission denied(public key)
サーバー側の認証が通っていない。
sshキーの指定の間違い、サーバー側で作成したユーザーがファイル書き込み権限がない
などでよく起こる。
ローカルとサーバー間を行ったり来たりするはめになる。
ls -l コマンドで権限を確認してchmod,chownコマンドを使用して修正した。
しかしこの時、権限の与え方を間違えるとインスタンス作成からやり直すことになるので
注意が必要。
自分はユーザー作成後、デフォルトのec2-userを後に削除したがec2-userの権限が残っており
sshで再ログインできなくなった。
.ssh/configファイルを作ってログインするように変更したので大丈夫になったが、怖くてec2-userが削除できない

デプロイ時のrake:migrateがエラー
これはサーバー上とdatabase.ymlのmysqlのユーザー、またはパスワードが相違している可能性が高い。
この辺りはエラーメッセージを読めばだいたい対処可能。

socketが動かない
(nginx: connect() failed (111: Connection refused) while connecting to upstream)

原因は接続が拒否されている。

ソケットのパス指定が間違えている。
nginxのconfファイルをもう一度見直す。
絶対パスで指定する。
指定できるところは指定しないとこういったところ自動は怖い。
エラーが起きても原因が見つけにくい。
capistrano3-railsのGemを入れないとそもそもtmp/sockets/unicorn.sockファイルが生成されない。
ファイルが存在しないのだからそりゃつながらない。
自分はこれではまった。
capistranoのバージョン3以上を使っているなら必須です。

socketはつながっているが認証が通っていない場合も起こる。
インスタンスのユーザー指定が間違えている。
パーミッションが違うなどがほとんど。
EC2のインスタンスのセキュリティグループの設定が間違っている場合も起こる。
必要なアクセスが許可されていない場合があるので一応見直しましょう。

We're sorry, but something went wrong
接続ができたがrailsのエラー画面が出る
これはunicornが起動していない可能性が高い。
capistranoのdeploy.rbファイルとunicorn.rbファイルを見直した方がいい。
いろいろなカスタムメソッドがネット上に書いてあるが、自分は余計なものを何も書かなければ
トップ画面が表示された。
カスタムはもっと勉強してからにしよう。

oauth認証が通らない
サーバーにアクセスしたがgoogleのoauth認証が通らない。
callbackなどの設定をgoogledeveloperconsoleで設定し直す必要があった。
ドメインを取っていないのであれば、callbackのurlに直接hostは書けないので
hostsファイルで別名を設定する必要がある
しかし今度はInvalidClientとエラーが出た。
解決
google developer consoleにてAPIにて必須のAPIを追加していなかった。
CalendarAPIを使っていたがそれだけじゃダメみたいです。
Google+API、Contacts APIは必ずONにしないといけないみたい。
ONにすると認証が通りました。
サーバーとは関係のない問題でしたがよくありそうなので記載した。
原因の箇所をサーバー側なのかアプリケーション側なのかを見極めるのが大事だと分かった。

precompileがエラーが出る(CSS,JSファイルがない)
favicon,glyphiconあたりが怪しいので削除してみたが解決しなかった。
しかし、HTMLが変化していないのでunicornが再起動していない可能性があった。
pidを確認してみたらやはり再起動していなかった。
原因は、unicornが利用するディレクトリを作成し忘れていたため。