Amazon LightsailでRailsアプリをデプロイしたい（PostgreSQL）

## やりたいこと
エンジニア階級「癸（みずのと）」なりに、フランス語のアプリをガチで個人開発しています。:flag_fr:
今回は、いよいよ本番リリース。
Amazon Lightsailで個人開発のRailsアプリをデプロイしたいです。

Lightsailと言えど、サーバーのインスタンスとして使われているのはAWS EC2。基本的には下のリンクの記事を見れば、ある程度pikaわかるのですが（ほんとありがたい）、一部DBの設定などが違ったので自分がうまくいった手順を日記としてメモしておきたいと思います。

**全然体系立ってないですがご容赦ください！**
部分的に誰かの為にもなれば幸いです。

https://pikawaka.com/rails/ec2_deploy

実際にデプロイしたサイトはこちら。

https://minnfra.com

フランス語勉強している方はぜひ使ってみてください。

## 実装！

### 1. インスタンスの作成

![Capture d’écran 2022-01-01 à 20.38.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/4a5671f0-5344-b3f3-8355-16c112015a79.png)

```plaintext:今回のインスタンスの設定
Linux/Unix
Amazon Linux 2
sshペアキー： SSHキーペアの変更。新規作成後、ダウンロード。
インスタンスプランの選択： $10（３ヶ月無料）
インスタンス名： Amazon_Linux_2-1
キーオンリータグ： なし
キー値タグ： なし
```

### 2. 静的IPの作成

ネットワーキング → 静的 IP の作成 → インスタンスにアタッチ

### 3. sshでインスタンスに接続

ブラウザで使えるターミナルで簡単にssh接続できるぜ！というのがLightsailのウリらしいけど、僕は普通にローカルPCのターミナルからssh接続に挑戦。

ダウンロードしてデスクトップに置いてたSSHキーペアを.sshフォルダに格納。

```
$ mv ~/Desktop/*.pem ~/.ssh
```

キーの権限を変える。
既存のACL（Access Control List）を削除して，モードの数値の指定に従ったACLを再設定します。
所有者に読み取り権限を与える。

```
$ cd ~/.ssh

~/.ssh
$ chmod 400 YourKeyPair.pem
```

楽にssh接続できるようにconfigファイルに設定を記述
 
```
~/.ssh
$ vi config
```

```plaintext:~/.ssh/config
Host lightsail-yourapp
  HostName 12.345.67.890 #静的IPアドレス
  User ec2-user
  IdentityFile ~/.ssh/YourKeyPair.pem
```

次からこれでssh接続できるようになる。

```plaintext
~/.ssh
$ ssh lightsail-yourapp

The authenticity of host '35.75.254.144 (35.75.254.144)' can't be established.
ECDSA key fingerprint is SHA256:uGfj9K1234567yY0rbxa2nI7Uabcdefghijk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '35.75.254.144' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|
```

### 4. インスタンスの環境構築

#### 初期設定

```
$ sudo yum update

Total download size: 34 M
Is this ok [y/d/N]: y

Complete!
```

[追記] 2022/03/05に実行したら下記のような結果になりました。

```
$ sudo yum update
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                                                                                                                               | 3.7 kB  00:00:00
No packages marked for update
```

必要なパッケージのインストール

```
$ sudo yum -y install gcc-c++ make patch git curl zlib-devel openssl-devel ImageMagick-devel readline-devel libcurl-devel libffi-devel libicu-devel libxml2-devel libxslt-devel
```

#### Node.js

JavaScriptが動くように、Node.jsをインストール。

```
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -

$ sudo yum install -y nodejs
```

#### yarnをインストール

```
$ curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo

$ sudo yum -y install yarn
.
.
Installed:
  yarn.noarch 0:1.22.17-1

Complete!
```

#### rbenvインストール

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

