AWSを使ってアプリケーションを公開する手順(5)アプリケーションを公開する

#はじめに
AWSを使ってアプリケーションを公開する手順を記載していく。
この記事ではアプリケーションを公開する。

#EC2のサーバにアプリケーションのコードをクローンする準備

##「.ssh」ディレクトリに移動する
以下のコマンドを実行し、「.ssh」ディレクトリに移動する。

```
cd ~/.ssh/
```

##ssh接続
以下のコマンドを実行し、EC2インスタンスにsshでアクセスする。
(ダウンロードしたpemファイル名が「xxx.pem」、ElasticIPが12.345.67.890の場合)

```
ssh -i xxx.pem ec2-user@12.345.67.890
```

##GithubにSSH鍵を登録する
EC2インスタンスからGithubにアクセスするために、EC2インスタンスのSSH公開鍵をGithubに登録する必要がある。
SSH鍵を登録しないとpermission errorとなりクローンできない。SSH鍵をGithubに登録することで認証されるようになりコードのクローンが可能になる。

以下のコマンドを実行し、EC2サーバのSSH鍵ペアを作成する。

```
ssh-keygen -t rsa -b 4096
```

以下のコマンドを実行し生成されたSSH公開鍵を表示し、値をコピーする。

```
cat ~/.ssh/id_rsa.pub
```

以下のURLにアクセスすると画像のような画面に遷移する。
https://github.com/settings/keys

![SSH鍵登録スクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654714/d64db4ff-cb0f-32ad-87c3-fb699f06728a.png)

「Title」欄に任意のタイトルを記入、「Key」欄に先ほどコピーしたSSH公開鍵をペーストし、「Add SSH key」をクリックする。

以下のコマンドを実行しSSH接続できるか確認する。

```
ssh -T git@github.com
```

Permission denied (publickey).と表示された場合にはSSH鍵の設定が間違っている。

###「ssh-keygen」コマンドのオプションについて
「ssh-keygen」コマンドは公開鍵認証方式の秘密鍵と公開鍵を作成するコマンドである。
「-t」は作成する鍵の暗号化形式を「rsa」「dsa」「ecdsa」「ed25519」から指定するオプションである。
「-b」は作成する鍵のビット数を指定するコマンドである。
つまり上記のコマンドはRSA暗号化形式で4096ビットの鍵を生成するという意味である。

#アプリケーションサーバの設定
アプリケーションサーバとはブラウザからリクエストを受けてアプリケーションを動作させるソフトウェアである。

例えばローカル環境でRuby on Railsのアプリケーションの動作を確認する時、「rails s」というコマンドを入力する。これにより「puma」というアプリケーションサーバが起動する。
この状態でブラウザから「localhost:3000」にアクセスすることでrailsアプリケーションの動作確認を行うことができる。(localhost:3000は自身のPCを指す)

同様に、EC2サーバ上でアプリケーションを動作させるためには、EC2サーバ上でアプリケーションサーバを起動する必要がある。

##Unicornをインストールする
よく利用されるアプリケーションサーバの一つに、Unicornがある。Unicornは「rails s」コマンドの代わりに「unicorn_rails」コマンドで起動できる。

UnicornはRubyで作成されており、gem化されている。
Gemfileに以下の記述を追記する。
Unicornは本番環境のみで使用するので開発環境では不要。

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

アプリケーションのディレクトリで以下のコマンドを実行する。

```
bundle install
```

configディレクトリ直下に下記のようなUnicornの設定ファイルを作成する。

```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
```

###プロセスとは
プロセスとはPC(サーバ)上で動く全てのプログラムの実行時の単位のことを指す。ここでいうプログラムとはブラウザや音楽再生ソフト、ExcelなどのGUIやRubyなどのスクリプト言語の実行などが含まれる。プログラムが動いている数だけプロセスが存在する。

###workerとは
Unicornはプロセスを分裂させることができる。分裂したプロセスをworkerと呼ぶ。プロセスを分裂させることでリクエストに対するレスポンスを高速にすることができる。worker_processesという項目でworkerの数を設定する。
ブラウザなどからリクエストが来るとUnicornのworkerがrailsアプリケーションを動かす。railsはリクエストの内容とルーティングを照らし合わせ最終的に適切なViewまたはjsonをレスポンスする。レスポンスを受け取ったUnicornはそれをブラウザに返す。この一連の流れは0.1〜0.5秒程度で行われる。常にそれ以上のスピードでリクエストが頻発するようなアプリケーションだと一つのworkerだけでは処理が追いつかずレスポンスまで長い時間がかかってしまったり、サーバが止まってしまうことがある。worker_processesの数を増やすことでアプリケーションのレスポンスを早くすることができる。

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

