EC2、RDSを利用してRailsアプリをデプロイする [NGINX + puma + PostgreSQL + Rails 6]

# 目標：AWSのEC2、RDSを利用してRailsアプリをデプロイする

- Railsチュートリアルをより実践的な環境、Ultimate Hard Modeで挑みたい
- NGINX, puma, PostgreSQL, Rails 6で開発〜デプロイ
- ローカル開発環境へ移行
  [はじめてのDockerでRails開発環境構築 [NGINX + Rails 5 (puma) + PostgreSQL] - Qiita](https://qiita.com/daddy_hacker/items/66d8fd40ce01d1aeea02)
- 脱Herokuで模擬CI/CD環境を取り入れたい（この後自動デプロイまでいきたい）
- AWSを利用した、より実践的な本番環境構築のウォームアップ



このような目的のため、とりあえずAWS上でアプリが動くことを目標にしています
そのためセキュリティ面で本来必要そうな手順（環境変数定義や、SSL対応)を排除して進行しております

備忘録的に記録を残しておりますので、
思い出したことや、不備あれば随時訂正いたします

# 気づき

- 環境構築は学習コスト（時間）高い、けど仮想環境なら壊しても怒られないし、やってみようでなんとかなる

- **作ったアプリが最終的にどのような環境で動くのか、開発にトップダウンの視点が加わる**

- AWSの無料範囲内で学べる、学習コスト（マネー）低すぎる

  

# 環境

- AWS EC2 (amazon linux2)

- AWS RDS (PostgreSQL 11.6)

- NGINX: 1.12.2

- puma: 3.12.1

- Rails: 6.0.0

- Ruby: 2.6.3 

  

# 備忘録

## 前提

- EC2インスタンスが稼働している
- VPCはインターネット接続が可能(セキュリティグループ、サブネット、ルートテーブルの設定が適切)
- EC2インスタンスとローカル環境はSSH接続が可能(鍵認証)
- root権限をもつユーザーを作成しログインしていること
- RDSでデータベースインスタンスを作成している(今回はPostgreSQL 11.6, 初期データベースsample_app_production)
- EC2はGitHubと接続しており、`/var/www/rails`ディレクトリに`git clone`でアプリを設置済み
- master.keyを設定済み

### 参考：**上記については以下のサイトを参考に進めました、感謝。**

(DBの選定、設定の部分が異なっております)

