Dockerを使用したRailsアプリをAWS(EC2, RDS)からHerokuに移行する

AWSの無料枠が使える期限が差し迫っていること、今後は別アプリに力を入れたいのでAWSにアップしているRailsアプリを停止することにしてHerokuに移行することにしました。

AWSからHerokuに移行している参考になりそうな以下の記事があったのですぐできるかと思ったのですが、数日費やすことになってしまったので備忘録としてのこします(全体の流れとしてはとてもわかりやすいです)。
[【Rails + MySQL】AWS→herokuの移行 – blog.aiandrox](https://blog.aiandrox.com/posts/tech/2021/02/10/)
[HerokuにMySQL+NginxのRailsアプリを移管した - kumamotone’s blog](https://kumamotone.hatenadiary.jp/entry/2020/01/04/161139)

Dockerを使用しない場合でも同じような手順で使える部分があるとは思いますが参考程度に留めていただきますようお願いいたします。

## 動作環境

- MacOS
- Rails(6.0.4.1、gem `dotenv-rails`を使用して環境変数管理)
- Ruby(2.7.3)
- MySQL(5.7系)
- Docker(20.10.12)
- docker-compose(2.2.3)

移行に際して関連してくるAWSサービス

- EC2
- RDS
- S3

## 前提
- AWS環境にRailsアプリがあること
- RDSにデータを格納していること(MySQL)
- Herokuの登録、ログインができていること
- EC2へSSH接続できること

## 今回しないこと
- Heroku, AWSの詳細な説明
- HerokuにNginxをデプロイ
- 独自ドメインを使用
- S3からcloudinaryへの画像の移行

以下のリンクに移行方法が書いてありますが今回は触れません（登録してあるデータ量が少なかったので手作業でやりました）。
[Railsで運用しているサービスの画像配信をAWSスタックからCloudinaryに移行した話 | スペースマーケットブログ](https://blog.spacemarket.com/code/migration-to-cloudinary/)

## 手順
### 1. RDSのデータのバックアップファイルを作成
AWSで動作していた生データをHerokuに移行するためにバックアップファイルを作成します。

#### 1-1. EC2でSSHログイン
RDSに接続するためにEC2にログインします。

```sh:Localから
ssh -i "鍵ファイル(aaa.pem)" ec2-user@www.xxx.yyy.zzz
```
以下を参考にしています
[[初心者向け] 初めてのEC2ログイン：Linux編 | DevelopersIO](https://dev.classmethod.jp/articles/first-login-to-ec2-linux/#toc-8)

#### 1-2. RDSデータのバックアップファイルを作成する
EC2上で以下のコマンドを実行します。

```sh:EC2上で
mysqldump -u ユーザー名 -h ホスト名 -p データベース名 > dump.sql
```
`dump.sql`のファイル名はお好きな名前で大丈夫です。
ファイルが作成されたこと、どこにファイルがあるかを確認します()。

### 2. バックアップファイルをlocal(Mac)にコピー
AWSのEC2上からlocalに`dump.sql`ファイルをコピーするために以下のコマンドを実行します

```sh:Localから
scp -i "鍵ファイル(aaa.pem)" ec2-user@www.xxx.yyy.zzz:EC2上のdump.sqlまでのファイルパス localの保存したい場所のパス
#=> 例） scp -i "aws-key.pem" username@1.1.1.1:/var/www/hashlog/dump.sql ~/Desktop 
```
scpコマンドに関しては以下のリンクがわかりやすいです。
[scpコマンドでサーバー上のファイルorディレクトリをローカルに落としてくる - Qiita](https://qiita.com/katsukii/items/225cd3de6d3d06a9abcb)
[sshでファイル転送の手順 - Qiita](https://qiita.com/sayama0402/items/c5c2795968ced798150a)

以下は実際にEC2からmysqlのバックアップデータを作成、コピーしている記事です。
[RDSのdumpファイルを取得してlocalのMySQLに反映させる | すな.dev](https://www.sunapro.com/db-dump/#index_id2)

### 3. Herokuにアップロードするための前準備
#### 3-1. Herokuにterminalでログイン、アプリを作成する
以下のリンクを参考にします。
[【Rails7】DockerでRuby on Railsアプリを構築してHerokuにデプロイするまで](https://zenn.dev/kino_puehddby/articles/21a3cf0aacee7c#heroku%E3%81%B8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%AE%E6%BA%96%E5%82%99)

冒頭の記事を参考にMySQLデータベースはHeroku上ではJawsDBを使用しました。
[【Rails + MySQL】AWS→herokuの移行 – blog.aiandrox](https://blog.aiandrox.com/posts/tech/2021/02/10/#cleardb%E3%81%A0%E3%81%A8%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8B%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A7jawsdb%E3%81%AB%E3%81%99%E3%82%8B)

```sh:local
# heroku-cliのインストール
brew tap heroku/brew && brew install heroku
# herokuのコンテナレジストリにログイン
heroku container:login
# herokuアプリを作成 (アプリ名を引数につければ指定したアプリ名で作成できる)
heroku create
```

#### 3-2. MySQL用のアドオンを追加

```sh:terminal(local)
# MySQL5.7用のアドオンを追加
heroku addons:create jawsdb:kitefin --version=5.7
```

以下コマンドでアドオンが追加されているか確認できます。

```sh:terminal(local)
heroku addons
#=> Add-on                           Plan     Price  State  
#=> ───────────────────────────────  ───────  ─────  ───────
#=> jawsdb (jawsdb-amorphous-40408)  kitefin  free   created
#=>  └─ as JAWSDB
```


#### 3-3. 必要な環境変数を `JAWSDB_URL` の値にしたがってHerokuにセットする
[3-2](#3-2-mysql%E7%94%A8%E3%81%AE%E3%82%A2%E3%83%89%E3%82%AA%E3%83%B3%E3%82%92%E8%BF%BD%E5%8A%A0)でHerokuでMySQL(JawsDB)用のアドオンを追加できたので、Heroku上の環境変数の値を以下のコマンドで確認します。

```sh:terminal(local)
heroku config
# 現状だと以下のような感じで表示されるはず(他に値は表示されないはず)
# JawsDBのアドオンが追加できていなければ表示されない
# => JAWSDB_URL=mysql://NEWUSER:NEWPASS@NEWHOST:3306/NEWDATABASE
```


RailsでMySQLを使用している場合は上記の`JAWSDB_URL`の値を一部だけ変えなければいけません。
具体的には`mysql`を`mysql2`に変更します。
以下のコマンドで`JAWSDB_URL`の値を変更できます。
<small>※ `config:set`は`config:add`でも良いかもしれません。</small>

ちなみに間違えて設定した環境変数の値は、terminal`heroku config:unset 環境変数名`で削除できます

```sh:terminal(local)
heroku config:set JAWSDB_URL=mysql2://NEWUSER:NEWPASS@NEWHOST:3306/NEWDATABASE
```

```sh:terminal(local)
# JAWSDB_URL=mysql://NEWUSER:NEWPASS@NEWHOST:3306/NEWDATABASE を参考にすると
heroku config:set DATABASE_NAME="*************" # NEWDATABASE の値を代入
heroku config:set DATABASE_PASSWORD="****************" # NEWPASS の値を代入
heroku config:set DATABASE_USERNAME="***************" # NEWUSER の値を代入
```
人によっては以下の値を設定する場合もある感じです。自分の環境に合わせて設定をします。
[heroku configファイルの確認](https://qiita.com/shizimi_50/items/3f0ca56649dc8f968267#:~:text=heroku%20config%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E7%A2%BA%E8%AA%8D)が参考になりそうです。

- LANG
- RACK_ENV
- RAILS_ENV
- RAILS_LOG_TO_STDOUT
- RAILS_SERVE_STATIC_FILES
- SECRET_KEY_BASE


#### 3-4. Herokuで使用した環境変数の値をRails側で参照できるようにする

`config/database.yml`の各値はHerokuが発行するデータベース用のURLから分割して割り当て、変更する必要があります。
> [【Rails7】DockerでRuby on Railsアプリを構築してHerokuにデプロイするまで](https://zenn.dev/kino_puehddby/articles/21a3cf0aacee7c#mysql%E3%81%AE%E5%A0%B4%E5%90%88)

[3-3](#3-3-%E5%BF%85%E8%A6%81%E3%81%AA%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92-jawsdb_url-%E3%81%AE%E5%80%A4%E3%81%AB%E3%81%97%E3%81%9F%E3%81%8C%E3%81%A3%E3%81%A6heroku%E3%81%AB%E3%82%BB%E3%83%83%E3%83%88%E3%81%99%E3%82%8B) まででHerokuの環境変数に値は設定できているのであとはRails側に設定を反映させます。
設定するファイルは`config/database.yml`です。


```rb:config/database.yml
# Heroku configで表示される JAWSDB_URL の値との対応ができているか確認しながら設定
production:
  <<: *default
  database: <%= ENV['DATABASE_NAME'] %>
  username: <%= ENV['DATABASE_USERNAME'] %>
  password: <%= ENV['DATABASE_PASSWORD'] %>
  url: <%= ENV['JAWSDB_URL'] %>
```
> [【JAWSDB設定】HerokuでMysqlを使う - Qiita](https://qiita.com/shizimi_50/items/3f0ca56649dc8f968267)

### 4. Herokuにアップロード
全然知らなかったのですが、<strong>Dockerで作成したRailsアプリをHerokuにデプロイするには以下の2通りのやり方があるようです</strong>。
どちらもlocalのterminalで何回かコマンドを打ち込む必要があります。

- [Container Registry & Runtime (Docker Deploys) | Heroku Dev Center](https://devcenter.heroku.com/articles/container-registry-and-runtime#getting-started)
- [Building Docker Images with heroku.yml | Heroku Dev Center](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml)

1つ目のデプロイに関しては以下が参考になりそうです。
[DockerでRailsの環境構築してHerokuへデプロイする - Qiita](https://qiita.com/fuku_tech/items/dc6b568f7f34df10cae7#heroku%E3%81%B8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)

私の場合は2個目のやり方に従い`heroku.yml`を作成してデプロイしました。

```yml:heroku.yml(使用するDockerfileと同じ階層に配置)
build:
  docker:
    web: Dockerfile.production # 本番用のDockerfile
run:
  web: bundle exec puma -C config/puma.rb
```

terminalで以下のコマンドを実行してデプロイ

```sh:local
# Herokuデプロイ用のブランチを作成
git checkout -b 'heroku_main'
# 変更をcommitする
git add .
git commit -m "Add heroku.yml"
# Herokuのコンテナにスタックをセット
heroku stack:set container
# Herokuにpush
git push heroku heroku_main:main
# DBをdb:migrate
heroku run rails db:migrate
# ブラウザで動作確認
heroku open
```

### 5. エラー解決
ただ私の場合、Herokuにアップロードしたあと`heroku open`コマンドを実行してアプリして動作確認しましたが、以下のエラーが発生してherokuのエラー画面が表示されました。
`Webpacker::Manifest::MissingEntryError`

以下にかなりいろいろな対処法が記載されていましたが、私の場合は解決に至りませんでした。
[herokuへデプロイする際のWebpaker::Manifest::MissingEntryErrorというエラーの対処法まとめ - Qiita](https://qiita.com/ArakawaYukihiro/items/bb9d548c9887fcfba019)


最終的にはlocalでdocker-composeコマンドを使用して以下のコマンドを実行し、

- `rails webpacker:compile`
- `rails assets:precompile``

`.gitignore`の以下のフォルダの記述を削除してコンパイルされているファイルをgit add, commitして再度[4](#4-heroku%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89) のやりかたでHerokuへアップすることでアプリが動作するようになりました。

- `/public/assets`フォルダ
- `/public/packs`フォルダ

### 6. バックアップファイルのデータをDB(JawsDB)にインポートする
最後です。
以下のコマンドをlocalのterminalで実行してlocalのバックアップファイルからJawsDBにデータをインポートします。
[JawsDB MySQL | Heroku Dev Center](https://devcenter.heroku.com/articles/jawsdb#migrating-the-backup-to-jawsdb)

```sh:local
mysql -h NEWHOST -u NEWUSER -pNEWPASS NEWDATABASE < dump.sql
```
<strong>注意点ですが、`-p` の後にスペースは入れずにコマンドを実行してください。エラー扱いになってしまいます（ややこしい）。</strong>
[Unable To Migrate .SQL File to JawsDb (MySQL) - Stack Overflow](https://stackoverflow.com/questions/46745727/unable-to-migrate-sql-file-to-jawsdb-mysql)

実行完了までしばらく時間がかかります。`Warning: Using a password on the command line interface can be insecure`が表示されたら正常にコマンドが実行できているはずです。そのまま完了するまで待ちましょう。

### 7. 完了
`heroku open`などで該当アプリにアクセスして期待通りの表示がされていればOKです。
お疲れ様でした。