##Uglifierについての記述をコメントアウトする
UglifierというJavascriptを軽量化するためのgemがある。
Javascriptでテンプレートリテラル記法(`)を利用している場合、Uglifierはこれに対応していないのでデプロイ時にエラーの原因となるためコメントアウトする。

config/environments/production.rbの以下の記述をコメントアウトする。

```ruby:config/environments/production.rb
# config.assets.js_compressor = :uglifier
```

##変更修正をリモートリポジトリに反映する
ファイルをコミットし、Githubにプッシュする。
ブランチを切っている場合にはmasterブランチにマージする。

#Githubからコードをクローンする
Unicornの設定を済ませたコードをEC2インスタンスにクローンする。

##/var/wwwディレクトリを作成する
EC2インスタンスにSSH接続した後、以下のコマンドを実行し、/var/wwwディレクトリを作成する。

```
sudo mkdir /var/www/
```

##権限をec2-userに変更する
以下のコマンドを実行し、作成したwwwディレクトリの権限をec2-userに変更する。

```
sudo chown ec2-user /var/www/
```

##GithubからリポジトリURLを取得する
Githubのアプリケーションのページに移動し、以下の画像を参考にリポジトリURLをコピーする。

![リポジトリURLスクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654714/3a7a316d-b9f2-b96f-c491-c6185bebe62a.png)

##コードをクローンする
以下のコマンドを実行してコードをクローンする。
(以下の例はGithubのuser名が「test1234」、リポジトリ名が「testapp」の場合)

```
cd /var/www/
```

```
git clone https://github.com/test1234/testapp.git(先ほどコピーしたリポジトリURL)
```

#本番環境の設定
サービスを公開するための設定を行っていく。

##EC2の能力を拡張する
現状のEC2インスタンスはメモリが足りず、gemのインストール時などにエラーが発生する可能性がある。
そこでまずはメモリを増強する。

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

EC2インスタンスにSSH接続し、ホームディレクトリに移動する。

```
cd
```

以下のコマンドを順に実行していき、Swap領域を確保する。

```
sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512
```

```
sudo chmod 600 /swapfile1
```

```
sudo mkswap /swapfile1
```

```
sudo swapon /swapfile1
```

```
sudo sh -c 'echo "/swapfile1  none        swap    sw              0   0" >> /etc/fstab'
```

#gemをインストールする
クローンしたアプリケーションを起動するために必要なgemをインストールする。

以下のコマンドを実行し、クローンしたアプリケーションのディレクトリに移動する。

```
cd /var/www/testapp
```

以下のコマンドを実行し、rbenvでインストールしたRubyのバージョンが使用されているかチェックする。

```
ruby -v
```

##本番環境でgemを管理するためのbundlerをインストールする
ローカル環境で以下のコマンドを実行し、開発環境で使用しているbundlerのバージョンを確認する。

```
bundler -v
```

開発環境によってバージョンは異なるが、今回は2.0.1であったとする。
同じバージョンのものを本番環境にも導入するために、再度EC2インスタンスにSSH接続し、下記のコマンドを実行する。

```
gem install bundler -v 2.0.1(ローカル環境と同じバージョン)
```

```
bundle install
```

##環境変数の設定をする
データベースのパスワードなどセキュリティのためにGithubにアップロードできない情報は環境変数を利用して設定する。
環境変数はrailsでは「ENV['<環境変数名>']」という記述でその値を利用することができる。

###secret_key_baseを作成する
secret_key_baseとは、Cookieの暗号化に用いられる文字列である。railsアプリケーションを動作させる際は必ず用意する必要がある。外部に漏らしてはいけない値なので環境変数から参照する。

下記のコマンドを実行し、secret_key_baseを作成する。
コマンドを実行すると長い文字列が生成されるのでこれをコピーしておく。

```
rake secret
```

###EC2インスタンスに環境変数を設定する
環境変数は/etc/environmentというファイルに保存することでサーバ全体に適用される。環境変数の書き込みはvimコマンドを用いて行う。

下記のコマンドを実行し/etc/enrvironmentを編集する。

```
sudo vim /etc/environment
```

/etc/environmentを以下のように編集する。

```ruby:/etc/environment
DATABASE_PASSWORD='<MySQLのrootユーザのパスワード>'
SECRET_KEY_BASE='<先ほどコピーしたsecret_key_base>'
```

編集し保存したら下記のコマンドを実行し、一度ログアウトする。

```
exit
```

再度EC2インスタンスにSSH接続する。
下記のコマンドを実行し、環境変数が適用されているか確認する。

```
env | grep DATABASE_PASSWORD
```

```
env | grep SECRET_KEY_BASE
```

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

ポートの設定をするためにはEC2のセキュリティグループという設定を変更する必要がある。
セキュリティグループとはEC2サーバが属するまとまりのようなもので、複数のEC2インスタンスのネットワーク設定を一括で行うためのものである。

AWSにログインし、EC2インスタンス一覧画面から対象のインスタンスを選択し、セキュリティグループのリンクをクリックする。
![18_セキュリティグループリンクスクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654714/03769502-7da9-c1a7-37a7-8deb2a523383.png)

セキュリティグループの設定画面に遷移するのでインバウンドタブの編集をクリックする。
![19_インバウンド編集スクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654714/139113d2-906b-9d00-0c03-985bd2a81260.png)

インバウンドのルールの編集ポップアップ画面が表示されたら、ルールの追加をクリックし、タイプを「カスタムTCPルール」、プロトコルを「TCP」、ポート範囲を「3000」、送信元を「カスタム」・「0.0.0.0/0」に設定する。
![セキュリティグループポートスクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654714/aa9897e5-2d1a-7e76-fabb-8e8665fc0101.png)

#本番環境でrailsを起動する
本番環境でrailsを起動する前に、現状、開発環境と本番環境でMySQLの設定が異なるため、開発環境のMySQLの設定を本番環境に合わせる。

開発環境のconfig/database.ymlを以下のように編集し、コミット、Githubにプッシュする。

```ruby:config/database.yml
production:
  <<: *default
  database: ~~~（それぞれのアプリケーション名によって異なるのでこれは編集しない）
  username: root
  password: <%= ENV['DATABASE_PASSWORD'] %>
  socket: /var/lib/mysql/mysql.sock
```

EC2インスタンスにSSH接続し、下記のコマンドを実行する。

```
git pull origin master
```

以下のコマンドを実行し、アプリケーションのディレクトリに移動する。

```
cd /var/www/testapp
```

以下のコマンドを実行し、データベースを作成する。

```
rails db:create RAILS_ENV=production
```

以下のコマンドを実行し、マイグレーションを実行する。

```
rails db:migrate RAILS_ENV=production
```

もしここでMysql2::Error: Can't connect to local MySQL server through socketというエラーが発生した場合にはMySQLが起動していない可能性があるため、以下のコマンドを実行し、MySQLの起動を行う。

```
sudo service mysqld start
```

以下のコマンドを実行し本番環境でunicornを起動する。
「-c config/unicorn.rb」は設定ファイルの指定、「-E production」は本番環境で操作させることを意味する。
「-D」はDaemonの略で、プログラムを起動させつつターミナルで別のコマンドを打てるようにするコマンドである。 

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

ここで、ブラウザで http://123.456.789:3000/ にアクセスしてみる。(Elastic IPが123.456.789の場合)
cssが反映されていない画面が表示されていれば成功。

##アセットファイルをコンパイルする
開発環境ではアクセスごとにアセットファイル(image、css、javascript)を自動でコンパイルする仕組みが備わっているが、本番環境ではパフォーマンスのためアクセスごとに実行されないようになっている。
よって、本番環境では事前にアセットをコンパイルする必要がある。
以下のコマンドを実行し、アセットをコンパイルする。

```
rails assets:precompile RAILS_ENV=production
```

コンパイルが成功したら反映を確認するためrailsを再起動する。
まず、ターミナルからUnicornのプロセスを確認しプロセスをkillする。

以下のコマンドを実行しUnicornのコマンドを確認する。
psコマンドは現在動作しているプロセスを確認するためのコマンドで、auxはpsコマンドの表示結果を見やすくするオプションである。
| grep unicornとすることでpsコマンドの結果からunicorn関連のプロセスのみを抽出している。

```
ps aux | grep unicorn
```

表示結果からunicorn_rails masterのPIDを確認する。ここでは例として「17877」だったとする。

以下のコマンドを実行しUnicornのプロセスを停止する。

```
kill 17877
```

下記のコマンドを実行し、再度Unicornを起動する。このときRAILS_SERVE_STATIC_FILES=1を先頭に付ける。これによりコンパイルされたアセットをUnicornが見つけられるようになる。

```
RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D
```

再度ブラウザで http://12.345.67.890:3000/ にアクセスしてみる。(Elastic IPが12.345.67.890の場合)
今度はcssが反映された状態で画面が表示されるはず。

#railsの起動がうまくいかない時に確認すること
- プッシュのし忘れ、EC2サーバのプルのし忘れ
- EC2サーバ側で/var/www/testapp/log/unicorn.stderr.logを確認しエラーが出ていないか確認する
- MySQLの起動は正しく行えているか
- SECRET_KEY_BASE等が正しく設定できているか
- EC2インスタンスの再起動を行ってみる

#参考
[ssh-keygenコマンド](https://www.atmarkit.co.jp/ait/articles/1908/02/news015.html)
[テンプレートリテラル記法](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals)

# 関連記事
[AWSを使ってアプリケーションを公開する手順(1)AWSアカウントの作成](https://qiita.com/osawa4017/items/78ee5912084dd83c5a0d)
[AWSを使ってアプリケーションを公開する手順(2)EC2インスタンスの作成](https://qiita.com/drafts/ec5aa2965a89320b074d)
[AWSを使ってアプリケーションを公開する方法(3)EC2インスタンスの環境構築]
(https://qiita.com/osawa4017/items/8dc09203f84e04bf0e66)
[AWSを使ってアプリケーションを公開する手順(4)データベースの作成]
(https://qiita.com/osawa4017/items/7dba25f4fa30ab0b1246)
[AWSを使ってアプリケーションを公開する手順(6)Nginxを導入する]
(https://qiita.com/osawa4017/items/9b707baf6ddde623068c)