[【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その1〜ネットワーク,RDS環境設定編〜】 - Qiita](https://qiita.com/Yuki_Nagaoka/items/975b7598806d6ae0c0b2)





### パッケージのアップデート

まずは

```
sudo yum update
```



## NGINXインストール

 `amazon-linux-extras`からインストール可能

```
sudo amazon-linux-extras install nginx1.12 -y
```

起動と自動起動設定

```
sudo systemctl start nginx && sudo systemctl enable nginx && systemctl status nginx
```

この時点でブラウザからEC2のElastic IPにアクセスすると
"Welcome to **nginx** on Amazon Linux!"というページが表示される(NGINXがポート: 80ポートで待ち受けている)



### 参考: NGINX関連コマンド

起動終了

```
$ sudo systemctl start nginx
$ systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
.
.
.
[sample_app@ip-10-0-0-44 rails]$ sudo systemctl stop nginx
```

自動起動

```
sudo systemctl enable nginx
```





## PostgreSQL（クライアント）をインストール

```
sudo amazon-linux-extras install postgresql11
```



### 参考：クライアント以外もインストールする

```
sudo yum install postgresql-server postgresql-devel postgresql-contrib
```



### 参考：`amazon-linux-extras`を使わない方法やアンインストールなど

[EC2(Amazon Linux2)にPostgreSQLをインストールする | my opinion is my own](https://zatoima.github.io/postgresql-ec2-insatll.html)

PostgreSQL install　Amazon linux2
[Amazon linux2にpostgresqlをインストールする手順 | 瀬戸内の雲のように](https://www.setouchino.cloud/blogs/95)

PostgreSQL install
[PostgreSQL: Linux downloads (Red Hat family)](https://www.postgresql.org/download/linux/redhat/)

PostgreSQL uninstall
[How To Completely Uninstall PostgreSQL | ObjectRocket](https://kb.objectrocket.com/postgresql/how-to-completely-uninstall-postgresql-757)



## PostgreSQLに接続

PostgreSQLクライアントであるpsqlからRDSに作成したDBインスタンスに接続

```
psql --host=<DBインスタンスのエンドポイント> --port=5432 --dbname=sample_app_production  --username=sample_app
--port=5432 --dbname=sample_app_production  --username=sample_app

ユーザ sample_app のパスワード:
psql (11.5、サーバ 11.6)
SSL 接続 (プロトコル: TLSv1.2、暗号化方式: ECDHE-RSA-AES256-GCM-SHA384、ビット長: 256、圧縮: オフ)
"help" でヘルプを表示します。

sample_app_production=> 
```

RDSに接続可能なこと、初期データベースが存在することが確認できれば問題ないです



### 参考：AWS RDS公式
[PostgreSQL データベースエンジンを実行する DB インスタンスへの接続 - Amazon Relational Database Service](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_ConnectToPostgreSQLInstance.html)

>## psql を使用した PostgreSQL DB インスタンスへの接続
>
>psql コマンドラインユーティリティのローカルインスタンスを使用して、PostgreSQL DB インスタンスに接続できます。PostgreSQL またはクライアントコンピュータにインストールされた psql クライアントのいずれかが必要です。psql を使用して PostgreSQL DB インスタンスに接続するには、ホスト情報とアクセス認証情報を指定する必要があります。
>
>以下の形式のいずれかを使用して、Amazon RDS 上の PostgreSQL DB インスタンスに接続します。接続時にパスワードを求められます。バッチジョブまたはスクリプトには、`--no-password` オプションを使用します。
>
>*この DB インスタンスに初めて接続する場合、デフォルトのデータベース名 **postgres** を `--dbname` オプションに使用してみてください。*
>
>Unix の場合、次の形式を使用します。
>
>```
>psql \
>--host=<DB instance endpoint> \
>--port=<port> \
>--username=<master user name> \
>--password \
>--dbname=<database name> 
>               
>```
>
>
>(中略)
>
>たとえば、次のコマンドは、架空の認証情報を使用して、`mypgdb` という PostgreSQL DB インスタンス上の `mypostgresql` というデータベースに接続します。
>
>```
>psql --host=mypostgresql.c6c8mwvfdgv0.us-west-2.rds.amazonaws.com --port=5432 --username=awsuser --password --dbname=mypgdb 
>```

### 参考：psql認証のtrust, ident, md5

RDSでDBインスタンスを作成したときの情報を使用します
接続認証にOSのユーザー名とDBのユーザー名の一致を求める仕組みがあるようですが
RDSとのhost-client接続ではユーザー名が違ってもパスワード認証で問題なかったです

[PostgreSQL 認証に失敗しないための Ident、MD5、Trust 比較 - eTuts+ Server Tutorial](https://server.etutsplus.com/how-to-configure-postgresql-to-use-ident-md5-trust-authentication/)



###  うまくいかない時は

#### タイムアウトしてしまい、認証に至らない（超重要）

セキュリティグループの設定を確認する
私はアウトバウンドの設定が必要でした（控えめに言って半日悩んだ）
[EC2からRDS(MySQL)に接続できない。セキュリティグループの設定について・・・ - Qiita](https://qiita.com/nao-otsu/items/8cabd447d73b621cd07d)



#### クライアント(EC2)とホスト(RDS)のPostgreSQLバージョンが異なる

`amazon-linux-extras`を使用せずにインストールすると
クライアント側が古いバージョンになってしまうことがありました
(RDS側は初期設定のPostgreSQL 11.6)
古いバージョンのアンインストールをして、新しいものをインストールしてください

```
警告： psql のメジャーバージョンは 9 ですが、サーバーのメジャーバージョンは 11 です。
         psql の機能の中で、動作しないものがあるかもしれません。
```





### 参考（メモ）：以下はクライアントとしてpsqlを利用するだけなら関係ないかも

#### 設定ファイルの場所

postgresql.conf

```
sudo vi /var/lib/pgsql/data/postgresql.conf
```

pg_hba.conf（認証関係）

```
sudo vi /var/lib/pgsql/data/pg_hba.conf
```



#### ユーザーpostgresのパスワードがわからない

途中自動生成されたユーザーpostgresのpasswordがわからず再設定しました
[Postgresでパスワードを忘れた場合の対策 - Qiita](https://qiita.com/colorrabbit/items/0e481af2490603a271fe)



#### local(EC2)にDBを設置する場合のコマンドメモ

```
$ sudo postgresql-setup initdb
$ sudo systemctl enable postgresql.service
$ sudo systemctl start postgresql.service
```



## PostgreSQLの設定をする

`config/database.yml`を編集

```
default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres
  password: password
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  host: db
  database: myapp_development

test:
  <<: *default
  host: db
  database: myapp_test

production:
  <<: *default 
  database: sample_app_production
  username: postgres # RDS DBインスタンスのユーザー名
  password: password # RDS DBインスタンスのパスワード
  host: xxxx.xxxx.ap-northeast-1.rds.amazonaws.com # RDS DBインスタンスのエンドポイント
```

本来ここに平文でユーザー名やパスワードを記録しないほうがよい

productionの`<<: *default`が抜けていてadapterが指定されていないよと半日怒られ続けました...
"adapterActiveRecord::AdapterNotSpecified: database configuration does not specify adapter"

hostを指定していればportの指定は不要でした

### 参考：database.ymlのPostgreSQL向け設定情報 [ActiveRecord::ConnectionAdapters::PostgreSQLAdapter](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html)



## NGINXの設定をする

dockerで環境構築したものが引き継がれたらいいのに...

pumaとsocket接続するようにしています

/etc/nginx/conf.d/sample_app.confを作成、編集

```
vi　/etc/nginx/conf.d/sample_app.conf
```



```
error_log  /var/www/rails/sample_app/log/nginx.error.log;
access_log /var/www/rails/sample_app/log/nginx.access.log;

##ココ
upstream sample_app {
        server unix:///var/www/rails/sample_app/tmp/sockets/puma.sock;
}

server {
    listen 80;
    client_max_body_size 4G;
    server_name 54.238.15.249;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/rails/sample_app/public;

    location ~ ^/assets/ {
        root /var/www/rails/sample_app/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://sample_app;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/rails/sample_app/public;
    }
}
```

NGINX再起動

```
sudo systemctl restart nginx
```



参考：NGINXコマンドほか

自動起動設定

```
sudo chkconfig nginx on
```



## pumaの設定をする

アプリケーションサーバーとしてunicornは使用しません
Railsに組み込まれているpumaを利用してみます

NGINXとpumaはソケット接続するため`config/puma.rb`に以下を2点編集
（私はDockerで開発環境をNGINX + pumaに設定をした時点で設定済みでした）

```
# ポート: 3000をlistenしない
#port        ENV.fetch("PORT") { 3000 }

# socketの設定
bind "unix://#{Rails.root}/tmp/sockets/puma.sock"
```



### 参考：puma.rbの設定　[puma/dsl.rb at master · puma/puma](https://github.com/puma/puma/blob/master/lib/puma/dsl.rb)



### 参考：puma関連コマンド

 production環境でpuma起動

```
bundle exec rails s -e production
```

puma, pumactlコマンドは私の環境では使えませんでした

```
bundle exec pumactl start
```

[Pumaの起動におけるpumaコマンドとpumactlコマンドの違い - Qiita](https://qiita.com/himatani/items/33cbbe84815534f8b479)

> ### pumaコマンドとpumactlコマンドの違い
>
> このあたりちゃんと説明した日本語の情報がとても少なくて、リポジトリのREADMEを読んだり「what is deference between puma command and pumactl command」みたいなキーワードでがんばって調べたらRuby Journalになんかそれっぽいことが書いてあった。英語ができないととてもつらい。
>
> > As we can see that above operations can be tedious and error prone and definitely not fun to work with a big deployment scale. Introducing pumactl, this utility automates all of above tasks.
> > [Digesting Pumactl](http://ruby-journal.com/digesting-pumactl/)
>
> 要するに、pumaコマンドで起動したりしてるといちいちオプション付けないといけないから大規模開発だとめっちゃつらいしやばいくらい闇だし、pumaコマンドでやってることはpumactlコマンドで自動化しような。ということを言っている。
>
> なるほど、確かに。pumactlコマンドだと問答無用で設定ファイルが読まれるようになっているから、スレッドの数はどうするんだだの、ポート番号はどうするんだだの、ちゃんとコードで管理できる。
>
> 停止や再起動においてもpumactlコマンドで安全にできるらしい。
>
> > Available commands: halt, restart, phased-restart, start, stats, status, stop, reload-worker-directory





## YarnとNodeをインストール、アップデート

Rails 6以降で必要
以降の過程でYarnが必要、nodeが古いと言われます

Yarn

```
#　wgetインストール済みなら省略可能
$ yum -y install wget
$ wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
$ curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -


$ sudo yum install yarn

$ yarn --version
1.22.4
```

Node

```
$ sudo npm install n -g
$ sudo n stable

$ node -v
v12.18.2
```

参考：[capistrano・EC2・postgresql・rails6で自動デプロイ設定した際のエラー例 - Qiita](https://qiita.com/shota0701nemoto/items/980c613dfc9be9586792)





## アプリを起動させる

マイグレーション、アセットプリコンパイル

```
# マイグレーション
bundle exec rake db:migrate RAILS_ENV=production

# プリコンパイル
bundle exec rake assets:precompile RAILS_ENV=production

```

Webサーバー、アプリケーションサーバー起動

```
# NGINX再起動
sudo service nginx restart

# puma起動
bundle exec rails s -e production
```



**問題なければブラウザからElastic IPにアクセスすることで、
アプリのインターフェイスが表示されるはずです**



## "We're sorry, but something went wrong."

もうね、怒られるのなれましたよ

ブラウザにに上記メッセージが表示される場合ログを確認しましょう

```
cd log # アプリルート直下

tail -n 30 production.log

tail -n 30 nginx.error.log
```

私の場合`production.log`は空っぽで
`nginx.error.log`で以下のエラーが記録されていました

"connect() to unix:/home/var/www/rails/sample_app/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream"

/etc/nginx/conf.d/sample_app.confのsocketパス指定が間違っておりました



## その他既知の問題点

- SSL非対応
  `config/environments/production.rb`の`config.force_ssl = true`を`config.force_ssl = false`に

- 新規ユーザー登録のところでメール認証がうまく行かない

  今後対応