SpringBoot/React/MySQLの簡単なアプリをAWS EC2にデプロイする

---
# ゴール
* 開発環境でアプリは作るけど、**どうやって本番環境に公開していいのかわからん！**
を
開発環境でアプリは作れるし、**泥臭いけどアプリを公開することができる！**
に変えることができる！

* よくあるバックエンド（Java SpringBoot）・フロントエンド（React）・DB（MySQL）の
構成で作成した簡単なアプリを公開し、自宅のPCからアクセスできるようになる

# この記事でやること

* SpringBootを使ったJavaアプリケーションをEC2上に立ち上げる
* JavaアプリケーションからAmazonRDS（MySQL）に接続する
* ReactアプリケーションをEC2上に立ち上げる

# この記事でやらないこと

* アプリケーションのソースコードの解説

## 手順（かかる時間）

1. 全体構成図（読むだけ。5分）
1. ローカルで動くSpringBoot/React/MySQLのサンプルアプリの作成
1. VPCの構築（20分程度）
1. RDSインスタンスの作成（5分）
1. EC2インスタンスの作成（5分）
1. EC2インスタンス上でアプリを起動（10分）
1. 動作確認

計：1時間～程度

# 1. 全体構成図

ざっくり以下の構成です！

* 東京リージョン内の２つのアベイラビリティーゾーンにWebサーバ、APサーバ、DBサーバを構成
* EC2をWebサーバ、APサーバとして、RDSをDBサーバとして利用しています
![全体構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/b20dd1d2-1058-6cd2-b327-d0fbe987c0b3.png)

# 2. ローカルで動くSpringBoot/React/MySQLのサンプルアプリの作成

