プログラミング

SpringBoot×Reactを2つのEC2インスタンスにデプロイする方法

この記事は約13分で読めます。

こんにちは。じゅんです。

前回、1つのEC2インスタンスにSpring BootとReactの簡単メモアプリをデプロイしました。
今回はそのメモアプリを2つのEC2インスタンスにデプロイします。
2つのインスタンス間で通信を行う上で必要な設定を学んでいきたいと思います。

これまでの作業内容については以下の記事にまとめています。

全体像

今回もNginxをリバースプロキシとして使います。

EC2インスタンスを作成する

まずWebサーバーとしてReactを起動させるEC2インスタンスを作成します。

項目名意味
名前とタグ > 名前simple-memo
-web-server(任意)
インスタンスの名前
キーペア (ログイン) > キーペア名 > 新しいキーペアを作成simple-memoインスタンスに接続するときの認証情報
APサーバーと同じものを使用
ネットワーク設定 > 右上編集ボタン > ファイアウォール(セキュリティグループ) > セキュリティグループを作成するsimple-memo-web-sg
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 1タイプ:ssh
ソース:3.112.23.0/29
インスタンスを操作するための接続(ssh)をEC2 Instance Connectのみに制限(AWSコンソール上からのみ接続可)
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 2タイプ:HTTP
ソースタイプ:0.0.0.0/0
Nginxへの外部からのアクセスを解放
他の項目はデフォルトのままです。

次にAPサーバーとしてSpring Bootを起動させるEC2インスタンスを作成します。

項目名意味
名前とタグ > 名前simple-memo-ap-server(任意)インスタンスの名前
キーペア (ログイン) > キーペア名simple-memo
RSA
.pem
インスタンスに接続するときの認証情報
ネットワーク設定 > 右上編集ボタン > ファイアウォール(セキュリティグループ) > セキュリティグループを作成するsimple-memo-ap-sg(任意)
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 1タイプ:ssh
ソース:3.112.23.0/29
インスタンスを操作するための接続(ssh)をEC2 Instance Connectのみに制限(AWSコンソール上からのみ接続可)
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 2タイプ:ssh
ソースタイプ:マイIP
jarファイル転送時にscpコマンドでssh接続するため自身の端末のみ許可
ネットワーク設定 > 右上編集ボタン > インバウンドセキュリティグループのルール > セキュリティグループルール 3
タイプ:カスタムTCP
ポート範囲:8080ソースタイプ:WebサーバーのパブリックIP
ReactからのAPI通信を受け入れるため
他の項目はデフォルトのままです。

Nginxの設定

今回もNginxをリバースプロキシとして使用します。
nginxのインストールと設定についてはこちらの記事を参考にしてください。

外部からのアクセスをNginxがポート80で受け取り、Reactが起動しているポート3000に代わりにリクエストを送ります。

Reactからのhttp://(WebサーバのパブリックIP)/api/へのリクエストをNginxが受け取り、http://(APサーバのパブリックIP):8080/apiに代わりにリクエストを送ります。

/etc/nginx/nginx.conf
server {
    listen       80;
    server_name  localhost;

    # 外部 → Nginx → React
    location / {
        proxy_pass http://localhost:3000;
    }

    # React → Nginx → APサーバのSpring Boot
    location /api {
        proxy_pass http://(APサーバのパブリックIP):8080/api;
    }
}

nginxを起動します。

Zsh
sudo systemctl start nginx

設定ファイルの導入

開発環境と本番環境では、APIのリクエスト元とリクエスト先が異なります。

環境リクエスト元リクエスト先
開発環境http://localhost:3000http://localhost:8080/api
本番環境http://(WebサーバーのIPアドレス)http://(WebサーバーのIPアドレス)/api

リクエスト元の情報はSpringBootの@CrossOriginでCORS問題に対応しており、
リクエスト先の情報はReactのリクエスト作成で使用しています。

設定ファイルを導入して環境を切り替えやすくします。

SpringBoot

設定ファイルの作成

Spring Bootでは接続情報などを記載する目的でapplication.propertiesという設定ファイルを使用します。

application.propertiesには共通環境(開発環境と本番環境の両方)で使用する設定を記述します。

開発環境や本番環境用の設定はapplication-dev.propertiesapplication-prod.propertiesという設定ファイルを作成して記述します。

src/main/resources/application-dev.properties
# 開発環境のAPI接続元
api.endpoint=http://localhost:3000
src/main/resources/application-prod.properties
# 本番環境のAPI接続元
api.endpoint=http://(WebサーバのIPアドレス)

環境の切り替えはapplication.propertiesに記述します。

src/main/resources/application.properties
# アプリケーションの名前
spring.application.name=simple-memo
# 使用するプロパティファイルの指定(追記)
spring.profiles.active=dev
【ミニコラム: 設定ファイルの名称】
今回、devとprodを開発環境と本番環境としましたが、この部分は任意です。
なのでapplication-prd.propertiesとしても良い訳です。
その場合、application.propertiesでactive=prdと対応させる必要はあります。

設定ファイルの読み込み

環境によって読み込む設定ファイルを動的に切り替えるために、WebConfig.javaを使用します。

@CrossOriginの引数には設定ファイルの値を埋め込むことはできないため、WebConfig.javaで動的に構成します。

WebConfig.javaはアプリケーションが起動する際に自動的に読み込まれる設定クラスです。

