はじめに
概要
DBを利用するWebアプリケーションにおいて、AWSを使用した本番環境の構築・コンテナ化アプリのデプロイ方法について取り上げます。
前提条件
- Java/Spring Boot環境構築済み
- Docker環境構築済み
- AWSアカウント作成済み
リポジトリ
動作環境
- Windows 11 Home(24H2)
- Java 21
- Maven 3.9.11
- Spring Boot 3.5.4
- MySQL 8.4.5
- Docker 27.3.1
- Docker Desktop 4.36.0
本手順
前編では、開発環境の構築まで完了しました。
後編では、AWS上で本番環境を構築し、アプリケーションをデプロイする方法をまとめていきます。
DB構成
以降の手順では、DB構成は以下の通り進めます。
開発用DB
| 項目 | 内容 |
|---|---|
| サーバ | ローカルマシン(Dockerコンテナ) |
| データベース名 | qiita_spring_ecs_dev |
| ユーザ名 | qiita_spring_ecs_dev_user |
| パスワード | devpassword |
本番用DB
| 項目 | 内容 |
|---|---|
| サーバ | DBサーバ(RDS) |
| データベース名 | qiita_spring_ecs_prod |
| ユーザ名 | qiita_spring_ecs_prod_user |
| パスワード | prodpassword |
環境全体像
1. ネットワークの構築(VPC)
2つのAZにまたがるネットワークを構築します。
パブリックサブネットは2つ、プライベートサブネットは4つ用意します。
CIDR設計は以下の通りです。
| ネットワーク | IPv4アドレス | CIDRブロック |
|---|---|---|
| VPC | 10.0.0.0/16 | 00001010.00000000.00000000.00000000 |
| public subnet 1 (1a) | 10.0.0.0/20 | 00001010.00000000.00000000.00000000 |
| public subnet 2 (1c) | 10.0.16.0/20 | 00001010.00000000.00010000.00000000 |
| private subnet 1 (1a) | 10.0.128.0/20 | 00001010.00000000.10000000.00000000 |
| private subnet 2 (1c) | 10.0.144.0/20 | 00001010.00000000.10010000.00000000 |
| private subnet 3 (1a) | 10.0.160.0/20 | 00001010.00000000.10100000.00000000 |
| private subnet 4 (1c) | 10.0.176.0/20 | 00001010.00000000.10110000.00000000 |
AWSコンソールにログインし、以下の通りVPCを作成します。
| 項目 | 内容 |
|---|---|
| 作成するリソース | VPCなど |
| 名前 | qiita-spring-ecs-rds(自動生成オン) |
| AZの数 | 2 |
| パブリックサブネットの数 | 2 |
| プライベートサブネットの数 | 4 |
| NATゲートウェイ | なし |
| VPCエンドポイント | S3ゲートウェイ |
2. セキュリティグループの作成
以下の通り作成します。
1) ロードバランサー用
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-sg-elb |
| インバウンドルール | 下表の通り |
| タイプ | ポート範囲 | ソース |
|---|---|---|
| HTTP | 80 | 0.0.0.0/0 |
2) ECSサービス用
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-sg-service |
| インバウンドルール | 下表の通り |
| タイプ | ポート範囲 | ソース |
|---|---|---|
| カスタムTCP | 8080 | qiita-spring-ecs-rds-sg-elb |
3) VPCエンドポイント用
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-sg-vpce |
| インバウンドルール | 下表の通り |
| タイプ | ポート範囲 | ソース |
|---|---|---|
| HTTPS | 443 | qiita-spring-ecs-rds-sg-service |
4) DBサーバ用
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-sg-db |
| インバウンドルール | 下表の通り |
| タイプ | ポート範囲 | ソース |
|---|---|---|
| MYSQL/Aurora | 3306 | qiita-spring-ecs-rds-sg-service |
3. 本番用DBの作成(RDS)
1) オプショングループ作成
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-option-group |
| エンジン | mysql |
| メジャーエンジンバージョン | 8.4 |
2) パラメータグループ作成
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-parameter-group |
| エンジンのタイプ | MySQL Community |
| パラメータグループファミリー | mysql8.4 |
3) サブネットグループ作成
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-subnet-group |
| VPC | qiita-spring-ecs-rds-vpc |
| アベイラビリティーゾーン | ap-northeast-1a, ap-northeast-1c |
| サブネット | private subnet 3 (1a), private subnet 4 (1c) |
4) データベース作成
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 作成方法 | 標準作成 |
| エンジンのタイプ | MySQL |
| エンジンバージョン | MySQL 8.4.5 |
| テンプレート | 本番稼働用 |
| 可用性と耐久性 | マルチAZ(2インスタンス) |
| DBインスタンス識別子 | qiita-spring-ecs-prod-db |
| マスターユーザ名 | admin |
| 認証情報管理 | セルフマネージド(パスワードを自動生成:オン) |
| VPC | qiita-spring-ecs-rds-vpc |
| DBサブネットグループ | qiita-spring-ecs-subnet-group |
| パブリックアクセス | なし |
| セキュリティグループ | qiita-spring-ecs-rds-sg-db |
| データベース認証 | パスワード認証 |
| 最初のデータベース名 | qiita_spring_ecs_prod |
| DBパラメータグループ | qiita-spring-ecs-parameter-group |
| オプショングループ | qiita-spring-ecs-option-group |