## サンプルアプリのソースコード
* SpringBootについて
    * サンプルソースコード: [こちら](https://github.com/hims0k/spring-sample)
    * application.properties を `src/main/resources` 配下に配置

```properties:src/main/resource/application.properties
spring.datasource.url=jdbc:mysql://${MySQLのエンドポイント}:3306/{DBName}?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=${ルートユーザ}
spring.datasource.password=${ルートユーザのパスワード}
server.servlet.context-path=/react-sample-app/
```

* Reactについて
    * サンプルソースコード: [こちら](https://github.com/hims0k/react-ts-sample2)

* MySQLについて
    * Dockerで構築しました
    * データを用意するのが面倒だったので以下を利用しています  
[MySQL:world_database](https://dev.mysql.com/doc/index-other.html)

* その他
    * Nginxを利用してプロキシパスを設定しています

```conf:nginx.conf
server {
    listen       80;
    server_name  localhost;

    // Reactへのアクセス
    location / {
        proxy_pass http://localhost:3000;
    }

    // SpringBootへのアクセス
    location /react-sample-app {
        proxy_pass http://localhost:8080/react-sample-app;
    }
}
```

## 動作確認

* [http:localhost/](http:localhost/) にアクセス
* 以下のような画面が表示され、国コード（完全一致）を入力すると、それに伴うデータが表示されればOK！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/89f12553-7c0f-c367-225f-305aaf628f5b.png)

# 3. VPCの構築

* 開発環境で動作したらいよいよ公開準備です
* VPC内に３つのサブネットを構築します

|   Subnet         |       AZ          |  IPv4 CIDRブロック  |       用途           |
|:----------------:|:-----------------:|:------------------:|:--------------------:|
| PublicSubnet1   |   ap-northeast-1a |   10.0.0.0/24      |   Web/APサーバ用       |
| PrivateSubnet1  |   ap-northeast-1a |   10.0.1.0/24      |   DBサーバ用           |
| PrivateSubnet2  |   ap-northeast-1c |   10.0.2.0/24      |   DBサーバ用（冗長構成）|

* パブリックサブネット　：Internet Gatewayに接続しているサブネット（インターネットにつながる）
* プライベートサブネット：Internet Gatewayに接続していないサブネット（インターネットにつながらない）

![VPC構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/b302e20b-e948-8551-3c9d-d9030c9b8622.png)

## 手を動かしてVPC構成する
<details><summary>具体的な手順は以下</summary><div>

### VPCの作成

1. [VPCホーム](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#CreateVpc:) にアクセス
1. VPCの作成 から以下の画像のように入力（名前タグは適当に、今回IPv4CIDRブロックは `10.0.0.0/21` に）
1. VPCを作成
![VPCの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/497e5991-5889-fdb1-0c92-d7bde0d29285.png)

### サブネットの作成

1. 左ペインから[サブネット](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#subnets:)を選択 -> サブネットの作成
1. 画像のように入力　-> サブネットを作成
（３つとも同じ要領で作成）
![サブネットの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/2e6e5dfe-06ac-c5a2-cb32-f27bd359fff8.png)

### Internet Gatewayの作成

* Internet Gateway：VPCとインターネットの間を通信可能にする  
[引用元：公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_Internet_Gateway.html)

1. 左ペインから[インターネットゲートウェイ](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#igws:)を選択 -> インターネットゲートウェイの作成
1. 名前をつけて -> インターネットゲートウェイの作成

## ルートテーブルの作成

* ルートテーブル：VPC内のルーターにおいて、ネットワーク通信の送信先を制御する（例：通信をネットワークに向ける）  
[引用元：公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_Route_Tables.html)

1. **パブリックサブネット用のルートテーブルの作成**
    1. PublicSubnet1 を選択 -> 詳細画面の下ペイン「ルート」を表示  
    （このままではインターネットにつながっていないので、通信をInternetGatewayに向ける）
    1. ルートを編集 -> ルートを追加
    1. 送信先 `0.0.0.0/0`, ターゲット `インターネットゲートウェイ` -> 変更を保存
    1. ルートテーブル詳細画面の「サブネットの関連付け」 -> PublicSubnet1を紐付ける
1. **プライベートサブネット用のルートテーブルの作成**  
    （VPCのメインルートテーブルはパブリック用で利用したので、新たにイチから作成する）
    1. 左ペインから[ルートテーブル](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#RouteTables:)を選択 -> 名前をつける、VPCを選択 -> ルートテーブルを作成
    1. 作成したルートテーブルを選択 -> 詳細画面下の「ルート」表示
    1. 通信がインターネットゲートウェイに向いていないことを確認
    1. サブネットの関連付けからPrivateSubnet1,2を紐付ける

| ルートテーブル           | 明示的なサブネットの関連付け   | 
| :----------------------: | :----------------------------: | 
| パブリックサブネット用   | PublicSubnet1                  | 
| プライベートサブネット用 | PrivateSubnet1, PrivateSubnet2 | 

以下の図のような構成まで作成完了
![ルートテーブルの作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/60bbf250-2b62-ec1d-d88c-78bb05cbd662.png)


### セキュリティグループの作成

* セキュリティグループ：インスタンスの仮想ファイアウォール。通信のイン/アウトをコントロールする  
[引用元：公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_SecurityGroups.html)

1. **Web/APサーバインスタンス用のセキュリティグループの作成**（アウトバウンドはとりあえずデフォルトでOK）
![インバウンドルール＿WEBAP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/91554f2d-3250-6c78-d21a-807a94904ce0.png)

2. **DBサーバインスタンス用のセキュリティグループの作成**（アウトバウンドはとりあえずデフォルトでOK）
![インバウンドルール＿DB.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/c8cf9d1f-15db-ed94-d033-04d55de5ccdb.png)

ようやく終わり。ここまでで以下の構成のように仕上がっている
![VPC構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/3f8f58ed-022c-1233-01ea-746eb87d414d.png)

</div></details>

# 4. RDSインスタンスの作成

* application.propertiesもパッケージする都合上、エンドポイント・ルートユーザ周りが必要
* そのため、先にRDSインスタンスを作成していきます
* 概略は以下

| 項目 | 値 |
|:-|:-|
| DBエンジン  | MySQL Community |
| VPC  |  VPCの構築で作成したもの |
| サブネットグループ  | 新たに作成（PrivteSubnet1, PrivateSubnet2をグルーピング）  |
| セキュリティグループ  | VPCの構築で作成したDBサーバ用のセキュリティグループ  |
| インスタンスクラス  | db.t2.micro  |

## 手を動かしてRDSインスタンスを作成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/55a05615-b3f0-4d23-9179-17d54a0cbe3a.png)

<details><summary>具体的な手順は以下</summary><div>

1. [Amazon RDS](https://ap-northeast-1.console.aws.amazon.com/rds/home?region=ap-northeast-1#)にアクセス
1. 左ペインから **サブネットグループ** を選択 -> VPCの構築で作成した `PrivateSubnet1` `PrivateSubnet2` を選択  
※RDSインスタンスの作成にはサブネットグループが必須です
1. 左ペインから **データベース** を選択 -> データベースの作成
1. **DBエンジンの選択**：（今回は）MySQL
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/f38a2dfa-2351-a65c-62f5-19317c16d087.png)
1. **テンプレート** 開発/テスト, 無料利用枠 どちらでもOKです
1. **設定** DBインスタンス識別子: なんでもOK, マスタユーザー名/マスターパスワードを指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/369f64f3-46e7-6781-b83a-f98bdd479963.png)
1. **DBインスタンスクラス** 今回は `db.t2.micro` を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/604660dc-fd1e-a110-7ac7-c5862659eaeb.png)
1. **ストレージ** はデフォルトでOK
1. **可用性と耐久性** 今回は `スタンバイインスタンスを作成する` で試していますが、どちらでもOK
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/8286b3d7-0883-e0dc-b85f-e98739af7a96.png)
1. **接続** 以下の画像のように、今まで構築してきたものを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/1331a11b-24ef-5c8b-81b3-d7ea6ece7e70.png)
1. **概算月間コスト** を確認 -> データベースの作成（金額が気になるようなら「無料利用枠」でOK）

</div></details>

# 5. EC2インスタンスの作成（5分）

* アプリをデプロイするためのEC2インスタンスを作成します × 2（Webサーバー・APサーバー）
* 概略は以下

| ステップ | 項目 | 値 |
|:-|:-|:-|
| AMIの選択  | AMI  | Amazon Linux 2  |
| インスタンスタイプの選択  | インスタンスタイプ  | t2.micro  |
| インスタンスの設定  | ネットワーク  | 作成したVPC  |
| （同上）  | サブネット  | PublicSubnet1  |
| （同上）  | 自動割り当てパブリックIP  | 有効  |
| ストレージの追加  | -  | - （初期設定でOK）  |
| タグの追加  | Name  | WebServer, APServer  |
| セキュリティグループの設定  | セキュリティグループ  | 作成したWeb・APサーバ用のセキュリティグループ  |

## 手を動かしてEC2インスタンスを作成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/99ea09e6-d592-c356-8f27-28019bbe2d95.png)

<details><summary>具体的な手順は以下</summary><div>

1. [EC2](https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Instances:)へアクセス
1. インスタンスを起動
1. **Amazon マシンイメージ（AMI）**： Amazon Linux 2
1. **インスタンスタイプの選択**: t2.micro
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/c37e1dce-7f53-e5b8-eb33-79ef239a0f01.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/ea11b6ce-b80f-9387-a97f-bb10fd6b4bfe.png)
1. **ストレージの追加**: 初期設定でOK
1. **タグの設定**： それぞれ「Name: WebServer, APServer」
1. **セキュリティグループ**： 作成したWeb・APサーバ用のセキュリティグループ
1. key-pair のダウンロード
1. 確認と作成（作成完了まで待ち）
1. ステータスチェック: 2/2 のチェックに合格しました で作成完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/ed04531a-d358-cc10-7150-07651c3e614b.png)