```
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

$ rbenv install 2.7.2
// ここでフリーズした...?と思うくらい結構時間かかりました。

$ rbenv rehash

$ rbenv global 2.7.2

$ ruby --version
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
```

### 5. DBを作る（Postgresql）

実は当初アプリケーションサーバーのローカルにDBを置いちゃうか、別にインスタンスを立てるか（=マネージドデータベースにするか）と迷っていましたが、ローカルに置くということは、パブリックなサーバーにDBを置くということということに気づき、ちゃんとDBはDBのインスタンスを作ることにしました。（言えない、僕がAWSソリューションアーキテクトプロフェッショナルだなんて... :see_no_evil:）

#### Lightsailマネージドデータベースの作成

![Capture d’écran 2022-01-15 à 13.53.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/fae562c0-6ada-93f9-55f3-e151cf9b90cb.png)


![Capture d’écran 2022-01-15 à 14.00.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/f303f7e7-12b3-f597-fe05-6548e03235fa.png)

```plaintext:今回のマネージドデータベースの設定
ap-northeast-1a
PostgreSQL 12.8
ログイン認証情報の指定： なし
マスターデータベース名を指定： なし
データベースプラン： 標準、$15 USD（最初の3ヶ月無料）
名前： 一意であればなんでも。わかりやすいのを。
キーオンリータグ： なし
キー値タグ： なし
```

結構時間かかります。10分くらいかかったかな。

### 6. インスタンスにアプリを入れる

鍵作る。

```
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
// 以下Enterを押すだけ
Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
```

作った鍵を見てみる。

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

表示された鍵をコピーしてgithubへ。
https://github.com/settings/keys

`New SSH key`ボタンを押して、鍵ペーストして任意の名前を。

でssh接続してみる。

```
[ec2-user@... ~]$ ssh -T git@github.com
The authenticity of host 'github.com (52.192.72.89)' can't be established.
ECDSA key fingerprint is SHA256:p2...
ECDSA key fingerprint is MD5:7b:....
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,52.192.72.89' (ECDSA) to the list of known hosts.
Hi kakudaisuke! You've successfully authenticated, but GitHub does not provide shell access.
```

アプリをインストールするディレクトリ作っておく。
ここに後でアプリのリポジトリをgit cloneします。

```
$ sudo mkdir /var/www/

// ディレクトリの権限をec2-userにする
$ sudo chown ec2-user /var/www/
```

### 7. Nginxインストール

```
[ec2-user@... ~]$ sudo amazon-linux-extras install nginx1

$ nginx -v
nginx version: nginx/1.20.0

$ sudo systemctl start nginx
```

ブラウザで静的IPアドレスを入力すると見れる！

![Capture d’écran 2022-01-02 à 22.07.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/645077/6f5e1e4e-3ee9-8214-8e5b-3ac0c3d7f533.png)

#### Nginxの設定をする。

```
// Nginxの設定を見られる
$ cat /etc/nginx/nginx.conf

// rails.confを作って開く
$ sudo vi /etc/nginx/conf.d/rails.conf
```