WebConfig.java
package com.example.simplememo.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

  private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);

  @Value("${api.endpoint}")
  private String apiEndpoint;

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    // apiEndpointの値をログに出力して確認
    logger.info("Configured API Endpoint for CORS: {}", apiEndpoint);

    registry.addMapping("/api/**")
            .allowedOrigins(apiEndpoint)
            .allowedMethods("GET", "POST", "PUT", "DELETE");
  }
}

application.propertiesはアプリケーション起動時に自動的に読み込まれるのでWebConfig.javaで指定する必要はありません。

設定クラスを作成すると、コントローラクラスで個別にCORS設定をする必要がなくなります。

Java
// @CrossOrigin(origins="localhost:8080") // 不要になる
public class SimpleMemoController {
...
}

動作確認

上記の修正を行い、開発環境で実行します。

Loggerを使用して起動直後のコンソールに接続先を出力するようにしました(下赤枠)。

上の赤枠にはapplication.propertiesで設定したactiveの値が表示されます。

application.propertiesをprodに変えて再実行してみます。

Screenshot

適切に変更されていることが確認できます。

React.envの導入

Reactでは接続情報などを記載する目的で.envファイルをします。

開発環境と本番環境の設定用に.env.development.env.productionをルートディレクトリに作成します。

.env.development
REACT_APP_API_BASE_URL=http://localhost:8080
.env.production
REACT_APP_API_BASE_URL=(webサーバのIPアドレス)

環境変数名はREACT_APP_で始める必要があります。

どちらのファイルを読み込むかはReact実行方法で切り替えます(後述)。

設定ファイルの読み込み

process.env.REACT_APP_XXXとすることで.envファイルの内容を読み込みます。

src/App.tsx
// const response = await fetch("http://localhost:8080/api/memos");
const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/api/memos`);

動作確認

開発環境用の環境変数を確認するためにnpm run startで実行します。

ブラウザの開発者ツールでURLが正しいことを確認します。

本番用の環境変数を確認するために以下のコマンドを実行します。

Zsh
npm run build        # 実行ファイルを作成
npm install -g serve # 軽量サーバーを提供するパッケージ
serve -s build       # ビルドされた静的ファイルを軽量サーバーで実行

同じくブラウザの開発者ツールでURLが.env.productionから取得できていることを確認します。

これでコードを変更せずに環境変数を切り替えることができました。

Spring BootをAPサーバーにデプロイ

Spring Bootのみデプロイする方法はこちらで詳しくまとめています。

開発環境からjarファイルを転送したら、以下のコマンドでjarファイルを実行します。

Zsh
java -jar ~/simple-memo-0.0.1-SNAPSHOT.jar

実行できたらcurlコマンドで動作確認します。

Zsh
# メモを作成
$ curl -s -X POST -H "Content-Type: application/json" -d 'サンプル0' http://localhost:8080/api/memos
0

# メモ一覧を表示
$ curl -s -X GET http://localhost:8080/api/memos | jq
[
  {
    "id": 0,
    "content": "サンプル0",
    "createdAt": "2024/05/29 22:01:56",
    "updatedAt": "2024/05/29 22:01:56"
  }
]

コマンドの詳細については以下の記事を参考にしてください。

ReactをWebサーバーにデプロイする

Reactのみデプロイする方法はこちらで詳しくまとめています。

GitからReactアプリをクローンしたら、ビルドして起動してみます。

Zsh
cd simple-memo-frontend/   # 作業ディレクトリに移動
npm install                # package.jsonに記載されたパッケージをインストール
npm run build
sudo npm install -g serve  # 軽量サーバーを提供するパッケージ
serve -s build             # ビルドされた静的ファイルを軽量サーバーで実行

WebサーバーのパブリックIPアドレスにブラウザから接続します。

接続できました!

項目
APサーバ > セキュリティグループWebサーバのパブリックIPからのHTTPアクセスを許可しているか
Nginx > nginx.confAPI通信のリクエスト先がhttp://(APサーバのパブリックIP):8080/api
SpringBoot > application.propertiesspring.profiles.activeがprod
SpringBoot > application-prod.propertiesapi.endpointがWebサーバのパブリックIP
React > .env.productionREACT_APP_API_BASE_URLがWebサーバのパブリックIP

まとめ

以上がSpringBootとReactを別々にEC2インスタンスに起動して通信する方法でした。

実は開発環境に作成した10日前からこのデプロイに取り組んでいたのですが思ったより時間がかかってしまいました。

1つのEC2インスタンスに起動することにして問題を切り分けたことでNginxの仲介役としての使い方を学ぶことができました。

問題の切り分けは大事ですね。引き続き勉強していきます!

参考文献

application.propertiesについて

Spring Boot解説第7回(開発環境編:application.propertiesについて) - Qiita
こんにちは!株式会社情創 技術開発局三度の飯より焼肉が好きな@YAKINIKUです!今回はapplication.propertiesについて解説します。#application.prope…
Spring Boot の小径 第4歩 Spring Boot 匍匐前進
Spring-MVCの散歩道 EclipseによるMavenプロジェクトの作り方を解説するページです

React .envについて

Reactで環境変数をつかってAPIの向き先を変更する【React, 環境変数】
結論 .envまたは.env.***ファイルにREACT_APP_***の名前で定義する ソースコード内でpr
Reactで環境変数を読み込む【開発・本番で切り替え可能】 | RalaCode
「Reactで環境変数を使いたい」という方向けの記事です。「開発環境」と「本番環境」で使い分けて取得することが可能となっており、このあたりも含めて解説します。

コメント