Rails5系で新しく開発しているウェブアプリケーションを、AWSのEC2で稼働している本番環境にCapistranoでデプロイすることになったときのお話です。これまで個人でなにかを開発する際にはRails4系でアプリケーションサーバにUnicornを採用していましたが、今回からはPumaを採用することにしたのでその手順です。
こちらの記事の後編です。
Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(前編)
前提
- Rails 5.0.0
- Ruby 2.4.0
- rbenv 0.4.0
- Puma
- Nginx
- Capistrano 3.7.0
- Amazon Linux AMI 2017.03.0 (HVM)
手順
-
- EC2にRails環境を構築
-
- Nginxのインストールと起動
-
- Nginxの設定を修正
-
- デプロイ設定を修正(ローカル)
-
- アプリケーションログを確認
-
- データベースを設定
-
- アプリケーションサーバを起動
-
- デプロイタスクを修正(ローカル)
1. EC2にRails環境を構築
ここまで手順通りに進めている場合にはEC2環境にはまだRailsアプリケーションを動かす環境が整っていないので順番にいれていく。
$ pwd
/home/deploy
- yum
$ sudo yum update
$ sudo yum install git
$ sudo yum install gcc
$ sudo yum install gcc-c++
$ sudo yum install openssl-devel
$ sudo yum install readline-devel
$ sudo yum install mysql-devel
- ruby-build
$ git clone git://github.com/sstephenson/ruby-build.git
$ sudo ruby-build/install.sh
- 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 ~/.bash_profile
$ exec $SHELL -l
$ rbenv --version
rbenv 1.1.1
- rbenv-vars
$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
- Ruby 2.4.0
$ rbenv install 2.4.0
$ rbenv global 2.4.0
$ rbenv rehash
$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]
- bundler
$ gem install bundler
- rubyracer
$ gem install therubyracer
- Rails 5.0.0
$ gem install rails -v 5.0.0
$ rails -v
Rails 5.0.0
- Puma
$ gem install puma
- MySQL
$ sudo yum install mysql-devel
$ sudo yum install mysql-server
$ sudo /etc/init.d/mysqld start
$ sudo chkconfig mysqld on
2018-09-02 updated:
mysql-serverをyumでインストールできない場合はこちらを参考に。
AWS Amazon Linux2にMySQL5.7を構築する
2. Nginxのインストールと起動
まずは本番環境にNginxをインストールする。以下のコマンドで正しく起動できない場合は /var/log/nginx/error.log
にエラーログが出力されているはずなので確認する。ブラウザからアクセスしてNginxのwelcome画面が表示されていれば正しく起動できている。
$ sudo yum -y install nginx
$ sudo /etc/init.d/nginx start
設定ファイルはデフォルトでこちら /etc/nginx/nginx.conf
が読まれる。初期設定で生成されているファイルは念のためコピーしておくことにする。
$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.cpy
3. Nginxの設定を修正
Nginxの設定ファイルを修正する。と言っても、デフォルトで生成されたものを何箇所か変更した程度。以下にサンプルを載せておくが自身の環境に応じてよしなに修正する。
$ sudo vi /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
index index.html index.htm;
upstream puma {
server unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name localhost;
root /var/www/my-app-name/current/public;
include /etc/nginx/default.d/*.conf;
location / {
try_files $uri $uri/index.html $uri.html @webapp;
}
location @webapp {
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://puma;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
Nginxの設定ファイルの例はこちらに詳しく書かれている。
Nginx configuration example file
4. デプロイ設定を修正(ローカル)
デプロイ対象の本番環境には /var/www/my-app-name/shared/
配下に以下のディレクトリが並ぶようにする。development環境ではアプリケーションのルート直下にこれらのファイルが存在したが、Capistranoでデプロイした本番環境ではこれらは /shared
に配置する。デフォルトではデプロイしない設定になっているため修正する。
- log
- public
- tmp
ローカルに戻り、以下の設定を追記し再度デプロイを行う。上記のディレクトリ群が /shared
配下に生成されていることを確認する。
app_dir = File.expand_path("../..", __FILE__)
bind "unix://#{app_dir}/tmp/sockets/puma.sock"
pidfile "#{app_dir}/tmp/pids/puma.pid"
state_path "#{app_dir}/tmp/pids/puma.state"
stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
$ bundle exec cap production deploy
5. アプリケーションログを確認
本番環境に戻りPumaを再起動し、socketファイルやpidファイルが生成されることを確認する。
$ ssh deploy@xx.xxx.xxx.xxx
$ cd /var/www/my-app-name/current
$ bundle exec pumactl start
ブラウザからアクセスし shared/log/
配下にRailsのアプリケーションログやPumaのログが落ちることを確認する。
$ tail -f shared/log/production.log
$ tail -f shared/log/puma.stderr.log
$ tail -f shared/log/puma.stdout.log
6. データベースを設定
MySQLにrootでログインし新規ユーザを作成する。作成するユーザ名は database.yaml
で設定した名前と揃える。
$ mysql -u root
mysql> GRANT ALL PRIVILEGES ON `my-app-name_production`.* TO `my-app-name`@localhost IDENTIFIED BY 'my-app-password';
初期設定では文字コードがlatin1になっているのでutf8に変更していく。character_set_filesystem
と character_sets_dir
以外をすべて utf8
に設定する。
mysql> show variables like "chara%";
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
設定ファイルに以下の2行を追記して再起動する。
[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8
$ sudo /etc/init.d/mysqld restart
文字コードが正しくutf8に設定されていることを確認する。問題なければ作成したユーザで rails db:create
および rails db:migrate
をおこなっておく。
$ bundle exec rails db:create RAILS_ENV=production
$ bundle exec rails db:migrate RAILS_ENV=production
7. アプリケーションサーバを起動
Pumaをproductionモードで起動し表示確認をおこなう。pumaコマンドで起動する場合は -C
オプションで設定ファイルを指定しないと読みにいかないので気をつける。また、今回はNginx側の設定でsocketファイル読みにいくように設定してるため -p
オプションでポートを指定してしまうとsocketファイルがlistenされなくなるため気をつける。
$ bundle exec puma -t 5:5 -e production -C config/puma.rb
# もしくは
$ bundle exec pumactl start
8. デプロイタスクを修正(ローカル)
デプロイコマンド実行時に関連するタスクを実行するように以下をデプロイタスクに追加する。
- デプロイ時にbundlerを起動
group :development do
gem 'capistrano-bundler'
end
require "capistrano/bundler"
- デプロイ時にmigrationを実行
require "capistrano/rails/migrations"
- デプロイ時にPumaを再起動
group :development do
gem 'capistrano3-puma'
end
require 'capistrano/puma'
install_plugin Capistrano::Puma
capistrano3-pumaを利用するための設定ファイル追加する。 puma:config
コマンドでローカルで自動生成した設定ファイルをリモートサーバにアップロードできる。
$ bundle exec cap production puma:config
設定ファイルはshared配下に配置され、下記のようなコマンドでローカルからPumaの起動、停止、再起動ができるようになる。
$ bundle exec cap production puma:start
$ bundle exec cap production puma:stop
$ bundle exec cap production puma:restart
$ bundle exec cap production puma:phased-restart
デプロイタスクにPumaの再起動タスクを組み込む。
namespace :deploy do
desc "Make sure local git is in sync with remote."
task :confirm do
on roles(:app) do
puts "This stage is '#{fetch(:stage)}'. Deploying branch is '#{fetch(:branch)}'."
puts 'Are you sure? [y/n]'
ask :answer, 'n'
if fetch(:answer) != 'y'
puts 'deploy stopped'
exit
end
end
end
desc "Initial Deploy"
task :initial do
on roles(:app) do
before 'deploy:restart', 'puma:start'
invoke 'deploy'
end
end
desc "Restart Application"
task :restart do
on roles(:app), in: :sequence, wait: 5 do
invoke 'puma:restart'
end
end
before :starting, :confirm
end
デプロイ実行時にPumaが正しく再起動されていることを確認する。
$ bundle exec cap production deploy
おまけ
エラーログをちゃんと読めばだいたいすぐに解決すると思うが、今回も念のため想定されるエラーと解決方法を載せておく。
1. ディレクトリにアクセスする権限がない
directory index of "/var/www/my-app-name/current/public/" is forbidden, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", host: "xx.xxx.xxx.xxx"
→ locationディレクティブの中にで try_files
の記述が足りない。
location / {
try_files $uri $uri/index.html $uri.html @webapp;
}
location @webapp {
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://puma;
}
2. socketファイルが正しく読めない
connect() to unix:///my-app-name/path/to/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///my-app-name/path/to/sockets/puma.sock:/", host: "xx.xxx.xxx.xxx"
→ socketファイルが正しく生成されていない。もしくは生成されているが名前やパスが正しくない。
app_dir = File.expand_path("../..", __FILE__)
bind "unix://#{app_dir}/tmp/sockets/puma.sock"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
upstream puma {
server unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock;
}
location @webapp {
proxy_pass http://puma;
}
3. socketファイルで通信ができない
connect() to unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock failed (111: Connection refused) while connecting to upstream, client: yyy.yyy.yy.yyy, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///var/www/my-app-name/shared/tmp/sockets/puma.sock:/", host: "xx.xxx.xxx.xxx"
→ Nginxがsocketファイルで通信できなかったよと言っている。以下のようなコマンドでPumaを起動する際 -p
オプションでポートを指定すると、デフォルトでsocketファイルをlistenしない仕様になっている。今回のようにsocketファイルで通信をおこないたい場合にはポートの指定はしない。
# NG ->
$ bundle exec puma -t 5:5 -p 3000 -e production -C config/puma.rb
Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
portの指定を外すと正しくsocketファイルをlistenしてくれる。
# OK ->
$ bundle exec puma -t 5:5 -e production -C config/puma.rb
Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
* Listening on unix:///var/www/my-app-name/releases/20170620162839/tmp/sockets/puma.sock
Use Ctrl-C to stop
# OK ->
$ bundle exec pumactl start
Puma starting in single mode...
* Version 3.9.1 (ruby 2.4.0-p0), codename: Private Caller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
* Listening on unix:///var/www/my-app-name/releases/20170620162839/tmp/sockets/puma.sock
Use Ctrl-C to stop
4. MySQLのsocketファイルが読めない
#<Mysql2::Error: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)>
→ MySQLのsocketファイルが見つからないよと言っている。以下のコマンドでsocketファイルのパスを確認し database.yaml
のproduction設定に追加する。
$ mysqladmin -u root version
production:
<<: *default
socket: /var/lib/mysql/mysql.sock
もしくは、MySQLが正しく起動できているにも関わらずこのエラーが出る際にはPumaがdevelopmentモードで起動されており database.yaml
のdevelopmentの設定を読みにいっている可能性があるため、正しくproductionモードで起動できているか確認する。
5. secret_key_baseが設定されていない
#<RuntimeError: Missing `secret_key_base` for 'production' environment, set this value in `config/secrets.yml`>
→ 環境変数が正しく読めていない。アプリケーションのルート直下で $ rbenv vars
コマンドを実行し が正しく動作しているか確認する。 rbenv: no such command 'vars'
とおこられるときは正しくインストールできていない。以下のコマンドでrbenv-varsがインストールされ /var/deploy/.rbenv/plugins
配下に rbenv-vars
ディレクトリが生成されることを確認する。
$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
$ rbenv vars
# /var/www/my-app-name/current/.rbenv-vars
export DATABASE_PASSWORD=''
export SECRET_KEY_BASE=''