作成が完了すると、上記メッセージが表示されます。
自動生成としたパスワードですが、上記メッセージの「接続の詳細の表示」から確認できます。
パスワードを確認できるのはこの時だけのようなので、忘れずに控えておきます。
5) ユーザ作成
本番環境での実行時にアプリケーションが使用するユーザを作成します。
手順としては、AWSが提供するCloudShellを利用してRDSへ接続し、管理者ユーザでMySQLにログインして作業を行います。

+ボタンから「Create VPC environment (max 2)」を選択します。
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| Name | qiita-spring-ecs-rds-shell(任意) |
| VPC | qiita-spring-ecs-rds-vpc |
| Subnet | private subnet 3 (1a) |
| Security group | qiita-spring-ecs-rds-sg-service |
RDSを構築したVPC・サブネットに対して、ECSサービス用のセキュリティグループを設定して接続しています。
DBサーバ上のMySQLにログインします。
mysql -u admin -h <作成したデータベースのエンドポイント> -p
パスワードの入力が求められるので、先ほど控えたパスワードを入力します。
ログインできたら、以下コマンドでユーザの作成を行います。
CREATE USER 'qiita_spring_ecs_prod_user'@'10.0.%' IDENTIFIED BY 'prodpassword';
ホスト名については、2つのWebサーバから使用するユーザになるので、それらのアドレスを集約しています。
続いて、本番用のデータベース内に作成する全てのテーブルに対する全ての権限をこのユーザに付与します。
GRANT ALL ON qiita_spring_ecs_prod.* TO 'qiita_spring_ecs_prod_user'@'10.0.%';
本来であれば、本番用のユーザには最小限の権限を付与するべきかと思います。
CloudShellには、今回使用したMySQLクライアント含め、GitやDockerなど開発でよく使用されるツールが事前にインストールされています。
別途必要なツールがあれば、ベースはAmazon Linuxなので、yumコマンドでインストールできます。
4. 本番用DB接続情報登録(Secrets Manager)
本番用DBに接続するために使用するパスワードなどの機密情報は、AWS Secrets Managerに保存し、ECSタスクからはこちらを参照して使用するようにします。
Secrets Managerから、以下の通りシークレットを保存します。
| 項目 | 内容 |
|---|---|
| シークレットのタイプ | Amazon RDS データベースの認証情報 |
| ユーザー名 | qiita_spring_ecs_prod_user |
| パスワード | prodpassword |
| データベース | qiita-spring-ecs-prod-db |
| シークレットの名前 | prod/qiita-spring-ecs-rds/mysql |
5. サンプルアプリケーションの更新
1) 本番用DB接続情報の追加
本番環境で動くアプリケーションから本番用DBへのアクセス用に、prodプロファイルを用意します。
spring.datasource.url=jdbc:mysql://<作成したデータベースのエンドポイント>:3306/${PROD_DB_DATABASE}
spring.datasource.username=${PROD_DB_USERNAME}
spring.datasource.password=${PROD_DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=validate
開発環境同様、接続情報は環境変数を参照していますが、本番環境では.envファイルではなくSecrets Managerから環境変数として取得します。
6. プライベートリポジトリの作成(ECR)
ECRに以下の通りプライベートリポジトリを作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-app |
| ミュータビリティ | Mutable |
| 暗号化設定 | AES-256 |
7. Dockerイメージの作成
1) 本番用イメージ定義作成(Dockerfile)
作成済みのDockerfileに下の2行を追加します。
FROM amazoncorretto:21 AS base
WORKDIR /app
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
COPY .env .env
EXPOSE 8080
FROM base AS dev
EXPOSE 5005
ENTRYPOINT ["java", "-Djava.net.preferIPv4Stack=true", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "/app/app.jar", "--spring.profiles.active=docker"]
FROM base AS prod
ENTRYPOINT ["java", "-jar", "/app/app.jar", "--spring.profiles.active=prod"]
本番環境では、prodステージを指定して作成したイメージを使用します。
こちらは開発環境と異なり、デバッグ機能はなくしており、またSpring Bootのプロファイルはprodを指定しています。
2) イメージのビルド・プッシュ
はじめに、アプリケーションをビルドしておきます。
./mvnw clean package
dbサービスが立ち上がっていない場合ビルドが失敗するので、docker compose up -d dbで立ち上げておきます。
ECRのレジストリに対して認証を行います。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
先ほど作成したDockerfileをもとに、prodステージを指定してDockerイメージをビルドします。
docker build --target prod -t qiita-spring-ecs-rds-app-prod .
最後に、作成したイメージに1.0.0のタグを付け、ECRへプッシュします。
docker tag qiita-spring-ecs-rds-app-prod:latest <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/qiita-spring-ecs-rds-app:1.0.0
docker push <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/qiita-spring-ecs-rds-app:1.0.0
8. VPCエンドポイントの作成
ECSサービスはプライベートサブネットに配置しますが、VPC外部のAWSサービスへアクセスする必要があるため、エンドポイントを作成します。
作成するエンドポイントは以下の5つです。
| エンドポイント | 内容 |
|---|---|
| com.amazonaws.ap-northeast-1.ecr.dkr | Dockerイメージのプル |
| com.amazonaws.ap-northeast-1.ecr.api | Dockerレジストリへの認証 |
| com.amazonaws.ap-northeast-1.s3(Gateway) | Dockerイメージの保存 |
| com.amazonaws.ap-northeast-1.logs | CloudWatch Logsへのログ書き込み |
| com.amazonaws.ap-northeast-1.secretsmanager | シークレットの取得 |
3つ目のs3は、初めの「ネットワークの構築」でVPCと併せて作成しているので、残り4つを作成します。
ecr.dkr
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-vpce-ecr-dkr |
| タイプ | AWSのサービス |
| サービス | com.amazonaws.ap-northeast-1.ecr.dkr |
| VPC | qiita-spring-ecs-rds-vpc |
| サブネット | private subnet 1 (1a), private subnet 2 (1c) |
| セキュリティグループ | qiita-spring-ecs-rds-sg-vpce |
名前・サービス以外の項目は同一の内容で、残り3つのエンドポイントも作成します。
9. ロードバランサーの作成(ELB)
はじめに以下の通りターゲットグループを作成します。
| 項目 | 内容 |
|---|---|
| ターゲットタイプ | IPアドレス |
| 名前 | qiita-spring-ecs-rds-tg |
| プロトコル/ポート | HTTP/8080 |
| VPC | qiita-spring-ecs-rds-vpc |
| ヘルスチェックプロトコル | HTTP |
| ヘルスチェックパス | /users |
ターゲットには何も登録せずに登録とします。
ECSタスク起動時に、ECSがタスクに対してIPアドレスの割り当て・ターゲットグループへの追加を自動で行ってくれるので、ここでは何も登録しなくて大丈夫です。
続いて以下の通りロードバランサーを作成します。
| 項目 | 内容 |
|---|---|
| ロードバランサータイプ | Application Load Balancer |
| 名前 | qiita-spring-ecs-rds-elb |
| スキーム | インターネット向け |
| VPC | qiita-spring-ecs-rds-vpc |
| サブネット | public subnet 1 (1a), public subnet 2 (1c) |
| セキュリティグループ | qiita-spring-ecs-rds-sg-elb |
| プロトコル/ポート/デフォルトアクション | HTTP/80/qiita-spring-ecs-rds-tg |
ロードバランサーのステータスが「アクティブ」となっていることを確認します。
10. ECSの構築
1) クラスターの作成
以下の通り作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-cluster |
| インフラストラクチャ | AWS Fargate (サーバーレス) |
2) IAMロールの作成
続いて、この後作成するタスク定義にアタッチするIAMロールを作成します。