[pikawakaさん](https://pikawaka.com/rails/ec2_deploy)のを参照しました。

```nginx:/etc/nginx/conf.d/rails.conf
upstream unicorn {
  server unix:/var/www/YOUR_APP_NAME/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name http://35.75.254.144/; // IPアドレス
  root /var/www/YOUR_APP_NAME/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

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

  try_files $uri/index.html $uri @unicorn;
  error_page 500 502 503 504 /500.html;
}
```

### 8. unicornを入れる

自分のアプリのローカルリポジトリで作業です。
Gemにunicornを追加します。

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

unicorn.rbファイルを下記のように作成します。
[pikawakaさん](https://pikawaka.com/rails/ec2_deploy)の引用です（まるぱくりとも言う...）。

```ruby:config/unicorn.rb
# rootパスのディレクトリを指定
root_path = File.expand_path('../../', __FILE__)

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

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

# プロセスIDの保存先を指定
pid "#{root_path}/tmp/pids/unicorn.pid"

# ポート番号を指定
listen "#{root_path}/tmp/sockets/unicorn.sock"

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

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

#応答時間を待つ上限時間を設定
timeout 30

# ダウンタイムなしでUnicornを再起動時する
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
```

### 9. gitからリポジトリをクローンする

前準備として、Passwordを設定する。下記ドキュメントの手順で作ればOK。

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

```
[ec2-user@... ~]$ cd /var/www/
[ec2-user@... www]$ git clone https://github.com/YOUR_ACCOUNT/YOUR_REPOSITORY.git
Cloning into 'minnfra'...
Username for 'https://github.com': アカウント名を入れる
Password for 'https://YOUR_ACCOUNT@github.com': ここは打っても何も表示されない
```

ローカルでbundlerのバージョンを確認。

```terminal:ローカル
❯ bundler --version
Bundler version 2.2.3
```

ローカルと同じバージョンのbundlerをインストール。

```
[ec2-user@... minnfra]$ gem install bundler -v 2.2.3
Fetching bundler-2.2.3.gem
Successfully installed bundler-2.2.3
Parsing documentation for bundler-2.2.3
Installing ri documentation for bundler-2.2.3
Done installing documentation for bundler after 3 seconds
1 gem installed
````

#### bundle installする

```
[ec2-user@... minnfra]$ bundle install
```

##### エラー１（NoMemoryError: failed to allocate memory）

```
## Backtrace

NoMemoryError: failed to allocate memory
.
.
.
Unfortunately, an unexpected error occurred, and Bundler cannot continue.

First, try this link to see if there are any existing issue reports for this error:
https://github.com/rubygems/rubygems/search?q=failed+to+allocate+memory&type=Issues

If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
https://github.com/rubygems/rubygems/issues/new?labels=Bundler
```

↑月`$10（メモリ2GB）`のインスタンスの時はこんなエラーにならなかったけど、`$3.5（メモリ512MB）`を選ぶとこうなりました。。

こういう時は、pikawakaさんの「[スワップファイルでメモリを強化](https://pikawaka.com/rails/ec2_deploy#スワップファイルでメモリを強化)」を見て実行すればOK。本当にありがとうございます。


##### エラー２（An error occurred while installing pg (1.2.3)）

```
Fetching pg 1.2.3
Installing pg 1.2.3 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /home/ec2-user/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/pg-1.2.3/ext
/home/ec2-user/.rbenv/versions/2.7.2/bin/ruby -I /home/ec2-user/.rbenv/versions/2.7.2/lib/ruby/2.7.0 -r
./siteconf20220103-19854-1bxwn8w.rb extconf.rb
checking for pg_config... no
No pg_config... trying anyway. If building fails, please try again with
 --with-pg-config=/path/to/pg_config
checking for libpq-fe.h... no
Can't find the 'libpq-fe.h header
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
	--with-opt-dir
	--without-opt-dir
	--with-opt-include
	--without-opt-include=${opt-dir}/include
　　（以下略）

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /home/ec2-user/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/extensions/x86_64-linux/2.7.0/pg-1.2.3/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /home/ec2-user/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/pg-1.2.3 for inspection.
Results logged to /home/ec2-user/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/extensions/x86_64-linux/2.7.0/pg-1.2.3/gem_make.out

An error occurred while installing pg (1.2.3), and Bundler cannot continue.
Make sure that `gem install pg -v '1.2.3' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  pg
```

今度はpg（postgresql）で怒られている。
インスタンスにpostgresのクライアントをインスタンスにインストール必要がある？
てか、そうか、PostgreSQLもクラサバなのか！
アプリサーバーにクライアントがあって、それでもってデータベースのサーバーと通信するってわけか？

#### インスタンスにPostgresqlのクライアントを入れる

この記事のコマンドを拝借。
https://qiita.com/santasan1224/items/22a08e4b6fc9929c7ad4

```
$ amazon-linux-extras list | grep postgre
  5  postgresql9.6            available    \
  6  postgresql10             available    [ =10  =stable ]
 41  postgresql11             available    [ =11  =stable ]
 58  postgresql12             available    [ =stable ]
 59  postgresql13             available    [ =stable ]
```

先ほどLightsailマネジメントコンソール上で作ったマネージドデータベースのPostgreSQLが`ver 12.8`なので`postgresql12`を選択。

```
$ sudo amazon-linux-extras install -y postgresql12
```

これでインストールはできた。
ちなみに、12.**8**とかそういう小数点以下を指定するのは無いらしい↓

```
$ sudo amazon-linux-extras install postgresql12.8
Topic postgresql12.8 is not found.
```

これでLightsailインスタンス内でpsqlコマンドが打てるようになった！

```
$ psql --version
psql (PostgreSQL) 12.7
```

でもbundle installがまだ怒られる。
よく読むと`No pg_config...`と。pg_configってのがないのか？

この辺を読む

- [yumでPostgreSQLをインストールしてみよう](https://lets.postgresql.jp/documents/tutorial/yum/yum)
- [postgresql](http://penguin.triumf.ca/comps/fc3/ja_frames/postgresql.php)
- https://www.setouchino.cloud/blogs/95

このコマンド↓を打つ！って記事が多い。この辺はPostgreSQLのパッケージらしい。

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

でも今は、`pg_config`ってのがないって怒られていて、`postgresql-devel`が`PostgreSQLのヘッダやpg_configなどの開発用モジュール`らしいからとりあえずこれだけインストールしてみる。

```
$ sudo yum install postgresql-devel
```

この状態でもう一度`bundle install`挑戦。
ぬお通った！

### 10.precompileをする

```
$ rails assets:precompile RAILS_ENV=production
rails aborted!
ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit
```

怒られた。
credentialsを作ります。

本番環境用のcredentialsファイルを作ります。
ローカルでの作業になります。

```
$ EDITOR="vim" bin/rails credentials:edit --environment=production
Adding config/credentials/production.key to store the encryption key: 1234566789abcdefghijklm

Save this in a password manager your team can access.

If you lose the key, no one, including you, can access anything encrypted with it.

      create  config/credentials/production.key

Ignoring config/credentials/production.key so it won't end up in Git history:

      append  .gitignore

Couldn't decrypt config/credentials/production.yml.enc. Perhaps you passed the wrong key?
```

余談ですが、誤って先に手動で`config/credentials/`に`production.yml.enc`とファイルを作ってから、このコマンドでこのファイルを開こうとするとこういう残念な結果になります（恥）↓

```
$ EDITOR="vim" bin/rails credentials:edit --environment=production
Couldn't decrypt config/credentials/production.yml.enc. Perhaps you passed the wrong key?
```

さて。
もう一回`rails assets:precompile RAILS_ENV=production`やってみる。

```
[ec2-user@ip-172-26-0-224 minnfra]$ rails assets:precompile RAILS_ENV=production
Missing encryption key to decrypt file with. Ask your team for your master key and write it to /var/www/minnfra/config/credentials/production.key or put it in the ENV['RAILS_MASTER_KEY'].
```

怒られた。

credentialsディレクトリはこんな状況。

```
$ ls config/credentials/
credentials.yml.enc  production.yml.enc
```

そうか、`master.key`がない！（production.yml.encの場合、`prodcution.key`）

作る。

```
$ vi production.key
```

で、ローカルリポジトリの`config/production.key`の中身をコピーして貼り付ける。

そして再度突進。

```
$ rails assets:precompile RAILS_ENV=production
rails aborted!
ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit
```

突き返される。
`secret_key_base`がない。

secret_key_baseとは？
[Railsのsecret_key_baseを理解する（翻訳）](https://techracho.bpsinc.jp/hachi8833/2017_10_24/46809)

Lightsailインスタンスの中でこれをやる↓

```
$ rake secret
abcdefghijklmnopqrstuvwsyz0123456789abcdefghijklmnopqrstuvwsyz0123456789
```

この出力された文字列をローカルでエディタでproduction.yml.encに書き込んでリモートリポジトリにプッシュして、インスタンスにデプロイ（git pull）。

で、もういっちょ`rails assets:precompile RAILS_ENV=production`。

通った！でもなんかwarning出てる。

```
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  css/application-9184e8ff.css (1.45 MiB)
  js/application-797e26ccb2c4e5207df8.js (1.43 MiB)
  js/bundle-d36948777eda3f1527a8.js.map.gz (287 KiB)
  js/application-797e26ccb2c4e5207df8.js.gz (517 KiB)
  js/application-797e26ccb2c4e5207df8.js.map.gz (794 KiB)
  js/application-797e26ccb2c4e5207df8.js.br (429 KiB)
  js/application-797e26ccb2c4e5207df8.js.map.br (656 KiB)

WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
  application (2.88 MiB)
      css/application-9184e8ff.css
      js/application-797e26ccb2c4e5207df8.js


WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--7-1!node_modules/postcss-loader/src/index.js??ref--7-2!node_modules/sass-loader/dist/cjs.js??ref--7-3!app/javascript/stylesheets/application.scss:
    Entrypoint mini-css-extract-plugin = *
    [0] ./node_modules/css-loader/dist/cjs.js??ref--7-1!./node_modules/postcss-loader/src??ref--7-2!./node_modules/sass-loader/dist/cjs.js??ref--7-3!./app/javascript/stylesheets/application.scss 4.27 MiB {0} [built]
        + 1 hidden module
```

ちなみに、Railsのデフォルトの環境は、`development`, `test`, `production`らしく、`staging`とか作りたかったら、`webpacker.yml`もちゃんと定義してあげないといけないみたい。こんな風に怒られた↓

```
RAILS_ENV=staging environment is not defined in config/webpacker.yml, falling back to production environment
```

定義してあげる。

```ruby:config/webpacker.yml
staging:
  <<: *default

  # Production depends on precompilation of packs prior to booting for performance.
  compile: false

  # Extract and emit a css file
  extract_css: true

  # Cache manifest.json for performance
  cache_manifest: true
```

### 11.本番用のDB作成

ドキドキ。縄文土器。

```
$ rails db:create RAILS_ENV=production

$ rails db:migrate RAILS_ENV=production
```

データベースにアクセスしてみる。

```
[ec2-user@ip-172-26-0-224 ~]$ psql --host=ls-47e123456789abcdefghijkl.abcd123456.ap-northeast-1.rds.amazonaws.com --port=5432 --username=<USER_NAME> --password --dbname=<DATABASE_NAME>
Password: <Lightsailマネジメントコンソール参照>
psql: error: FATAL:  database "DATABASE_NAME" does not exist
```

怒られた。
これで解決↓。

```
[ec2-user@... ~]$ psql --host=ls-47e031b8c36b0ab29fdca644c674b3ccf1a9e5ce.c6jqhebd9xuz.ap-northeast-1.rds.amazonaws.com --port=5432 --username=minnfra --password --dbname=postgres
Password: <Lightsailマネジメントコンソール参照>
```

なるほど、`dbname`は`postgres`なのか。

### 12. 起動する

#### Nginx起動。

```
$ sudo systemctl reload nginx

$ sudo systemctl start nginx
```

#### Rails起動

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

IPアドレスにアクセス...
み、みれる！！！！アプリが見れる涙（ら抜き表現）

後日再度試したら、Nginxのページしか表示されなかったのですが、一度プロセスを`kill`して再起動したらちゃんとアプリのページが表示されました。

```

$ ps aux | grep unicorn

$ kill 12345
```