</div></details>

# 6. EC2インスタンス上でアプリを起動

## MySQLにデータをサンプルデータ投入

* サンプルデータ用SQLをWebサーバ or APサーバにコピーする
* [MySQLサンプルデータ](https://dev.mysql.com/doc/index-other.html)

```bash
# コピーする
$ scp -i <key_pair> <world.sqlをダウンロードしたパス>/world.sql ec2-user@<WebサーバのPublicIP>:~/<お好きなところへ>
```

* RDSインスタンスには直接SSH接続できないので、Webサーバを経由する

```bash
# WebサーバにSSH接続
$ ssh -i <key_pair> ec2-user@<WebサーバのPublicIP>

# MySQL のインストール
$ sudo yum install -y mysql

# RDSインスタンスに接続
$ mysql -u <マスターユーザー> -h <RDSインスタンスのエンドポイント> -p
password: <マスターユーザーのパスワード>
```

* データ投入

```sql
> SOURCE ~/world.sql
```

## Reactアプリケーションをデプロイ

* 以下の記事を参考にデプロイします  

https://qiita.com/longtime1116/items/18553e43bfb44cbc9d81

* アプリのビルド・起動は以下を実行（productionモードで起動するため。リソースの読み込み方法が異なります）

```bash
# yarn のインストール
$ npm -g install yarn

# アプリケーションのビルド
$ yarn build

# アプリケーションの立ち上げ
$ yarn global add serve
$ serve -s build
```

* Nginxの設定

```conf:/etc/nginx/nginx.conf
server {
    # omit
    # ポート5000で起動するため
    location / {
            proxy_pass http://localhost:5000;
    }

    # APサーバに向ける。PublicIPでもいいが、PrivateIPを指定
    location /react-sample-app/ {
            proxy_pass http://{APサーバーのPrivateIP}:8080/react-sample-app/;
    }
```

* Nginxの再起動

```bash
$ sudo systemctl restart nginx.service
```


## SpringBootアプリケーションをデプロイ

* RDSのエンドポイント/ユーザ/パスワードをapplication.properties に記載します

* アプリケーションのビルド

```gradle
// ビルドして実行可能Jarにパッケージング
$ grade buildJar
```

* JarをEC2にコピー

```bash
$ scp -i <key-pair> <spring-sample-0.0.1-SNAPSHOT.jarのパス> ec2-user@<APサーバのPublicIP>:~/<お好きなところへ>
```

* EC2にJavaをインストール

```bash
$ sudo yum install java-11-amazon-corretto
# Version確認
$ javac --version
javac 11.0.11
$ java --version
openjdk 11.0.11 2021-04-20 LTS
OpenJDK Runtime Environment Corretto-11.0.11.9.1 (build 11.0.11+9-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.11.9.1 (build 11.0.11+9-LTS, mixed mode)
```

* アプリケーションの実行

```bash
# spring-sample-0.0.1-SNAPSHOT.jar をコピーした階層で
$ java -jar spring-sample-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.2)
```

## おさらい

* 構築完了です！
* はじめに記載した以下の図のように構成されています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/3f4d3cf2-3e02-ac63-bcf9-b4de6d783b14.png)

# 動作確認

* [http://{WebサーバのIP}/](http://{WebサーバのIP}/) にアクセスすると、以下の画面が表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239326/caed08b4-cabf-f0f9-4a6b-92395ab7c9e8.png)

* 超泥臭いですが、開発環境で実行したものをAWS上で動作させることができました
* （このままの構成・デプロイ方法ではないと思いますが）本番環境でどのようにアプリが動いているのかを掴むきっかけになれば幸いです。

以上です。



# 参考文献

この記事は以下の情報を参考にして執筆しました。
- [React/Next.jsアプリケーションを作成し、AWS EC2を使って本番環境にデプロイするまで
](https://qiita.com/longtime1116/items/18553e43bfb44cbc9d81)
- [MySQL Other MySQL Documentation Example Databases](https://dev.mysql.com/doc/index-other.html)
___
この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。
https://aws-cloud-tech.com