はじめに
「コンソールで40〜50分かかったALB + EC2(Tomcat) + RDS環境をコードで再現したい」——それを実現するのが AWS CloudFormation です。
この記事では、template.yaml 1ファイルにVPC・サブネット・セキュリティグループ・IAMロール・SSM Parameter Store・RDS・ALB・ターゲットグループ・EC2(約27リソース)を定義し、コマンド1本で3層構成を一括構築するハンズオンを紹介します。コンソール版(AWSコンソール版ハンズオン)と全く同じ構成をCloudFormationで自動化します。
ローカル環境(VSCode)
├── template.yaml(約27リソースを定義)
└── AWS CLI コマンド
↓ スタック作成(コマンド1本)
AWS環境
└── VPC(10.0.0.0/16)
├── パブリックサブネット1(10.0.1.0/24 / AZ-a)← ALB + EC2
├── パブリックサブネット2(10.0.2.0/24 / AZ-c)← ALB(2AZ必須)
├── プライベートサブネット1(10.0.3.0/24 / AZ-a)← RDS配置
├── プライベートサブネット2(10.0.4.0/24 / AZ-c)← DBサブネットグループ用
├── ALB SG → EC2 SG → RDS SG(3段階のSG-to-SG制御)
├── SSM Parameter Store: /my/alb/db-password
├── RDS MySQL 8.0(db.t3.micro)
├── ALB(HTTP:80 → ターゲットグループ → EC2:8080)
└── EC2(t2.micro / Tomcat / UserDataで自動セットアップ)このハンズオンで体験できること:
- ALB・ターゲットグループ・リスナーを
template.yamlで一括定義する方法 TomcatVersionパラメータでTomcatのバージョンを切り替える設計- ALBの2AZ要件をCloudFormationでどう表現するか
delete-stack1本でALB・RDS・IAMなど27リソースを自動削除
このハンズオンの特徴:
- コンソール版では40〜50分・13ステップかかった作業が、コマンド1本(RDS待ち15〜20分込み)で完了
delete-stack1本でALBのリスナー含む全リソースの依存関係を自動解決して削除
この記事は AWSコンソール版ハンズオン の比較記事です。
コンソール版でALB・ターゲットグループ・ヘルスチェックを視覚的に学んだ後に読むと理解が深まります。
-->
CloudFormation vs コンソール:どれだけ違うか
| 比較項目 | CloudFormation | コンソール(手動) |
|---|---|---|
| VPC + サブネット4つ + IGW + ルートテーブル | template.yaml に定義済み | 約20〜30分・複数画面 |
| SG 3つ(ALB/EC2/RDS)の作成 | template.yaml に定義済み | 3画面で個別設定 |
| ALB + TG + Listener の作成 | template.yaml に定義済み | 3つの画面で設定 |
| Tomcat UserDataの貼り付け | template.yaml に組み込み済み | 手動コピペ |
| 全体のデプロイ | コマンド1本(RDS待ち15〜20分) | 約40〜50分 |
| 削除 | delete-stack 1本(自動解決) | 13ステップ手動 |
| 再現性 | 高い(何度でも同じ構成を再現可能) | 低い(手順漏れのリスク大) |
| バージョン管理 | Gitで管理可能 | 不可 |
キーワード解説
| 用語 | 意味 |
|---|---|
| CloudFormation | AWSが提供するIaC(Infrastructure as Code)サービス |
| スタック | CloudFormationが管理するリソースのまとまり。今回は約27リソースを1スタックで管理 |
| AWS::ElasticLoadBalancingV2::LoadBalancer | ALB本体を定義するリソース型 |
| AWS::ElasticLoadBalancingV2::TargetGroup | ターゲットグループを定義するリソース型 |
| AWS::ElasticLoadBalancingV2::Listener | ALBのリスナー(受付ルール)を定義するリソース型 |
| DeletionPolicy: Delete | スタック削除時にRDSも確実に削除する設定 |
| CAPABILITY_NAMED_IAM | 名前付きIAMリソースを含むスタックの作成に必要なフラグ |
前提条件
| ツール | 確認コマンド |
|---|---|
| AWS CLI v2 | aws --version |
AWS認証確認:
aws sts get-caller-identity構築されるリソース(約27個)
| カテゴリ | リソース | 論理ID |
|---|---|---|
| ネットワーク | VPC | VPC |
| ネットワーク | インターネットゲートウェイ | InternetGateway + IGWAttachment |
| ネットワーク | パブリックサブネット × 2 | PublicSubnet1 + PublicSubnet2 |
| ネットワーク | プライベートサブネット × 2 | PrivateSubnet1 + PrivateSubnet2 |
| ネットワーク | パブリックルートテーブル | PublicRouteTable + PublicRoute + PublicSubnet1RTA + PublicSubnet2RTA |
| ネットワーク | プライベートルートテーブル | PrivateRouteTable + PrivateSubnet1RTA + PrivateSubnet2RTA |
| ネットワーク | S3 VPC Gateway Endpoint | S3VPCEndpoint |
| セキュリティ | ALBセキュリティグループ | ALBSecurityGroup |
| セキュリティ | EC2セキュリティグループ | EC2SecurityGroup |
| セキュリティ | RDSセキュリティグループ | RDSSecurityGroup |
| IAM | EC2ロール + インスタンスプロファイル | EC2Role + EC2InstanceProfile |
| パラメータ | SSM Parameter Store | DBPasswordParam |
| データベース | RDS DBサブネットグループ | DBSubnetGroup |
| データベース | RDS MySQLインスタンス | RDSInstance |
| ロードバランサー | ターゲットグループ | TargetGroup |
| ロードバランサー | ALB本体 | ALB |
| ロードバランサー | リスナー | ALBListener |
| コンピュート | EC2インスタンス | EC2Instance |
template.yaml(完全版)
このファイルをそのまま使ってハンズオンを実施できます。C:\my-aws\aws-learning-projects\alb-ec2-rds\template.yaml として保存してください。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ALB + EC2(Tomcat) + RDS 3-tier architecture hands-on (Phase 2-2)'
Parameters:
EmployeeId:
Type: String
Default: '123456'
Description: Employee ID used as a prefix for resource names
KeyName:
Type: String
Default: 'my-alb-key'
Description: Name of an existing EC2 KeyPair
MyIP:
Type: String
Default: '203.0.113.1/32' # [IMPORTANT] Replace with your own IP (check: curl https://checkip.amazonaws.com)
Description: Your IP address to allow SSH access (CIDR format)
DBPassword:
Type: String
Default: 'Handson1234!'
NoEcho: true
Description: Master password for RDS MySQL (also stored in SSM Parameter Store)
TomcatVersion:
Type: String
Default: '10.1.28'
Description: Apache Tomcat version (check latest at https://archive.apache.org/dist/tomcat/tomcat-10/)
Resources:
# VPC and Network
# ============================================================
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-vpc'
- Key: Cost
Value: !Ref EmployeeId
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-igw'
- Key: Cost
Value: !Ref EmployeeId
IGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnet 1 (AZ-a): ALB + EC2 Tomcat
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-public-subnet-1'
- Key: Cost
Value: !Ref EmployeeId
# Public Subnet 2 (AZ-b): ALB requires subnets in at least 2 AZs
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-public-subnet-2'
- Key: Cost
Value: !Ref EmployeeId
# Private Subnet 1 (AZ-a): RDS instance is placed here
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-private-subnet-1'
- Key: Cost
Value: !Ref EmployeeId
# Private Subnet 2 (AZ-b): required for RDS Subnet Group (minimum 2 AZs)
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.4.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-private-subnet-2'
- Key: Cost
Value: !Ref EmployeeId
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-public-rt'
- Key: Cost
Value: !Ref EmployeeId
PublicRoute:
Type: AWS::EC2::Route
DependsOn: IGWAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-private-rt'
- Key: Cost
Value: !Ref EmployeeId
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateRouteTable
# S3 VPC Gateway Endpoint: free, allows EC2 to reach S3 for dnf package downloads
S3VPCEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PublicRouteTable
- !Ref PrivateRouteTable
# ============================================================
# IAM
# ============================================================
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'n${EmployeeId}-alb-ec2-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-ec2-role'
- Key: Cost
Value: !Ref EmployeeId
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub 'n${EmployeeId}-alb-ec2-profile'
Roles:
- !Ref EC2Role
# ============================================================
# Security Groups
# ============================================================
# ALB Security Group: accept HTTP:80 from the internet
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'n${EmployeeId} ALB SG - HTTP:80 from internet'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP from internet
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-sg'
- Key: Cost
Value: !Ref EmployeeId
# EC2 Security Group: Tomcat:8080 from ALB SG only, SSH from MyIP
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'n${EmployeeId} EC2 Tomcat SG'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !GetAtt ALBSecurityGroup.GroupId
Description: Tomcat from ALB SG only (SG-to-SG control)
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref MyIP
Description: SSH from my IP
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-ec2-sg'
- Key: Cost
Value: !Ref EmployeeId
# RDS Security Group: MySQL:3306 from EC2 SG only
RDSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'n${EmployeeId} RDS MySQL SG (EC2-to-RDS only)'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !GetAtt EC2SecurityGroup.GroupId
Description: MySQL from EC2 SG only (SG-to-SG control)
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-rds-sg'
- Key: Cost
Value: !Ref EmployeeId
# ============================================================
# SSM Parameter Store
# NOTE: CloudFormation can only create String type, not SecureString.
# ============================================================
DBPasswordParam:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub '/n${EmployeeId}/alb/db-password'
Type: String
Value: !Ref DBPassword
Description: !Sub 'RDS MySQL master password for n${EmployeeId} ALB hands-on'
Tags:
Cost: !Ref EmployeeId
# ============================================================
# RDS
# ============================================================
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: !Sub 'n${EmployeeId} RDS subnet group (private subnets in 2 AZs)'
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-rds-subnet-group'
- Key: Cost
Value: !Ref EmployeeId
RDSInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Delete
Properties:
DBInstanceIdentifier: !Sub 'n${EmployeeId}-alb-rds-mysql'
DBInstanceClass: db.t3.micro
Engine: mysql
EngineVersion: '8.0'
MasterUsername: admin
MasterUserPassword: !Ref DBPassword
AllocatedStorage: '20'
StorageType: gp2
DBName: sampledb
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !GetAtt RDSSecurityGroup.GroupId
MultiAZ: false
PubliclyAccessible: false
BackupRetentionPeriod: 0
DeleteAutomatedBackups: true
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-rds-mysql'
- Key: Cost
Value: !Ref EmployeeId
# ============================================================
# ALB (Application Load Balancer)
# ============================================================
# Target Group: routes traffic to EC2 Tomcat on port 8080
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub 'n${EmployeeId}-alb-tg'
Protocol: HTTP
Port: 8080
VpcId: !Ref VPC
TargetType: instance
HealthCheckProtocol: HTTP
HealthCheckPort: '8080'
HealthCheckPath: /
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
Targets:
- Id: !Ref EC2Instance
Port: 8080
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-tg'
- Key: Cost
Value: !Ref EmployeeId
# ALB: internet-facing, spans 2 public subnets
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub 'n${EmployeeId}-alb-alb'
Type: application
Scheme: internet-facing
IpAddressType: ipv4
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !GetAtt ALBSecurityGroup.GroupId
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-alb'
- Key: Cost
Value: !Ref EmployeeId
# Listener: HTTP:80 -> forward to TargetGroup
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ALB
Protocol: HTTP
Port: 80
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
# ============================================================
# EC2 (AP Server with Tomcat)
# ============================================================
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
InstanceType: t2.micro
KeyName: !Ref KeyName
SubnetId: !Ref PublicSubnet1
SecurityGroupIds:
- !GetAtt EC2SecurityGroup.GroupId
IamInstanceProfile: !Ref EC2InstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -xe
exec > >(tee /var/log/user-data.log) 2>&1
dnf update -y
dnf install -y java-17-amazon-corretto wget
# Install Tomcat
# Check latest version at: https://archive.apache.org/dist/tomcat/tomcat-10/
TOMCAT_VERSION="${TomcatVersion}"
cd /opt
wget -q https://archive.apache.org/dist/tomcat/tomcat-10/v${!TOMCAT_VERSION}/bin/apache-tomcat-${!TOMCAT_VERSION}.tar.gz
tar xzf apache-tomcat-${!TOMCAT_VERSION}.tar.gz
mv apache-tomcat-${!TOMCAT_VERSION} tomcat
chmod +x /opt/tomcat/bin/*.sh
# Deploy simple web application to ROOT context
cat > /opt/tomcat/webapps/ROOT/index.html << 'HTMLEOF'
<!DOCTYPE html>
<html>
<body>
<h1>AP Server - Phase 2-2 ALB + Tomcat + RDS</h1>
<p>This server is load balanced by ALB and connects to RDS MySQL in the private subnet.</p>
</body>
</html>
HTMLEOF
# Start Tomcat
/opt/tomcat/bin/startup.sh
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-alb-ap-instance'
- Key: Cost
Value: !Ref EmployeeId
Outputs:
ALBEndpoint:
Description: ALB DNS name - access via browser
Value: !Sub 'http://${ALB.DNSName}'
EC2PublicIP:
Description: EC2 Tomcat server public IP (for SSH)
Value: !GetAtt EC2Instance.PublicIp
RDSEndpoint:
Description: RDS MySQL endpoint hostname
Value: !GetAtt RDSInstance.Endpoint.Address
DBPasswordParamName:
Description: SSM Parameter Store path for RDS password
Value: !Ref DBPasswordParam
SSHCommand:
Description: SSH command to EC2 AP server
Value: !Sub 'ssh -i C:\Users\username\.ssh\${KeyName}.pem ec2-user@${EC2Instance.PublicIp}'
MySQLConnectCommand:
Description: MySQL connect command (run FROM the EC2 AP server)
Value: !Sub 'mysql -h ${RDSInstance.Endpoint.Address} -u admin -pHandson1234! sampledb'作業順序
⓪ 自分のIPアドレスを確認する
↓
① キーペアを作成する(コンソールで実施)
↓
② プロジェクトフォルダに移動する
↓
③ スタックを作成する(aws cloudformation create-stack)
↓
④ 作成完了・Outputsを確認する(RDSは15〜20分かかる)
↓
⑤ 動作確認(ALBアクセス・SSH・RDS接続)
↓
⑥ スタックを削除する(aws cloudformation delete-stack)【重要】⓪ 自分のIPアドレスを確認する
curl https://checkip.amazonaws.com控えておく情報: 自分のIPアドレス(例: 203.0.113.1)
① キーペアの作成(コンソールで実施)
CloudFormationではキーペアのダウンロードができないため、コンソールで先に作成します。
AWSコンソール → EC2 → キーペア → 「キーペアを作成」
| 設定項目 | 値 |
|---|---|
| 名前 | my-alb-key |
| キーペアのタイプ | RSA |
| プライベートキーファイル形式 | .pem |
ダウンロードされた my-alb-key.pem を保存します。
C:\Users\ユーザー名\.ssh\my-alb-key.pem② プロジェクトフォルダに移動する
VSCodeのターミナル(CMD)を開き、プロジェクトフォルダに移動します。
cd C:\my-aws\aws-learning-projects\alb-ec2-rdstemplate.yaml があることを確認します。
dir template.yaml③ スタックの作成
以下のコマンドを実行します。203.0.113.1 は⓪で確認した自分のIPアドレスに置き換えます。
aws cloudformation create-stack ^
--stack-name my-alb-stack ^
--template-body file://template.yaml ^
--region ap-northeast-1 ^
--capabilities CAPABILITY_NAMED_IAM ^
--parameters ^
ParameterKey=EmployeeId,ParameterValue=123456 ^
ParameterKey=KeyName,ParameterValue=my-alb-key ^
ParameterKey=MyIP,ParameterValue=203.0.113.1/32 ^
ParameterKey=DBPassword,ParameterValue=Handson1234!
TomcatVersionパラメータについて: デフォルト値は10.1.28。変更する場合はParameterKey=TomcatVersion,ParameterValue=10.1.xxを追加します。利用可能なバージョンは https://archive.apache.org/dist/tomcat/tomcat-10/ で確認します。
CAPABILITY_NAMED_IAMについて:EC2Roleのように名前を指定したIAMリソースを作成する場合に必要なフラグです。
成功すると以下のような StackId が表示されます。
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/my-alb-stack/xxxxx"
}④ 作成完了・Outputsの確認
注意: RDSの作成には約15〜20分かかります。
CREATE_IN_PROGRESSが続く間は正常です。
作成状況の確認
aws cloudformation describe-stacks ^
--stack-name my-alb-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
CREATE_IN_PROGRESS | 作成中(待つ) |
CREATE_COMPLETE | 作成完了 |
CREATE_FAILED | 作成失敗(後述のトラブルシューティングを参照) |
ROLLBACK_COMPLETE | 失敗してロールバック完了 |
Outputs(接続情報)の確認
CREATE_COMPLETE になったら以下を実行します。
aws cloudformation describe-stacks ^
--stack-name my-alb-stack ^
--query "Stacks[0].Outputs" ^
--output table+----------------------+------------------------------------------------------------------+
| OutputKey | OutputValue |
+----------------------+------------------------------------------------------------------+
| ALBEndpoint | http://my-alb-alb-1234567890.ap-northeast-1.elb.amazonaws.com |
| EC2PublicIP | x.x.x.x |
| RDSEndpoint | my-alb-rds-mysql.xxxxx.ap-northeast-1.rds.amazonaws.com |
| DBPasswordParamName | /my/alb/db-password |
| SSHCommand | ssh -i C:\Users\...\.ssh\my-alb-key.pem ec2-user@x.x.x.x |
+----------------------+------------------------------------------------------------------+控えておく情報: ALBEndpoint、EC2PublicIP、RDSEndpoint
⑤ 動作確認
ALBのヘルスチェック確認
EC2 → ターゲットグループ → my-alb-tg → 「ターゲット」タブ
EC2のステータスが 「healthy」 になるまで待ちます(EC2起動後5〜10分)。
Tomcatの起動はEC2が「実行中」になってからさらに数分かかります。
initialやunhealthyが続く場合はしばらく待ちます。
ALB経由でWebアクセス確認
Outputsに表示された ALBEndpoint をブラウザで開きます。
http://(ALBEndpointのDNS名)「AP Server - Phase 2-2 ALB + Tomcat + RDS」と表示されれば正常です。
EC2にSSH接続する
Outputsの SSHCommand を実行します(パスは実際のパスに修正します)。
ssh -i C:\Users\ユーザー名\.ssh\my-alb-key.pem ec2-user@(EC2PublicIP)Parameter StoreからDBパスワードを取得する
EC2に接続した状態で実行します。
aws ssm get-parameter \
--name "/my/alb/db-password" \
--query "Parameter.Value" \
--output text \
--region ap-northeast-1Handson1234! が表示されれば成功です。
--with-decryptionが不要な理由: CloudFormationで作成したパラメータはType: String(平文)のため、フラグなしでそのまま値が返ります。コンソールで SecureString として作成した場合は--with-decryptionが必要です(コンソール版参照)。
RDSへのMySQL接続テスト
# RDSエンドポイントをOutputsの値に置き換える
RDS_ENDPOINT="my-alb-rds-mysql.xxxxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
# Parameter StoreからDBパスワードを取得して接続(String型のため--with-decryption不要)
DB_PASSWORD=$(aws ssm get-parameter \
--name "/my/alb/db-password" \
--query "Parameter.Value" \
--output text \
--region ap-northeast-1)
mysql -h $RDS_ENDPOINT -u admin -p"$DB_PASSWORD" sampledb接続成功後、SQL操作を確認します。
-- テーブル作成
CREATE TABLE items (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- データ挿入
INSERT INTO items (name) VALUES ('Apple'), ('Banana');
-- データ確認
SELECT * FROM items;
-- 後片付け
DROP TABLE items;
EXIT;EC2からSSH接続を切断します。
exit⑥ スタックの削除
課金を止めるために、ハンズオン完了後は必ず削除します。
aws cloudformation delete-stack ^
--stack-name my-alb-stack ^
--region ap-northeast-1注意: RDS削除には約10〜15分かかります。コマンド実行後もしばらく待つ必要があります。
削除状況の確認
aws cloudformation describe-stacks ^
--stack-name my-alb-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
DELETE_IN_PROGRESS | 削除中(待つ) |
DELETE_FAILED | 削除失敗(トラブルシューティングを参照) |
削除完了の確認(以下のエラーが表示されれば削除完了):
aws cloudformation describe-stacks ^
--stack-name my-alb-stack ^
--region ap-northeast-1An error occurred (ValidationError) when calling the DescribeStacks operation:
Stack with id my-alb-stack does not existキーペアの手動削除
キーペアはCloudFormationで管理していないため手動で削除します。
EC2 → キーペア → my-alb-key を選択 → 「アクション」→「削除」
コンソール版との比較
| 作業 | コンソール(手動) | CloudFormation |
|---|---|---|
| VPC + サブネット4つ + IGW + ルートテーブル | 約20〜30分 | template.yaml に定義済み |
| SG 3つ(ALB/EC2/RDS)の作成 | 3画面で個別設定 | template.yaml に定義済み |
| ALB + TG + Listener の作成 | 3つの画面で設定 | template.yaml に定義済み |
| Tomcat UserDataの貼り付け | 手動コピペ | template.yaml に組み込み済み |
| 全体のデプロイ | 約40〜50分 | コマンド1本(RDS待ち15〜20分) |
| 削除 | 13ステップ手動 | delete-stack 1本 |
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
CREATE_FAILED → ROLLBACKになる | パラメータ不正・リソース上限など | コンソールのCloudFormationイベントタブでエラー詳細を確認 |
| ALBに繋がらない | EC2のTomcatがまだ起動中 | TGのヘルスチェックがHealthyになるまで待つ(5〜10分) |
| ALBに繋がるが502エラー | Tomcat起動失敗 | SSH接続して sudo cat /var/log/user-data.log 確認 |
ERROR 2003: MySQL接続できない | RDS SGのソース設定ミス | RDS SGのインバウンドルールを確認 |
| Tomcatバージョンエラー | 指定バージョンがアーカイブに存在しない | https://archive.apache.org/dist/tomcat/tomcat-10/ で確認して TomcatVersion パラメータを更新 |
DELETE_FAILED | リソースの依存関係が残っている | コンソールからALBを手動削除してから delete-stack を再実行 |
スタック作成後しばらく CREATE_IN_PROGRESS が続く | RDSの作成に時間がかかっている | 正常。約15〜20分待つ |
失敗時の詳細確認コマンド
aws cloudformation describe-stack-events ^
--stack-name my-alb-stack ^
--query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[ResourceType,ResourceStatusReason]" ^
--output tableまとめ
| ステップ | 内容 |
|---|---|
| ① | キーペアをコンソールで作成 |
| ②③ | alb-ec2-rds/ フォルダに移動し create-stack を実行 |
| ④ | CREATE_COMPLETE になったらOutputsでALBエンドポイント・EC2IP・RDSエンドポイントを確認 |
| ⑤ | ヘルスチェックがHealthyになったらALB → EC2 → RDS MySQL への接続テスト |
| ⑥ | delete-stack 1本で27リソースを一括削除 |
CloudFormation版の最大のメリットは再現性と削除の簡単さです。コンソールでは40〜50分・13ステップかかった作業が、コマンド1本(待ち時間込みで約20分)で完了します。
コンソール操作でALB・ターゲットグループ・ヘルスチェックを視覚的に確認したい場合は、AWSコンソール版ハンズオンを参照してください。
コメント