Rails 6アプリをAWSへのデプロイする手順

# この記事での注意
この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

# サーバーにインストールする環境
- Amazon Linux 2
- Ruby 2.7.2 (rbenv)
- Rails 6.1
- MySQL or PostgreSQL
- Nginx + Puma 5
- Node.js 12
- Yarn

# 本番サーバーの構築
本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

##1. システムライブラリアップデート
まずはAmazon Linux 2のシステムライブラリを更新します。

```bash:サーバー
$ sudo yum update -y
```

##2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

```bash:サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8
```

##3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

```bash:サーバー
$ yum info chrony
$ chronyc sources -v
```

##4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

```bash:サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git
```
##5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

- PostgreSQLの場合

```bash:サーバー
$ sudo yum -y install postgresql-devel
```

- MySQLの場合

```bash:サーバー
$ sudo yum -y install mysql-devel
```

##6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

###6.1 rbenvのインストール

```bash:サーバー
$ 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 -v
```

###6.2 ruby-buildのインストール

```bash:サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l
```

###6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

```bash:サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2
```
rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

##7. Rails 6で必要なモジュールのインストール

###7.1 Node.jsのインストール

Rails 6からは <a href="https://nodejs.org/ja/">Node.js</a> や <a href="https://yarnpkg.com/">Yarn</a> が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

```bash:サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node
```

###7.2 Yarnのインストール
```bash:サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y
```

##8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

```bash:サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除）

$ bundle install
$ bundle exec rails -v
（インストールされたことを確認）

$ cd ..
$ rm -rf rails
```

# データベースの設定
##９. 本番データベースの作成
ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
1. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

***

##10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は **config/credentials.yml.enc** にセットします。（Rails5.2以降で可能）

このファイルはエディタで表示すると暗号化された文字列になっていますが、 **master.key** を使うことで閲覧、編集することができます。この **credentials.yml.enc** と **master.key** の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

```bash:ローカル
$ EDITOR=vim bin/rails credentials:edit
```

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。


```yaml:credential.yml.enc（ローカル）
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)
```

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

```yaml:database.yml（ローカル）
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432
```

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は **5432**、MySQLの場合は **3306** になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

```bash:ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master
```

#デプロイ設定
デプロイツール<a href="https://github.com/capistrano/capistrano">Capistrano</a>を使ったデプロイの設定を行います。

##11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

```ruby:Gemfile（ローカル）
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
```

```bash:ローカル
$ bundle install
$ bundle exec cap install
```

##12. Capfileの設定

下記のようにCapfileに追記します。

```ruby:Capfile（ローカル）
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :
```

##13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

```ruby:config/deploy.rb（ローカル）
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5
```
linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の **(server ip)** にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

```ruby:config/deploy/production.rb（ローカル）
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}
```

区切りが良いので、ここでいったんコミット、プッシュしましょう。（プッシュは任意です）

```bash:ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master
```

##14. Githubへのキー登録
デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

https://qiita.com/yukeippi/items/c6fa27376ce787502f78

##15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置（デプロイ）を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

```bash:サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails
```

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

```bash:ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)
```
エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

```bash:サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
（ローカルにあるmaster.keyを貼り付け）
```

これで問題は解決しましたので、再度、デプロイチェックを行います。

```bash:ローカル
$ bundle exec cap production deploy:check
```
成功するはずです。これでデプロイ先ディレクトリができました。

##16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

```bash:ローカル
$ bundle exec cap production deploy
```
最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

```bash:サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production
```

（最新リリース）は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

```bash:ローカル
$ bundle exec cap production deploy
```
これで成功するはずです。

# WebサーバーとRailsアプリの連携
##17. Pumaの設定

本番環境のPumaの設定を行います。

```ruby:config/puma/production.rb（ローカル）
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"
```

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

```bash:ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master
```

##18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

```bash:サーバー
$ sudo amazon-linux-extras install nginx1 -y
```
##19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

```bash:サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
```

```conf:/etc/nginx/conf.d/sample611.conf（サーバー）
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}
```
upstream puma内にあるpuma.sockのパスに気をつけてください。これは１５のbindで設定したパスにあわせなければなりません。

##20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

```bash:サーバー
$ sudo vim /etc/systemd/system/puma.service
```

```conf:/etc/systemd/system/puma.service（サーバー）
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target
```
ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

```bash:サーバー
$ sudo chmod +x /etc/systemd/system/puma.service
```
これでサーバー側の設定は終わりです。

##21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

```bash:サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma
```

下記のコマンドで起動を確認します。

```bash:サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]
```
上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

```bash:サーバー
$ sudo systemctl start nginx.service
```
これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://（サイトのIP）/

##21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

```ruby:config/deploy.rb（ローカル）
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end
```
これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

```bash:ローカル
$ git add .
$ git commit -m "Setup restart puma"
```

再起動することを確認するため、デプロイします。

```bash:ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s
```
デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です！お疲れ様でした。

# puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

```bash:サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service
```

以上です。