CloudFormationでRDS MySQL + EC2接続環境を自動構築する手順【22リソース一括デプロイ / コンソール版との比較付き】

AWS Basic
スポンサーリンク
スポンサーリンク

はじめに

「コンソールで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.yaml 1ファイルで定義する方法
  • DBPassword パラメータを NoEcho: true でマスクしてセキュアに渡す方法
  • DeletionPolicy: Delete + BackupRetentionPeriod: 0 でスタック削除時にRDSを確実に削除する設定
  • CloudFormationSecureString を作れない理由と代替手段

このハンズオンの特徴:

  • コンソール版では40〜50分かかった作業が、コマンド1本(RDS待ち15〜20分含む)で完了
  • delete-stack 1本で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で管理可能不可

スポンサーリンク

キーワード解説

用語意味
CloudFormationAWSが提供するIaC(Infrastructure as Code)サービス。YAMLやJSONでリソースを定義し、一括作成・削除できる
スタックCloudFormationが管理するリソースのまとまり。今回は22リソースを1スタックで管理
DeletionPolicyスタック削除時のリソースの扱いを指定するプロパティ。Delete/Retain/Snapshot(RDSのみ)の3種類
NoEchoCloudFormationパラメータの設定。true にするとコンソール上でパスワードが **** にマスクされる
SecureStringParameter StoreでKMS暗号化して保管するタイプ。CloudFormationでは作成不可
CAPABILITY_NAMED_IAMIAMリソースを含むスタックの作成・更新時に必要なフラグ

前提条件

ツール確認コマンド
AWS CLI v2aws --version

AWS認証確認:

aws sts get-caller-identity

構築されるリソース(22個)

カテゴリリソース論理ID
ネットワークVPCVPC
ネットワークインターネットゲートウェイInternetGateway + IGWAttachment
ネットワークパブリックサブネットPublicSubnet
ネットワークプライベートサブネット1(RDS配置)PrivateSubnet1
ネットワークプライベートサブネット2(2AZ要件)PrivateSubnet2
ネットワークパブリックルートテーブルPublicRouteTable + PublicRoute + PublicSubnetRTA
ネットワークプライベートルートテーブルPrivateRouteTable + PrivateSubnet1RTA + PrivateSubnet2RTA
ネットワークS3 VPC Gateway EndpointS3VPCEndpoint
セキュリティEC2セキュリティグループEC2SecurityGroup
セキュリティRDSセキュリティグループRDSSecurityGroup
IAMEC2ロール + インスタンスプロファイルEC2Role + EC2InstanceProfile
パラメータSSM Parameter StoreDBPasswordParam
データベース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::ParameterStringStringList のみサポートしています。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-ec2

template.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 ... |
+---------------------+--------------------------------------------------------------------+

控えておく情報: EC2PublicIPRDSEndpoint


⑤ 動作確認

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-1

Handson1234! が表示されれば成功です。

--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-1
An 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 decodedtemplate.yaml に日本語が含まれているtemplate.yaml のコメントは英語のみで記述する(Windows環境の既知の問題)
ERROR 2003: Can't connect to MySQL serverRDSがまだ起動中またはSGの設定ミスRDSのステータスが「利用可能」になっているか確認。RDS SGのソースがEC2 SGのIDになっているか確認
ERROR 1045: Access denied for userパスワードが誤り--parameters DBPassword=... の値を確認
AccessDeniedException on ssm get-parameterIAMロールに AmazonSSMReadOnlyAccess がないEC2RoleAmazonSSMReadOnlyAccess がアタッチされているか確認
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コンソール版ハンズオンを参照してください。

コメント