AWSにDockerでデプロイしたSpringアプリをRenderに移行してみた

# はじめに
タイトルにある通り、一度AWSにデプロイし誰でも見られる状態になっているアプリをRenderに移行しました。
理由としては[他にAWSへデプロイしたアプリ](https://github.com/MasaNakamura-ctrl/WaGotcha)があり、AWSの無料枠の制限を超えるのも時間の問題であったためです。
そこで今回の記事ではどのような手順でRenderに移行したかの手順やAWSの違いとして感じたことをお話ししていきます。
まずは具体的にどういうアプリを扱ったのかからお伝えします。
# アプリの概要
今回移行したのはSpring BootとPostgreSQLを使って実装したCRUDアプリとなります。
機能としては画面を開いた直後にログイン画面が表示され、正しいユーザーIDとパスワードを入力するとデータの一覧が表示される、そしてCreate,Update,Deleteにあたるボタンを押下することでDBやUIにそれが反映されるようになっています。
実際の動作は下記の動画のようになっています。
[![WATCHME](https://img.youtube.com/vi/oaYXNaPVrBQ/0.jpg)](https://www.youtube.com/watch?v=oaYXNaPVrBQ)
このアプリをAWSにデプロイした時は下記の構成を取っていました。
![lesson-assumed-app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/1513114d-e0ac-49cb-9a9d-d895ce776a28.png)
具体的な進め方としてはVPC内にパブリックサブネットとプライベートサブネットを構築し、前者にEC2インスタンス、後者にRDSとしてPostgreSQLを立てました。
その後application.propertiesやDockerfileをRDSに接続できるよう書き換えた上でjarファイルを作成し転送させています。
アプリについての詳細は[こちらのリンク](https://github.com/MasaNakamura-ctrl/lesson-assumed-app)からご参照ください。
また、実際のアプリは[こちらのリンク](https://lesson-assumed-app.onrender.com/)で公開しています。(予告なしに停止する場合もあるのでご了承ください。)
後述しますが、アプリの起動に少し時間がかかる場合もあります。
- ユーザーID:lessonList
- パスワード:confirming
# Renderへの移行手順
## RenderにDBを構築する
![render-menu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/6e94ea79-4569-4c91-b21a-4cc7ef6a962a.png)
まず、Renderのトップ画面を開くと右上に"+New"をいうボタンがあるので押下すると写真のようなメニューが表示されます。
まずは"PostgreSQL"を選択し、名前とリージョンなどの必須項目を入力しCreate DatabaseをクリックすればDBは立ち上がります。
そこにあるURLやパスワードなどがないとせっかくアプリをデプロイしても繋がらなく起動ができないので必ず参照できるようにしてください。
## アプリのデプロイ
DBを構築できたら、今度はapplications.propertiesとDockerfileをDBと接続できるよう書き換える必要があります。
```application.properties
spring.application.name=lesson-assumed-app
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# Docker上で構築したDBへの接続(下の3行を書き換え)
spring.datasource.url=jdbc:postgresql://postgres:5432/postgres
spring.datasource.username=user
spring.datasource.password=password

spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
# Hibernateにカラム名をそのままで使うよう設定
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
```
application.propertiesではDBの接続手段として必要なurl,username,passwordをRender側に合わせる必要があります。
(ローカルでの実装時はPostgreSQLをDocker上で構築し接続していたという背景がありますが記事の趣旨から離れるので割愛します)
```application.properties
spring.application.name=lesson-assumed-app
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

# Docker上で構築したDBへの接続(ローカルで起動する際は下記3行のコメントアウトを外してください)
#spring.datasource.url=jdbc:postgresql://postgres:5432/postgres
#spring.datasource.username=user
#spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
# Hibernateにカラム名をそのままで使うよう設定
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

# Renderとの接続(ローカルで起動する際は下記の4行をコメントアウトしてください)
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
server.port=8080
```
具体的にどこを変更したか見やすくなるようした4行を追記し、ローカル接続にあたる箇所はコメントアウトしています。
Renderに限らずGitのリポジトリを使ってデプロイする際に避けたいのは具体的なURLやパスワードなどをベタ書きで公開してしまうことです。
なのでここでは${変数名}によってコードを見るだけではわからないようにしています。
![secretVariables.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/a151a29f-7b7e-4d7b-96e7-8080acc3e17c.png)
問題となる${変数名}の中身ですが、RenderでWebServiceを選択している際に左側の"Environment"を押下すると"Envirionment Variables"というものが出てきます。
ここでKeyという箇所に変数名、Valuesに具体的な中身を記載していくことになります。
この中身はPostgreSQLを選ぶと左にInfoがあるので選択、Connectionsというところに情報が固まっているのでそれをペーストしていくことになります。
![connections.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/a29364f5-115a-47f2-a409-1ead8560ece3.png)
なので、
```
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
# PORTは確実に接続できるよう付けておくことをお勧めします
server.port=8080
```
のValueに入れるのは
- ${DB_USER}＝Usernameからペースト
- ${DB_PASSWORD}＝Passwordからペースト
という風になります。
曲者なのが${DB_URL}になります。
ここで使用するExternal Database URLには冒頭に"jdbc:"が付いてないので、予め入力した上でペーストするようにしてください。
```
${DB_URL}＝jbdc: + External Database URLからのペースト(実際の記述時はスペースや+は書かない)
```
次にDockerfileになります
```Dockerfile
# OpenJDK 21 (軽量な Alpine ベース)
FROM eclipse-temurin:21-jdk-alpine
# 作業ディレクトリの作成
WORKDIR /app
# プロジェクトのソースコードをコピー
COPY . .
# Maven Wrapper に実行権限を付与
RUN chmod +x mvnw
# 依存関係をキャッシュして事前にダウンロード
RUN ./mvnw dependency:go-offline -B
# アプリをビルド（テストをスキップ）
RUN ./mvnw package -DskipTests
# アプリを起動
CMD [“./mvnw”, “spring-boot:run”]
```
を下記のように書き換えアプリを起動する際のコマンドを変更させます。
```Dockerfile
# OpenJDK 21 (軽量な Alpine ベース)
FROM eclipse-temurin:21-jdk-alpine
# 作業ディレクトリの作成
WORKDIR /app
# プロジェクトのソースコードをコピー
COPY . .
# Maven Wrapper に実行権限を付与
RUN chmod +x mvnw
# 依存関係をキャッシュして事前にダウンロード
RUN ./mvnw dependency:go-offline -B
# アプリをビルド（テストをスキップ）
RUN ./mvnw package -DskipTests
# 必要ポートをEXPOSE（Renderでは効果薄だが一応）
EXPOSE 8080
# アプリを起動
CMD ["java", "-Dserver.port=8080", "-jar", "target/lesson-assumed-app-0.0.1-SNAPSHOT.jar"]
```
書き換えが済み、Gitへのpushまでできたら右上の"+New"から"🌐Web Service"を選択します。
Source Codeとしてしたような画面になるのでタブを真ん中にし、🌐があるところにgitでデプロイしたいリポジトリのURLを入力して次に進んでいきます。
![deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/4211e9d4-1f5b-42b9-a9dd-696f935345e3.png)
LanguageはDockerに設定をし、必要に応じProjectやBranchを設定してください。
(RegionはPostgresと揃えたほうがいいです)
一通り設定できたらページの下にある"Deploy Web Service"ボタンを押下してください。
デプロイの進捗を示す画面が出てくるので、いつまで経っても"Live"という表示が出なかったり、Failedと出てしまった場合はログを見て原因を特定するようにしてください。
## テーブル構築とデータの挿入
デプロイが成功したらテーブルを構築しその中にデータも挿入していきます。
これはPostgresのInfoからPSQL Commandをコピーしターミナルに貼り付け実行することでDBの中に入ることができます。
その中で必要なテーブルとデータを入れられたらRenderでの作業は完了です。
きちんと動作するかはシステムテストとして確認することをお勧めします。
もしよろければ[筆者が実際に行った手順](https://github.com/MasaNakamura-ctrl/lesson-assumed-app/tree/main/docs/system-test)も参考にしていただけますと幸いです。
## AWSにデプロイしたものの削除
Renderでデプロイしたものが問題なく動作することを確認できたら、AWSに上がっているものの公開を停止します。
ここは削除していくだけなので箇条書きで進めていきます。
1. Elastic IPを解放する
2. EC2インスタンスを削除する
3. VPCを削除する(このタイミングで関係のあるサブネットなども削除される)
# AWSとの違い
以上がRenderからDockerへと移行した手順でした。
Renderの良い点としてはPostgreSQLに対応しているのでそれありきで実装しているのであればデプロイ先でDBを選んだり対応させる手間を省けるというのが真っ先に挙げられるなと思いました。
また、Dockerを使っているのであればDBの接続先を正しく直せばすぐにデプロイできるのもAWSにはない良さだと思いました。
その一方で無料枠だとアプリの起動が遅く下記の画面のまましばらく待たなくてはいけなくなる場合があります。
![waiting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618104/c1b4a800-e5c4-4e46-93bb-1c1c5f1ceeec.png)
また、DBを無料枠で使い続けようという場合別途対応が必要となるのでポートフォリオとして長期的に公開するのに向いているかという問題は常に付きまとうように思いました。
# 終わりに
今回は一度AWSにデプロイしていたアプリをコスト削減のためにRenderへ移行したので具体的な手順やAWSとの違いをまとめました。
ここで使用したアプリは一度AWSにデプロイしただけでなく、設計やテストといったドキュメントの整備も徹底したものなのでよろしければ是非参考にしてもらえると幸いです。