ECSTaskExecutionRoleというロールを作成し、2つのポリシーをアタッチします。
各ポリシーの役割は以下の通りです。
| ポリシー | 役割 |
|---|---|
| AmazonECSTaskExecutionRolePolicy | ECRからイメージ取得、CloudWatch Logsへログ出力 |
| AmazonECSSecretsManagerAccessPolicy | Secrets Managerからシークレット取得 |
実際に作成していきます。
AmazonECSSecretsManagerAccessPolicy
IAMポリシーから以下の通り作成します。
| 項目 | 内容 |
|---|---|
| アクセス許可 | 以下の通り |
| 名前 | AmazonECSSecretsManagerAccessPolicy |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"<Secrets Managerに登録したシークレットのARN>"
]
}
]
}
ECSTaskExecutionRole
IAMロールから以下の通り作成します。
| 項目 | 内容 |
|---|---|
| エンティティタイプ | AWSのサービス |
| サービスまたはユースケース | Elastic Container Service |
| ユースケース | Elastic Container Service Task |
| 許可ポリシー | AmazonECSTaskExecutionRolePolicy, AmazonECSSecretsManagerAccessPolicy |
| 名前 | ECSTaskExecutionRole |
3) タスク定義の作成
以下の通りタスク定義を作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-task |
| 起動タイプ | AWS Fargate |
| タスク実行ロール | ECSTaskExecutionRole |
コンテナは以下の通り1つ作成します。
| 項目 | 内容 |
|---|---|
| 名前 | qiita-spring-ecs-rds-app |
| イメージURI | <作成したECRリポジトリのURI>:1.0.0 |
| 必須コンテナ | はい |
| プライベートレジストリ認証 | オフ |
| コンテナポート | 8080 |
| プロトコル | TCP |
| アプリケーションプロトコル | HTTP |
また、環境変数を以下の通り設定します。
| キー | 値のタイプ | 値 |
|---|---|---|
| PROD_DB_DATABASE | ValueFrom | Secrets Managerに登録したシークレットのARN:dbname:: |
| PROD_DB_USERNAME | ValueFrom | Secrets Managerに登録したシークレットのARN:username:: |
| PROD_DB_PASSWORD | ValueFrom | Secrets Managerに登録したシークレットのARN:password:: |
4) サービスの作成
先ほど作成したクラスターから、サービスを作成します。
| 項目 | 内容 |
|---|---|
| タスク定義ファミリー | qiita-spring-ecs-rds-task |
| リビジョン | 1(最新のもの) |
| サービス名 | qiita-spring-ecs-rds-service |
| コンピューティングオプション | 起動タイプ |
| 起動タイプ | FARGATE |
| プラットフォームバージョン | LATEST |
| 必要なタスク | 2 |
| リバランス | オン |
| VPC | qiita-spring-ecs-rds-vpc |
| サブネット | private subnet 1 (1a), private subnet 2 (1c) |
| セキュリティグループ | qiita-spring-ecs-rds-sg-service |
| パブリックIP | オフ |
| ロードバランサーの種類 | Application Load Balancer |
| コンテナ | qiita-spring-ecs-rds-app 8080:8080 |
| ロードバランサー | qiita-spring-ecs-rds-elb |
| リスナー | HTTP:80 |
| ターゲットグループ | qiita-spring-ecs-rds-tg |

