はじめに
「コンソールで40〜50分かかったRDS + EC2環境をコードで再現したい」——それを実現するのが AWS CloudFormation です。
この記事では、template.yaml 1ファイルにVPC・サブネット・セキュリティグループ・IAMロール・SSM Parameter Store・RDS MySQL・EC2(22リソース)を定義し、コマンド1本でRDS + EC2接続環境を一括構築するハンズオンを紹介します。コンソール版(AWSコンソール版ハンズオン)と全く同じ構成を、CloudFormationで自動化します。
ローカル環境(VSCode)
├── template.yaml(22リソースを定義)
└── AWS CLI コマンド
↓ スタック作成(コマンド1本)
AWS環境
└── VPC(10.0.0.0/16)
├── パブリックサブネット(10.0.1.0/24 / AZ-a)
│ └── EC2(Apache + mariadb client / パブリックIP あり)
│ └── IAMロール → SSM Parameter Store
│
├── DBサブネットグループ
│ ├── プライベートサブネット1(10.0.2.0/24 / AZ-a)← RDS配置
│ └── プライベートサブネット2(10.0.3.0/24 / AZ-c)← 2AZ要件
│
└── SSM Parameter Store: /my/rds/db-passwordこのハンズオンで体験できること:
- VPC・サブネット3つ・IGW・ルートテーブル・SG・IAM・SSM・RDS・EC2を
template.yaml1ファイルで定義する方法 DBPasswordパラメータをNoEcho: trueでマスクしてセキュアに渡す方法DeletionPolicy: Delete+BackupRetentionPeriod: 0でスタック削除時にRDSを確実に削除する設定CloudFormationがSecureStringを作れない理由と代替手段
このハンズオンの特徴:
- コンソール版では40〜50分かかった作業が、コマンド1本(RDS待ち15〜20分含む)で完了
delete-stack1本でRDS・VPC・IAMなど22リソースの依存関係を自動解決して削除
この記事は AWSコンソール版ハンズオン の比較記事です。
コンソール版でRDS・DBサブネットグループ・Parameter Storeを視覚的に学んだ後に読むと理解が深まります。
-->
CloudFormation vs コンソール:どれだけ違うか
| 比較項目 | CloudFormation | コンソール(手動) |
|---|---|---|
| VPC + サブネット3つ + IGW + ルートテーブル | template.yaml に定義済み | 約15〜20分・複数画面 |
| IAMロール作成 | template.yaml に定義済み | 別画面で作成 |
| SSM Parameter Store作成 | template.yaml に定義済み | 別画面で作成 |
| RDS DBサブネットグループ | template.yaml に定義済み | 別画面で作成 |
| RDS作成(設定項目20以上) | --parameters DBPassword=... を渡すだけ | 画面でポチポチ |
| 全体のデプロイ | コマンド1本(RDS待ち15〜20分) | 40〜50分 |
| 削除(依存関係あり) | delete-stack 1本(自動解決) | 11ステップ・手動管理 |
| 再現性 | 高い(同じ構成を何度でも再現可能) | 低い(手順漏れのリスク大) |
| バージョン管理 | Gitで管理可能 | 不可 |
キーワード解説
| 用語 | 意味 |
|---|---|
| CloudFormation | AWSが提供するIaC(Infrastructure as Code)サービス。YAMLやJSONでリソースを定義し、一括作成・削除できる |
| スタック | CloudFormationが管理するリソースのまとまり。今回は22リソースを1スタックで管理 |
| DeletionPolicy | スタック削除時のリソースの扱いを指定するプロパティ。Delete/Retain/Snapshot(RDSのみ)の3種類 |
| NoEcho | CloudFormationパラメータの設定。true にするとコンソール上でパスワードが **** にマスクされる |
| SecureString | Parameter StoreでKMS暗号化して保管するタイプ。CloudFormationでは作成不可 |
| CAPABILITY_NAMED_IAM | IAMリソースを含むスタックの作成・更新時に必要なフラグ |
前提条件
| ツール | 確認コマンド |
|---|---|
| AWS CLI v2 | aws --version |
AWS認証確認:
aws sts get-caller-identity構築されるリソース(22個)
| カテゴリ | リソース | 論理ID |
|---|---|---|
| ネットワーク | VPC | VPC |
| ネットワーク | インターネットゲートウェイ | InternetGateway + IGWAttachment |
| ネットワーク | パブリックサブネット | PublicSubnet |
| ネットワーク | プライベートサブネット1(RDS配置) | PrivateSubnet1 |
| ネットワーク | プライベートサブネット2(2AZ要件) | PrivateSubnet2 |
| ネットワーク | パブリックルートテーブル | PublicRouteTable + PublicRoute + PublicSubnetRTA |
| ネットワーク | プライベートルートテーブル | PrivateRouteTable + PrivateSubnet1RTA + PrivateSubnet2RTA |
| ネットワーク | S3 VPC Gateway Endpoint | S3VPCEndpoint |
| セキュリティ | EC2セキュリティグループ | EC2SecurityGroup |
| セキュリティ | RDSセキュリティグループ | RDSSecurityGroup |
| IAM | EC2ロール + インスタンスプロファイル | EC2Role + EC2InstanceProfile |
| パラメータ | SSM Parameter Store | DBPasswordParam |
| データベース | RDS DBサブネットグループ | DBSubnetGroup |
| データベース | RDS MySQLインスタンス | RDSInstance |
| コンピュート | EC2インスタンス | EC2Instance |
template.yaml(完全版)
このファイルをそのまま使ってハンズオンを実施できます。C:\my-aws\aws-learning-projects\rds-mysql-ec2\template.yaml として保存してください。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'RDS MySQL + EC2 connection hands-on with SSM Parameter Store password management'
Parameters:
EmployeeId:
Type: String
Default: '123456'
Description: Employee ID used as a prefix for resource names
KeyName:
Type: String
Default: 'my-rds-mysql-key'
Description: Name of an existing EC2 KeyPair
MyIP:
Type: String
Default: '123.456.78.901/32' # [IMPORTANT] Replace with your own IP (check: curl https://checkip.amazonaws.com)
Description: Your IP address to allow SSH and HTTP access (CIDR format e.g. 123.456.78.901/32)
DBPassword:
Type: String
Default: 'Handson1234!'
NoEcho: true
Description: Master password for RDS MySQL (also stored in SSM Parameter Store)
Resources:
# VPC and Network
# ============================================================
# VPC: isolated network for this hands-on
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-rds-vpc'
- Key: Cost
Value: !Ref EmployeeId
# Internet Gateway: connects public subnet to the internet
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-rds-igw'
- Key: Cost
Value: !Ref EmployeeId
# Attach Internet Gateway to VPC
IGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnet (AZ-a): EC2 AP server is placed here
PublicSubnet:
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}-public-subnet'
- 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.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-private-subnet-1'
- Key: Cost
Value: !Ref EmployeeId
# Private Subnet 2 (AZ-b): required for RDS Subnet Group (minimum 2 AZs)
# RDS Subnet Group must span at least 2 AZs even for single-AZ deployments
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: !Select [1, !GetAZs '']
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-private-subnet-2'
- Key: Cost
Value: !Ref EmployeeId
# Public Route Table: routes internet traffic via Internet Gateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-public-rt'
- Key: Cost
Value: !Ref EmployeeId
# Default route for public subnet: 0.0.0.0/0 -> Internet Gateway
PublicRoute:
Type: AWS::EC2::Route
DependsOn: IGWAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
# Private Route Table: no internet route (intra-VPC traffic only)
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-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 in public subnet 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
# ============================================================
# IAM Role: EC2 can use Session Manager and read SSM Parameter Store
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'n${EmployeeId}-rds-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}-rds-ec2-role'
- Key: Cost
Value: !Ref EmployeeId
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub 'n${EmployeeId}-rds-ec2-profile'
Roles:
- !Ref EC2Role
# ============================================================
# Security Groups
# ============================================================
# EC2 Security Group: SSH and HTTP access from MyIP
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'n${EmployeeId} EC2 AP server SG'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref MyIP
Description: SSH from my IP
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref MyIP
Description: HTTP from my IP
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-rds-ec2-sg'
- Key: Cost
Value: !Ref EmployeeId
# RDS Security Group: MySQL(3306) from EC2 SG only - not open to the internet
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}-rds-sg'
- Key: Cost
Value: !Ref EmployeeId
# ============================================================
# SSM Parameter Store (password management)
# NOTE: CloudFormation can only create String type, not SecureString.
# In production, create a SecureString manually and reference it with
# {{resolve:ssm-secure:/path/to/param}}
# ============================================================
DBPasswordParam:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub '/n${EmployeeId}/rds/db-password'
Type: String
Value: !Ref DBPassword
Description: !Sub 'RDS MySQL master password for n${EmployeeId} hands-on'
Tags:
Cost: !Ref EmployeeId
# ============================================================
# RDS
# ============================================================
# DB Subnet Group: registers which subnets RDS can use
# Requires subnets in at least 2 different Availability Zones
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}-rds-subnet-group'
- Key: Cost
Value: !Ref EmployeeId
# RDS MySQL Instance (single-AZ, db.t3.micro - free tier eligible)
RDSInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Delete
Properties:
DBInstanceIdentifier: !Sub 'n${EmployeeId}-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}-rds-mysql'
- Key: Cost
Value: !Ref EmployeeId
# ============================================================
# EC2 (AP Server)
# ============================================================
# AP Server: Apache + MySQL client in public subnet
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 PublicSubnet
SecurityGroupIds:
- !GetAtt EC2SecurityGroup.GroupId
IamInstanceProfile: !Ref EC2InstanceProfile
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd mariadb105
systemctl start httpd
systemctl enable httpd
echo "<h1>AP Server - RDS MySQL Hands-on</h1>" > /var/www/html/index.html
echo "<p>This server connects to RDS MySQL in the private subnet.</p>" >> /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub 'n${EmployeeId}-rds-ap-instance'
- Key: Cost
Value: !Ref EmployeeId
Outputs:
EC2PublicIP:
Description: EC2 AP server public IP
Value: !GetAtt EC2Instance.PublicIp
WebsiteURL:
Description: EC2 AP server website URL
Value: !Sub 'http://${EC2Instance.PublicIp}'
RDSEndpoint:
Description: RDS MySQL endpoint hostname (use this from EC2 to connect)
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 this FROM the EC2 AP server)
Value: !Sub 'mysql -h ${RDSInstance.Endpoint.Address} -u admin -pHandson1234! sampledb'template.yaml の設計ポイント(RDS固有):
DeletionPolicy の3種類:
| 設定 | 意味 |
|---|---|
Delete(今回) | スタック削除時にRDSも削除する |
Retain | スタック削除時にRDSを残す |
Snapshot(RDSのみ) | スタック削除時にスナップショットを作成してから削除 |
なぜ
DeletionPolicy: Deleteを明示するか: RDSはデフォルトでスナップショット作成を要求される場合があります。明示的にDeleteを指定することで確実にスキップできます。またBackupRetentionPeriod: 0で自動バックアップを無効にし、削除をよりスムーズにしています。
CloudFormationが SecureString を作れない理由:
AWS::SSM::Parameter は String と StringList のみサポートしています。SecureString は KMS の複雑な依存関係があるためサポート外です。本番環境では事前に SecureString を作成し {{resolve:ssm-secure:/path}} で参照する方法が推奨されます。
作業順序
⓪ 自分のIPアドレスを確認する
↓
① キーペアを作成する(コンソールで実施)
↓
② プロジェクトフォルダに移動する
↓
③ スタックを作成する(aws cloudformation create-stack)
↓
④ 作成完了・Outputsを確認する(RDSは15〜20分かかる)
↓
⑤ 動作確認(Web・SSH・RDS接続)
↓
⑥ スタックを削除する(aws cloudformation delete-stack)【重要】⓪ 自分のIPアドレスを確認する
curl https://checkip.amazonaws.com控えておく情報: 自分のIPアドレス(例: 203.0.113.1)
① キーペアの作成(コンソールで実施)
CloudFormationではキーペアのダウンロードができないため、コンソールで先に作成します。
AWSコンソール → EC2 → キーペア → 「キーペアを作成」
| 設定項目 | 値 |
|---|---|
| 名前 | my-rds-mysql-key |
| キーペアのタイプ | RSA |
| プライベートキーファイル形式 | .pem |
ダウンロードされた my-rds-mysql-key.pem を保存します。
C:\Users\ユーザー名\.ssh\my-rds-mysql-key.pem② プロジェクトフォルダに移動する
VSCodeのターミナル(CMD)を開き、プロジェクトフォルダに移動します。
cd C:\my-aws\aws-learning-projects\rds-mysql-ec2template.yaml があることを確認します。
dir template.yaml③ スタックの作成
以下のコマンドを実行します。203.0.113.1 は⓪で確認した自分のIPアドレスに置き換えます。
aws cloudformation create-stack ^
--stack-name my-rds-mysql-stack ^
--template-body file://template.yaml ^
--region ap-northeast-1 ^
--capabilities CAPABILITY_NAMED_IAM ^
--parameters ^
ParameterKey=EmployeeId,ParameterValue=123456 ^
ParameterKey=KeyName,ParameterValue=my-rds-mysql-key ^
ParameterKey=MyIP,ParameterValue=203.0.113.1/32 ^
ParameterKey=DBPassword,ParameterValue=Handson1234!
DBPasswordパラメータについて:NoEcho: trueが設定されているため、コンソール上では****とマスクされます。
CAPABILITY_NAMED_IAMについて:EC2Roleのように名前を指定したIAMリソースを作成する場合に必要なフラグです。
成功すると以下のような StackId が表示されます。
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/my-rds-mysql-stack/xxxxx"
}④ 作成完了・Outputsの確認
注意: RDSの作成には約15〜20分かかります。
CREATE_IN_PROGRESSが続く間は正常です。
作成状況の確認
aws cloudformation describe-stacks ^
--stack-name my-rds-mysql-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-rds-mysql-stack ^
--query "Stacks[0].Outputs" ^
--output table+---------------------+--------------------------------------------------------------------+
| OutputKey | OutputValue |
+---------------------+--------------------------------------------------------------------+
| EC2PublicIP | x.x.x.x |
| WebsiteURL | http://x.x.x.x |
| RDSEndpoint | my-rds-mysql.xxxxx.ap-northeast-1.rds.amazonaws.com |
| DBPasswordParamName| /my/rds/db-password |
| SSHCommand | ssh -i C:\Users\...\.ssh\...pem ec2-user@x.x.x.x |
| MySQLConnectCommand| mysql -h my-rds-mysql.xxxxx...rds.amazonaws.com -u admin ... |
+---------------------+--------------------------------------------------------------------+控えておく情報: EC2PublicIP、RDSEndpoint
⑤ 動作確認
Webブラウザで確認
Outputsに表示された WebsiteURL をブラウザで開きます。
「AP Server - RDS MySQL Hands-on」と表示されればEC2は正常です。
EC2にSSH接続する
Outputsの SSHCommand を実行します(パスは実際のパスに修正します)。
ssh -i C:\Users\ユーザー名\.ssh\my-rds-mysql-key.pem ec2-user@(EC2PublicIP)Parameter StoreからDBパスワードを取得する
EC2に接続した状態で実行します。
aws ssm get-parameter \
--name "/my/rds/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-rds-mysql.xxxxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
# Parameter StoreからDBパスワードを取得して接続(String型のため--with-decryption不要)
DB_PASSWORD=$(aws ssm get-parameter \
--name "/my/rds/db-password" \
--query "Parameter.Value" \
--output text \
--region ap-northeast-1)
mysql -h $RDS_ENDPOINT -u admin -p"$DB_PASSWORD" sampledbまたは Outputsに表示された MySQLConnectCommand を直接実行してもかまいません。
接続成功後、SQL操作を確認します。
-- テーブル作成
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- データ挿入
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
-- データ確認
SELECT * FROM users;
-- 後片付け
DROP TABLE users;
EXIT;EC2からSSH接続を切断します。
exit⑥ スタックの削除
課金を止めるために、ハンズオン完了後は必ず削除します。
aws cloudformation delete-stack ^
--stack-name my-rds-mysql-stack ^
--region ap-northeast-1注意: RDS削除には約10〜15分かかります。コマンド実行後もしばらく待つ必要があります。
削除状況の確認
aws cloudformation describe-stacks ^
--stack-name my-rds-mysql-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
DELETE_IN_PROGRESS | 削除中(待つ) |
DELETE_FAILED | 削除失敗(トラブルシューティングを参照) |
削除完了の確認(以下のエラーが表示されれば削除完了):
aws cloudformation describe-stacks ^
--stack-name my-rds-mysql-stack ^
--region ap-northeast-1An error occurred (ValidationError) when calling the DescribeStacks operation:
Stack with id my-rds-mysql-stack does not existこのメッセージが表示されれば削除完了です。
キーペアの手動削除
キーペアはCloudFormationで管理していないため手動で削除します。
EC2 → キーペア → my-rds-mysql-key を選択 → 「アクション」→「削除」
コンソール版との比較
| 作業 | コンソール(手動) | CloudFormation |
|---|---|---|
| VPC + サブネット3つ + IGW + ルートテーブル | 約15〜20分 | template.yaml に定義済み |
| IAMロール作成 | 別画面で作成 | template.yaml に定義済み |
| RDS DBサブネットグループ作成 | 別画面で作成 | template.yaml に定義済み |
| RDS作成(設定項目20以上) | 画面でポチポチ | --parameters DBPassword=... を渡すだけ |
| 全体のデプロイ | 約40〜50分 | コマンド1本(RDS待ち15〜20分) |
| 削除 | 11ステップ(RDS削除待ちあり) | delete-stack 1本 |
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
CREATE_FAILED → DBInstance失敗 | 指定AZに db.t3.micro が作成できない | コンソールで利用可能なAZを確認する |
stackName failed to satisfy regular expression pattern | スタック名が数字始まり | my-rds-mysql-stack のように英字で始める |
Unable to load paramfile, text contents could not be decoded | template.yaml に日本語が含まれている | template.yaml のコメントは英語のみで記述する(Windows環境の既知の問題) |
ERROR 2003: Can't connect to MySQL server | RDSがまだ起動中またはSGの設定ミス | RDSのステータスが「利用可能」になっているか確認。RDS SGのソースがEC2 SGのIDになっているか確認 |
ERROR 1045: Access denied for user | パスワードが誤り | --parameters DBPassword=... の値を確認 |
AccessDeniedException on ssm get-parameter | IAMロールに AmazonSSMReadOnlyAccess がない | EC2Role に AmazonSSMReadOnlyAccess がアタッチされているか確認 |
DELETE_FAILED | スナップショット作成が失敗した | コンソールからRDSを手動削除(スナップショットなしで)してから delete-stack を再実行 |
スタック作成後しばらく CREATE_IN_PROGRESS が続く | RDSの作成に時間がかかっている | 正常。約15〜20分待つ |
失敗時の詳細確認コマンド
aws cloudformation describe-stack-events ^
--stack-name my-rds-mysql-stack ^
--query "StackEvents[?ResourceStatus=='CREATE_FAILED'].[ResourceType,ResourceStatusReason]" ^
--output tableまとめ
| ステップ | 内容 |
|---|---|
| ① | キーペアをコンソールで作成 |
| ②③ | rds-mysql-ec2/ フォルダに移動し create-stack を実行 |
| ④ | CREATE_COMPLETE になったらOutputsでEC2IP・RDSエンドポイントを確認 |
| ⑤ | EC2 → Parameter Store → RDS MySQL への接続テスト |
| ⑥ | delete-stack 1本で22リソースを一括削除 |
CloudFormation版の最大のメリットは再現性と削除の簡単さです。コンソールでは40〜50分・11ステップかかった作業が、コマンド1本(待ち時間込みで約20分)で完了します。
コンソール操作でRDS・DBサブネットグループ・Parameter Storeを視覚的に確認したい場合は、AWSコンソール版ハンズオンを参照してください。
コメント