Edited at

Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(後編)

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)


手順


  • 1. EC2にRails環境を構築

  • 2. Nginxのインストールと起動

  • 3. Nginxの設定を修正

  • 4. デプロイ設定を修正(ローカル)

  • 5. アプリケーションログを確認

  • 6. データベースを設定

  • 7. アプリケーションサーバを起動

  • 8. デプロイタスクを修正(ローカル)


1. EC2にRails環境を構築

ここまで手順通りに進めている場合にはEC2環境にはまだRailsアプリケーションを動かす環境が整っていないので順番にいれていく。

$ pwd

/home/deploy

1) 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

2) ruby-build

$ git clone git://github.com/sstephenson/ruby-build.git

$ sudo ruby-build/install.sh

3) 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

4) rbenv-vars

$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars

5) 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]

6) bundler

$ gem install bundler

7) rubyracer

$ gem install therubyracer

8) Rails 5.0.0

$ gem install rails -v 5.0.0

$ rails -v
Rails 5.0.0

9) Puma

$ gem install puma

10) 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


/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 配下に生成されていることを確認する。


config/puma.rb

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


deploy.rb

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_filesystemcharacter_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行を追記して再起動する。


/etc/my.cnf

[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. デプロイタスクを修正(ローカル)

デプロイコマンド実行時に関連するタスクを実行するように以下をデプロイタスクに追加する。

1) デプロイ時にbundlerを起動


Gemfile

group :development do

gem 'capistrano-bundler'
end


Capfile

require "capistrano/bundler"


2) デプロイ時にmigrationを実行


Capfile

require "capistrano/rails/migrations"


3) デプロイ時にPumaを再起動


Gemfile

group :development do

gem 'capistrano3-puma'
end


Capfile

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の再起動タスクを組み込む。


config/deploy.rb

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 の記述が足りない。


/etc/nginx/nginx.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;
}


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ファイルが正しく生成されていない。もしくは生成されているが名前やパスが正しくない。


/var/www/my-app-name/current/config/puma.rb

app_dir = File.expand_path("../..", __FILE__)

bind "unix://#{app_dir}/tmp/sockets/puma.sock"


/var/www/my-app-name/current/config/deploy.rb

append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"



/etc/nginx/nginx.conf

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


config/database.yaml

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=''


参考