5分ほど待ち、作成されたサービスを確認すると、ステータスはアクティブとなり、2件のタスクが実行中であることが分かります。
11. 本番用テーブルの作成
1) マイグレーション実行
先ほどのサービス作成によりタスクが起動し、アプリケーションが実行されました。
これにより、本番環境でもFlywayによるマイグレーションが実行されているはずです。
再びCloudShellからRDSに接続し、テーブルが作成されていることを確認します。
mysql -u qiita_spring_ecs_prod_user -h <作成したデータベースのエンドポイント> -p qiita_spring_ecs_prod
ログインできたら、以下コマンドでテーブルを確認します。
show tables;
+---------------------------------+
| Tables_in_qiita_spring_ecs_prod |
+---------------------------------+
| flyway_schema_history |
| users |
+---------------------------------+
2 rows in set (0.003 sec)
正常に作成されていることが分かります。
2) 初期データ登録
この後の動作確認で使用するため、本番用テーブルに以下の通りデータを登録します。
INSERT INTO users (
name,
age
)
VALUES
('本番太郎', 70),
('本番花子', 80),
('本番一郎', 90);
12. 動作確認
以下URLにアクセスします。
http://<ロードバランサーのDNS名>/users

無事動いています!
後片付け
本手順において、AWSサービスに対する必要以上の課金を防ぐ必要がある場合、最低限行う対応は以下の通りです。
- ロードバランサーの削除
- ECSサービスの削除
- ECSタスク定義の削除
- ECRにプッシュしたイメージの削除
- VPCエンドポイント(ecr.dkr, ecr.api, logs, secretsmanager)の削除
- RDSデータベースの削除
- Secrets Managerの削除
おわりに
以上で、DBを利用したWebアプリケーションにおける、AWSを使用した本番環境の構築・コンテナ化アプリのデプロイが完了しました。
サーバは三層構成とし、WebサーバとDBサーバはプライベートサブネットに配置することで外部から隠蔽しています。
両サーバはマルチAZ構成でサーバを冗長化し、ロードバランサーによるトラフィックの分散、およびDBのレプリケーションを実現しました。
アプリケーションのDockerイメージは、マルチステージビルドを利用して、開発環境用・本番環境用を切り替えられるようにしています。
また、開発環境同様、Spring Bootのプロファイルを利用して本番用DBに接続しています。
接続情報はSecrets Managerで安全に管理しています。
最後に、他にも基礎的なAWSインフラ構築関連の記事を書いているので、よければご参照ください。



