【AWS】EC2でのデプロイ（EC2のRailsを起動）

# 目的
`AWS`のサーバーを利用し、Railsで作成したアプリを公開する。

# 開発環境
macOS: *Big Sur*
Rubyバージョン: *2.6.5*
Railsバージョン: *6.0.0*

# 前提

- `データベース`の設定が完了している。[【AWS】EC2でのデプロイ（データベースの設定）](https://qiita.com/oak1331/items/4bba171c74be654e0feb)

# 手順

1. [はじめに](#はじめに)
1. [EC2サーバーssh鍵ペアの作成](#ec2サーバーssh鍵ペアの作成)
1. [GitHubにssh鍵を登録](#githubにssh鍵を登録)
1. [Unicornの設定](#unicornの設定)
1. [本番環境の設定](#本番環境の設定)
1. [EC2内でgemをインストール](#ec2内でgemをインストール)
1. [環境変数の設定](#環境変数の設定)
1. [ポートの解放](#ポートの解放)
1. [Railsの起動](#railsの起動)
1. [アセットファイルのコンパイル](#アセットファイルのコンパイル)
1. [Railsの再起動](#railsの再起動)
1. [ターミナルで「unicorn_rails」が起動しない時の対処法](#ターミナルでunicorn_railsが起動しない時の対処法)
1. [ブラウザに「We’re sorry, but 〜」と表示されている時の対処法](#ブラウザにwere-sorry-but-と表示されている時の対処法)

# はじめに

今回は本番環境でRailsを起動していきます！

# EC2サーバーssh鍵ペアの作成

それでは早速始めていきます！
まず、デプロイできるIPアドレスを持ったEC2サーバー上でアプリを動かすために、アプリのコードをGitHubからEC2サーバへクローンします。

EC2インスタンスからGitHubにアクセスするためには、作成したEC2インスタンスのssh公開鍵をGitHubに登録する必要があります。
ssh鍵をGitHubに登録すると、GitHubはそれを認証に利用し、コードのクローンを許可してくれるようになります。

以下のコマンド入力して、EC2サーバのssh鍵ペアを作成します。
途中で「passphrase」など3段階ほど入力を求められることがありますが、すべて何も入力せずにEnterキーを押して進んでください。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ ssh-keygen -t rsa -b 4096
```

これで作成されました！

#  GitHubにssh鍵を登録

続いて、`catコマンド`で、公開鍵が含まれているファイル`id_rsa.pub`の中身をターミナル上に表示します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ cat ~/.ssh/id_rsa.pub
```

そして、表示された公開鍵の情報をすべて（「ssh-rsa」から「末尾の文字」まで）コピーします。

次に、コピーした公開鍵をGitHubに登録します。以下のリンクにアクセスしてください。
[SSH鍵登録ページ](https://github.com/settings/keys)

画面遷移後、右上にある「New SSH key」をクリックし、公開鍵のタイトルを設定してコピーした公開鍵を貼り付けます。
入力ができたら、「Add SSH key」をクリックして公開鍵を保存します。

GitHubに鍵を登録できたら、ssh接続できるか以下のコマンドで確認してみます！

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ ssh -T git@github.com
```

# Unicornの設定

次に、`アプリケーションサーバー`の導入と設定を行います。
全世界に公開するEC2サーバー上でアプリケーションサーバを動かすためには`Unicorn`というツールが必要です。

`Unicorn`とは、全世界に公開されるサーバ上で良く利用されるアプリケーションサーバーです。rails sコマンドの代わりに`unicorn_railsコマンド`で起動することができます。

それではまず`Unicorn`のインストールを行います！
Gemfileのgroup :production do〜end内に内容を追記します。

```ruby:Gemfile
group :production do
  gem 'unicorn', '5.4.1'
end
```

```:ターミナル（ローカル）
% bundle install
```

続いて`「config/unicorn.rb」`を作成し、内容を以下のように編集して保存します！

```ruby:config/unicorn.rb
#サーバ上でのアプリケーションコードが設置されているディレクトリを変数に入れておく
app_path = File.expand_path('../../', __FILE__)

#アプリケーションサーバの性能を決定する
worker_processes 1

#アプリケーションの設置されているディレクトリを指定
working_directory app_path

#Unicornの起動に必要なファイルの設置場所を指定
pid "#{app_path}/tmp/pids/unicorn.pid"

#ポート番号を指定
listen 3000

#エラーのログを記録するファイルを指定
stderr_path "#{app_path}/log/unicorn.stderr.log"

#通常のログを記録するファイルを指定
stdout_path "#{app_path}/log/unicorn.stdout.log"

#Railsアプリケーションの応答を待つ上限時間を設定
timeout 60

#以下は応用的な設定なので説明は割愛

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end
```

`worker`とは、分裂したUnicornの全プロセスのことです。プロセスを分裂させることで、リクエストに対してのレスポンスを高速にできます。

ブラウザなどからリクエストが来ると、`Unicorn`の`worker`がRailsアプリを動かします。
Railsは、リクエストの内容とルーティングを照らし合わせ最終的に適切なビュー(HTML)もしくはJSONをレスポンスします。
レスポンスを受け取った`Unicorn`は、それをブラウザに返します。一連の動きはおよそ0.1 ~ 0.5秒程度で行われます。
常にそれ以上のスピードでリクエストが頻発するようなアプリケーションだと、1つの`worker`だけでは処理が追いつかず、レスポンスまで長い時間がかかってしまったり最悪サーバがストップしてしまいます。
そんな時、`worker_processes`の数を 2,3,4と増やすことでアプリケーションからのレスポンスを早くできます。

|  設定項目            |  詳細                                                                         |
| ------------------- | ---------------------------------------------------------------------------- |
|  worker_processes   |  リクエストを受け付けレスポンスを生成するworker(ワーカー)の数                             |
|  working_directory  |  UnicornがRailsのコードを動かす際、ルーティングなど実際に参照するファイルを探すディレクトリを指定  |
|  pid                |  Unicornは、起動する際にプロセスidが書かれたファイルを生成します。その場所を指定              |
|  listen             |  どのポート番号のリクエストを受け付けることにするかを決定                                   |

編集できたら、リモートリポジトリに「commit→push」します！
その後下記コマンドを実行し、権限を付与します。

mkdirコマンドで新たにディレクトリを作成

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ sudo mkdir /var/www/
```

作成したwwwディレクトリの権限をec2-userに変更

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ sudo chown ec2-user /var/www/
```

次にGitHubから「リポジトリURL」を取得し、クローンします。
デプロイするアプリケーションのGitHubを開き、緑色で「Code」と表示されているボタンをクリックして、URLをコピーします。

そして、下記コマンドでコードをクローンします。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ cd /var/www/
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 www]$ git clone コピーしたURLを貼り付ける
```

これでクローンできました！

# 本番環境の設定

ここまでで、アプリケーションのコードをEC2にクローンすることができました。続いて、サービスを公開するための設定を行なっていきます！

現状動かしているEC2のインスタンスではコンピューターの能力が足りず、Gemのインストール時などにエラーが発生する可能性があります。具体的には、コンピューターの処理能力に関係するメモリが足りません。これは、無料で動かせるインスタンスの限界であるため仕方ありません。
そこで、今後の設定を行う前に`Swapファイル`というメモリを増強する処理を行います。

`Swapファイル`とは、メモリの容量を一時的に増やすために準備されるファイルのことです。コンピューターが処理を行う際、メモリと呼ばれる場所に処理内容が一時的に記録されます。メモリは容量が決まっており、容量を超えてしまうとエラーで処理が止まってしまいます。
EC2はデフォルトではSwapファイルを用意していないため、これを準備することでメモリ不足の処理エラーを防ぎます。

まずは、EC2にssh接続をしてホームディレクトリに移動します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ cd
```

続いて、以下のコマンドを順に実行します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ sudo chmod 600 /swapfile1
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ sudo mkswap /swapfile1
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ sudo swapon /swapfile1
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-25-189 ~]$ sudo sh -c 'echo "/swapfile1  none        swap    sw              0   0" >> /etc/fstab'
```

これで、Swapファイルの領域を確保することができました!

# EC2内でgemをインストール

EC2にクローンしたアプリケーションを起動するために必要なgemをインストールしていきます！

クローンしたディレクトリに移動

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 www]$ cd  /var/www/開発中のアプリケーション
```

rubyのバージョンを確認

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ruby -v
```

次に、本番環境でgemを管理するためのbundlerをインストールします。

まずは、ローカルで開発してきたアプリケーションでどのバージョンのbundlerが使われていたのか確認します。

```:ターミナル（ローカル）
% bundler -v
```

ローカルと同じバージョンのbundlerをEC2側にも導入します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ gem install bundler -v 2.1.4
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ bundle install
```

# 環境変数の設定

次に環境変数の設定です。
Cookieの暗号化に用いられる文字列の`secret_key_base`も環境変数に設定していきます。

まず以下のコマンドでsecret_key_baseを作成します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rake secret
```

表示された英数の羅列はすべてメモアプリなどに控えておきます。
続いて環境変数の設定です。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ sudo vim /etc/environment
```

コマンド入力後、`「i」`を入力し、入力モードに切り替えます。
その後下記の記述を打ち込みます。

```ruby:/etc/environment
#データベースのrootユーザーのパスワードを入力
DATABASE_PASSWORD='データベースのrootユーザーのパスワード'
SECRET_KEY_BASE='さきほど作成したsecret_key_base'

# 「S3」でダウンロードしたCSVファイルを参考に値を入力
AWS_ACCESS_KEY_ID='ここにCSVファイルのAccess key IDの値をコピー'
AWS_SECRET_ACCESS_KEY='ここにCSVファイルのSecret access keyの値をコピー'
```

設定した環境変数を反映させるために、一度本番環境をログアウトします。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ exit
```

次に、いま設定した環境変数がしっかり反映されているか確認します。

```:ターミナル（ローカル）
$ ssh -i [ダウンロードした鍵の名前].pem ec2-user@[作成したEC2インスタンスと紐付けたElastic IP]
```

ログイン後、下記コマンドで確認します。

```ruby:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ env | grep SECRET_KEY_BASE
SECRET_KEY_BASE='secret_key_base'

[ec2-user@ip-172-31-23-189 ~]$ env | grep DATABASE_PASSWORD
DATABASE_PASSWORD='データベースのrootユーザーのパスワード'

[ec2-user@ip-172-31-23-189 ~]$ env | grep AWS_SECRET_ACCESS_KEY
AWS_SECRET_ACCESS_KEY='Secret access key'

[ec2-user@ip-172-31-23-189 ~]$ env | grep AWS_ACCESS_KEY_ID
AWS_ACCESS_KEY_ID='Access key ID'
```

# ポートの解放

立ち上げたばかりのEC2インスタンスはsshでアクセスすることはできますが、HTTPなどの他の通信方法では一切つながらないようになっています。
そのため、サーバーとして利用するEC2インスタンスは事前にHTTPがつながるように「ポート」を開放する必要があります。

まず、EC2インスタンス一覧画面から、対象のインスタンスを選択し、「セキュリティ」のタブを開きます。次に、「セキュリティグループ」のリンクをクリックします。
インスタンスの属するセキュリティグループの設定画面に移動するので、「インバウンド」タブの中の「編集」をクリックします。
ページが切り替わるので、「インバウンドルールを編集」をクリックし、`ポート範囲「3000」`を追加します。
設定後、「ルールを保存」をクリックします。

これでポートの開放は以上です。

# Railsの起動

ここから、いよいよ本番環境でRailsを起動します。
本番環境でRailsを起動するには`unicorn_railsコマンド`を使います。

まず、VSCodeで`database.yml`の本番環境の設定を編集します。
本番環境のmysqlの設定に合わせるため、ローカルの`database.yml`を以下のように編集してください。

```ruby:config/database.yml（ローカル）
production:
  <<: *default
  database:（※こちらは編集しないでください）
  username: root
  password: <%= ENV['DATABASE_PASSWORD'] %>
  socket: /var/lib/mysql/mysql.sock
```

次に、編集を「commit→push」しましょう。
リモートリポジトリが更新されたため、サーバ上のアプリケーションにも反映させます。
次は、GitHubの内容をEC2に反映させる作業です。
以下のコマンドを実行してください。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>] git pull origin master
```

次は、EC2内でデータベースを作成するのですが、`RAILS_ENV=production`というオプションがつきます。
`RAILS_ENV=production`とは、本番環境でコマンド実行する時につくオプションです。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:create RAILS_ENV=production
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:migrate RAILS_ENV=production
```

ここまでできたら、Railsを起動します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 ~]$ cd /var/www/[リポジトリ]
```

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ bundle exec unicorn_rails -c config/unicorn.rb -E production -D
```

# アセットファイルのコンパイル

「アセットファイル」とは、画像・CSS・JavaScript等を管理しているファイルです。このアセットファイルを圧縮し、そのデータを転送する処理を「コンパイル」と言います。この作業を行わないと、本番環境でCSSが反映されずにビューが崩れてしまったり、エラーでブラウザが表示されない、などの問題が生じてしまいます。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails assets:precompile RAILS_ENV=production
```

ここまで終えたら再度Railsを起動させたいのですが、すでにサーバーは立ち上がっています。
そこで、Railsを再起動します！

# Railsの再起動

まず、`Unicornのプロセス`を確認します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn
```

すると、以下のようにプロセスが表示されるはずです。

```:ターミナル
ec2-user 17877  0.4 18.1 588472 182840 ?       Sl   01:55   0:02 unicorn_rails master -c config/unicorn.rb -E production -D
ec2-user 17881  0.0 17.3 589088 175164 ?       Sl   01:55   0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn
```

大事なのは左から2番目の列です。ここに表示されるのがプロセスのid（PIDと言う）になります。
「unicorn_rails master」と表示されているプロセスがUnicornのプロセス本体です。この時のプロセスidは「17877」となっています。

それでは、以下のコマンドを実行してUnicornのプロセスをKillしましょう。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのプロセスid>
```

実行したプロセスを再度表示させ、終了できていることを確認しましょう。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn
```

最後に、Railsを再起動するコマンドを実行しましょう。
今回はコマンドの先頭に`RAILS_SERVE_STATIC_FILES=1`というオプションをつけます。
`RAILS_SERVE_STATIC_FILES=1`は、Railsがコンパイルされたアセットを見つけられるように指定する役割があります。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
```

ブラウザで http://＜Elastic IP＞:3000/ にアクセスして、サイトが正常に表示されているか確認してみましょう!

# ターミナルで「unicorn_rails」が起動しない時の対処法

「Railsが起動しない」「ブラウザで確認するとエラーが表示されている」などの問題がある場合、まずは「エラーログ」を確認する必要があります。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
master failed to start, check stderr log for details
```

このように、unicorn_railsを実行した際に`master failed to start, check stderr log for details`と出た場合、unicornのエラーログを確認する必要があります。

それでunicornのエラーログを確認してみます！

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ less log/unicorn.stderr.log
```

ログファイルは「一番下から最新のログ」が表示されます。`「shiftキー」＋「G」`を実行すると、一番下まで一瞬で移動できます。

# ブラウザに「We’re sorry, but 〜」と表示されている時の対処法

このような表示が出ている場合、まずは`production.log`を確認する必要があります。
`production.log`とは、サーバーログを記録する場所で、EC2内での出来事を記録している場所です。

以下のコマンドを実行します。

```:ターミナル（EC2内で実行）
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ less log/production.log
```

 ログファイルは「一番下から最新のログ」が表示されます。`「shiftキー」＋「G」`を実行すると、一番下まで一瞬で移動できます。


# 最後に

以上で、EC2のRails起動は完了です。
次回はWebサーバーの設定を行っていきます。[【AWS】EC2でのデプロイ（Webサーバーの設定）](https://qiita.com/oak1331/items/501cc3cea4411abfd26d#502-but-gateway%E3%81%AE%E5%AF%BE%E5%87%A6%E6%B3%95)
では。