4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JAWS-UG のイベントで使うハンズオンシナリオを Amazon Q Developer for CLI で作ってみた話

Last updated at Posted at 2025-07-21

2025年7月19日 に JAWS-UG 神戸のオフラインイベントとして開催した勉強会で AWS Fault Injection Service(AWS FIS)のハンズオンのイベントを実施しました。

その際に、Amazon Q Developer for CLI を用いてハンズオンシナリオを作成しましたので、その経緯とやってみた感想を記しておきます。

きっかけ

AWS FIS を取り上げたハンズオン企画を実施して JAWS-UG 神戸のイベントとして実施したいと考えたのは 2025年4月 の中盤頃でした。

すでに喉元を過ぎている感がありますが 2025年4月15日 に東京リージョンのアベイラビリティゾーンで電源の消失に端を発した AZ 障害が発生しました。

このところ大きめ、かつ、広範な基盤障害がなかったこともあり、筆者がテクニカルサポートエンジニアを務めている所属企業へのお問い合わせも多岐にわたるお問い合わせがありました。幸いにも 1 つの AZ に閉じた障害でしたので影響も限定的で、当該の AZ を利用していないお客様や他の AZ も含めたマルチ AZ 構成をとっていたお客様にはほとんど影響がなかったはずです。

ただ、いただくお問い合わせの一定数には、クラウドの良いところを活かして実際に試してみると良いのでは、と思われるものもありました。

とはいえ、稼働中のサービス/システムに対して擬似障害であっても注入することは困難ですし、検証であっても試験計画を提出し承認を受けてからでないと実行できないプロジェクトも多いはずです。
カオスエンジニアリングをマネージドサービスとして実行できる AWS FIS というサービスの名前を知っていてもよくわからないものを担ぐこと自体難しいだろうなと思いながら問い合わせ対応をしていました。

そこで、全く無関係・無責任な環境に心置きなく擬似障害を起こしてみて AWS FIS がどのようなサービスであるか体験してみる勉強会を開いてみようと考えたのがきっかけです。

なぜシナリオを手作りしたか

AWS が公開する公式ハンズオンのうち日本語化されているものを収録する JP Contents Hub というスペースがあります。

元々ここに収録されているシナリオに AWS FIS を取り上げたものも存在していました。
ただハンズオン企画を検討後のゴールデンウィーク中に試してみた際に正常に動作しない状況であることが判明しました。

挙動を追いかけてみたところ冒頭の初期構築の際に実行される CloudFormation テンプレート内部で、S3 バケットから取得している別の CloudFormation テンプレートがありました。その呼び出される側のテンプレートで、現時点で選択できない DB インスタンスタイプを指定している部分が見つかりました。

その構造的に利用者側で修正ができず、AWS で修正をしない限り動作しなさそうだと判断したので、ハンズオンシナリオを自作することにしました。

Amazon Q Developer for CLI の利用について

個人的には AWS CLI が好きなので何らかの手順書を作るときは CLI での手順で作成することが多いです。

ただ、不特定多数のイベント参加者の環境差分を極力排除するには、取り上げる技術要素以外の共通環境を構築する部分など、本編部分以外になるべくつまづきポイントを減らした方が良いと考えました。

そのため IaC を活用し、実行手順の読み飛ばしや環境差異に左右されない実行をしたかったのですが、普段 CloudFormation を書かないのでできれば楽したい、と考えたのがモチベーションです。

私の環境が macOS であるので、Amazon Q Developer for CLI はすでに導入済みで、日常業務でもよく利用していました。

そこで、考えていることを Amazon Q Developer for CLI に伝えながら、ハンズオンの流れに応じた CloudFormation テンプレートを作成できないかやってみた次第です。

構想と作成時の流れ

構想

大元の構想として頭の中にあったのは以下のような感じでした。

  • パブリックサブネットとプライベートサブネットを持った VPC を作成する
  • 擬似障害を受ける環境は WordPress で作成した Web サイトとする
  • 当初の環境としては、自動割り当てで付与されたパブリック IP アドレスを持つ EC2 インスタンスをパブリックサブネットに 1 台配置する
  • データベースとして利用する MySQL ハンズオンの時間内でトラブル発生すると別のトラブルシュートが発生してしまうため Amazon Aurora を使う
    • ただし、初期配置ではマルチ AZ 構成にしない
  • WordPress のインストールは EC2 インスタンス作成時の userdata で実行するようにする
  • RDS に対する障害注入シナリオを実行した後で、Aurora MySQL をマルチ AZ 化して、同じ障害注入シナリオの影響を受けないことを体験する(AWS FIS の体験部分)
  • EC2 インスタンスに対する障害注入シナリオを実行した後で以下のように構成変更することで同じ障害注入シナリオを実行しても影響を受けなくなっていることを体験する(AWS FIS の体験部分)
    • ALB 配下で稼働するWeb サーバをプライベートサブネットで稼働する EC2 での実行となるように構成変更する
    • Auto Scaling グループで束ねてマルチ AZ 化した上で、ロードバランサーの背後で動作するように変更する。

AWS FIS の実験テンプレートの作成は AWS マネジメントコンソールを利用する方法と AWS CLI で実施する 2 種類を用意しようと考えていて、これらは私自身が作成しようと考えていました。
また、Apache のインストールから WordPress のインストーラーのダウンロード、所定のディレクトリへの配置やパーミッション設定などの userdata の組み立ても私自身で実行します。

作成の方針(Amazon Q Developer for CLI に任せる方針)

Amazon Q Developer for CLI に任せたのは以下 3 つの CloudFormation テンプレートの作成です。

  1. VPC 一式、Aurora MySQL サーバを構築し、別途用意する userdata を使うようにした EC2 インスタンスの構築するテンプレート
  2. <1> の内容から Aurora MySQL DB クラスターへのリーダーインスタンスを作成するための CloudFormation スタックの変更セット用のテンプレート
  3. <2> の後に実行する CloudFormation スタックの変更セット用の CloudFormation テンプレート。このテンプレートでは以下を実施し、EC2 インスタンスのマルチ AZ 化を行う
    • ロードバランサーの作成
    • AMI の作成と WordPress 内部設定の書き換え(WordPress ではベースとなるサイトの URL を内部設定にもつためこの値をロードバランサーの DNS 名に書き換える)
    • これらを踏まえてプライベートサブネットで EC2 インスタンスを稼働させ、Auro Scaling でマルチ AZ 化する

作成手順と所要時間

作成手順というのもはばかるのですが、前述の作成の方針にある CloudFormation テンプレートを順に構想部分で書いた内容を自然言語で伝えながら作成していきました。

<1>のテンプレートについては、初回の出力内容でエラー発生しない、問題なく動作する CloudFormation テンプレートとして生成されました。

作成された初期手順で利用するテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template to create a VPC with public and private subnets, Aurora Serverless MySQL, and EC2 instance with SSM support'

Parameters:
  VpcCidr:
    Description: CIDR block for the VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1Cidr:
    Description: CIDR block for Public Subnet 1
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2Cidr:
    Description: CIDR block for Public Subnet 2
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1Cidr:
    Description: CIDR block for Private Subnet 1
    Type: String
    Default: 10.0.3.0/24

  PrivateSubnet2Cidr:
    Description: CIDR block for Private Subnet 2
    Type: String
    Default: 10.0.4.0/24

  AvailabilityZone1:
    Description: Availability Zone 1
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1a

  AvailabilityZone2:
    Description: Availability Zone 2
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1c

  # Aurora Serverless Parameters
  DBClusterIdentifier:
    Description: DB cluster identifier
    Type: String
    Default: aurora-serverless-cluster

  DBName:
    Description: Database name
    Type: String
    Default: mydb

  DBMasterUsername:
    Description: Database master username
    Type: String
    Default: admin
    NoEcho: true

  DBMasterUserPassword:
    Description: Database master user password
    Type: String
    NoEcho: true
    MinLength: 8
    Default: Password123!

  MinCapacity:
    Description: Minimum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 1
    MinValue: 1
    MaxValue: 128

  MaxCapacity:
    Description: Maximum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 8
    MinValue: 1
    MaxValue: 128

  # EC2 Parameters
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large

  KeyPairName:
    Description: Name for the new EC2 KeyPair to be created
    Type: String
    Default: MyEC2KeyPair

  YourIp:
    Description: Your current IP address for SSH access
    Type: String
    Default: 0.0.0.0/0
    
  AmazonLinux2023AMI:
    Description: Amazon Linux 2023 AMI ID
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

  CreateSSMVPCEndpoints:
    Description: Create VPC Endpoints for SSM (recommended for private subnets)
    Type: String
    Default: 'true'
    AllowedValues:
      - 'true'
      - 'false'

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPC

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-IGW

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # Public Subnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PublicSubnet1Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PublicSubnet2Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PrivateSubnet1Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PrivateSubnet2Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  # Public Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicRT

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  # Private Route Tables
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT2

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  # Aurora Security Group
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Aurora Serverless MySQL cluster
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-SG

  # DB Subnet Group
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for Aurora Serverless MySQL cluster
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-DB-SubnetGroup

  # Aurora Serverless v2 Cluster
  AuroraDBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-mysql
      EngineVersion: 8.0.mysql_aurora.3.04.0
      EngineMode: provisioned
      ServerlessV2ScalingConfiguration:
        MinCapacity: !Ref MinCapacity
        MaxCapacity: !Ref MaxCapacity
      DBClusterIdentifier: !Ref DBClusterIdentifier
      DatabaseName: !Ref DBName
      MasterUsername: !Ref DBMasterUsername
      MasterUserPassword: !Ref DBMasterUserPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      VpcSecurityGroupIds:
        - !GetAtt AuroraSecurityGroup.GroupId
      BackupRetentionPeriod: 7
      DeletionProtection: false
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Cluster

  # Aurora Serverless v2 Instance
  AuroraDBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-instance
      DBClusterIdentifier: !Ref AuroraDBCluster
      Engine: aurora-mysql
      DBInstanceClass: db.serverless
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Instance

  # EC2 Security Group
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow HTTP access from anywhere
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref YourIp
          Description: Allow SSH access from your IP
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SG

  # EC2 Key Pair
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: !Ref KeyPairName
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-KeyPair

  # IAM Role for EC2 Instance with SSM Managed Instance
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      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/CloudWatchAgentServerPolicy
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SSM-Role

  # Instance Profile for EC2 Instance
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2InstanceRole

  # EC2 Instance
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref AmazonLinux2023AMI
      KeyName: !Ref EC2KeyPair
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds:
        - !GetAtt EC2SecurityGroup.GroupId
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo dnf update -y
          sudo dnf upgrade -y
          sudo dnf install -y httpd php php-mysqli php-pdo_mysql php-pdo php-phar php-mbstring php-zlib php-ctype php-session php-json php-xml php-libxml php-openssl php-zip php-curl php-fileinfo php-intl php-gd php-sodium

          sudo rm -rf /var/www/html/
          sudo wget https://ja.wordpress.org/latest-ja.zip
          sudo unzip latest-ja.zip -d /var/www/
          sudo mv /var/www/wordpress/ /var/www/html/
          sudo chown -R apache:ec2-user /var/www/html/
          sudo chmod 755 /var/www/html/
          sudo systemctl enable httpd --now
          sudo curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          sudo chmod +x wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp
          sudo find /var/www/html -type f -exec chmod 664 {} \;
          sudo find /var/www/html -type d -exec chmod 775 {} \;
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-Instance
        - Key: Ec2InstanceFailure
          Value: Allowed

  # SSM VPC Endpoints (Conditional)
  SSMVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  SSMMessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  EC2MessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  VPCEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPCEndpoint-SG

Conditions:
  CreateVPCEndpoints: !Equals [!Ref CreateSSMVPCEndpoints, 'true']

Outputs:
  VpcId:
    Description: VPC ID
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VpcId

  AuroraClusterEndpoint:
    Description: Aurora Serverless Cluster Endpoint
    Value: !GetAtt AuroraDBCluster.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraEndpoint

  AuroraClusterPort:
    Description: Aurora Serverless Cluster Port
    Value: !GetAtt AuroraDBCluster.Endpoint.Port
    Export:
      Name: !Sub ${AWS::StackName}-AuroraPort

  EC2InstanceId:
    Description: EC2 Instance ID
    Value: !Ref EC2Instance
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceId

  EC2PublicIP:
    Description: Public IP address of the EC2 instance
    Value: !GetAtt EC2Instance.PublicIp
    Export:
      Name: !Sub ${AWS::StackName}-EC2PublicIP

  EC2PublicDNS:
    Description: Public DNS name of the EC2 instance
    Value: !GetAtt EC2Instance.PublicDnsName
    Export:
      Name: !Sub ${AWS::StackName}-EC2PublicDNS
      
  KeyPairName:
    Description: Name of the created EC2 KeyPair
    Value: !Ref EC2KeyPair
    Export:
      Name: !Sub ${AWS::StackName}-KeyPairName

  EC2InstanceRoleArn:
    Description: ARN of the IAM Role attached to the EC2 instance
    Value: !GetAtt EC2InstanceRole.Arn
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceRoleArn

その初回に出力されたテンプレートを私自身がハンズオンイベントに初参加する立場として実行してみて無理がないかを試しながら微調整を Amazon Q に指示をして修正していきました。
その際修正したポイントはキーペアの扱いで、修正回数も 1 回のみでした。

<2> のテンプレートについても DB 部分以外に変更を与えない「変更セットであること」が伝わっていなかったこと、手順的にほしい Outputs の値を指示し直した 2 回の変更のみで済んでいます。

作成された 1 回目の変更セットとして利用するテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template to create a VPC with public and private subnets, Aurora Serverless MySQL, and EC2 instance with SSM support'

Parameters:
  VpcCidr:
    Description: CIDR block for the VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1Cidr:
    Description: CIDR block for Public Subnet 1
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2Cidr:
    Description: CIDR block for Public Subnet 2
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1Cidr:
    Description: CIDR block for Private Subnet 1
    Type: String
    Default: 10.0.3.0/24

  PrivateSubnet2Cidr:
    Description: CIDR block for Private Subnet 2
    Type: String
    Default: 10.0.4.0/24

  AvailabilityZone1:
    Description: Availability Zone 1
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1a

  AvailabilityZone2:
    Description: Availability Zone 2
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1c

  # Aurora Serverless Parameters
  DBClusterIdentifier:
    Description: DB cluster identifier
    Type: String
    Default: aurora-serverless-cluster

  DBName:
    Description: Database name
    Type: String
    Default: mydb

  DBMasterUsername:
    Description: Database master username
    Type: String
    Default: admin
    NoEcho: true

  DBMasterUserPassword:
    Description: Database master user password
    Type: String
    NoEcho: true
    MinLength: 8
    Default: Password123!

  MinCapacity:
    Description: Minimum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 1
    MinValue: 1
    MaxValue: 128

  MaxCapacity:
    Description: Maximum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 8
    MinValue: 1
    MaxValue: 128

  # EC2 Parameters
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large

  KeyPairName:
    Description: Name for the new EC2 KeyPair to be created
    Type: String
    Default: MyEC2KeyPair

  YourIp:
    Description: Your current IP address for SSH access
    Type: String
    Default: 0.0.0.0/0
    
  AmazonLinux2023AMI:
    Description: Amazon Linux 2023 AMI ID
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

  CreateSSMVPCEndpoints:
    Description: Create VPC Endpoints for SSM (recommended for private subnets)
    Type: String
    Default: 'true'
    AllowedValues:
      - 'true'
      - 'false'

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPC

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-IGW

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # Public Subnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PublicSubnet1Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PublicSubnet2Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PrivateSubnet1Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PrivateSubnet2Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  # Public Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicRT

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  # Private Route Tables
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT2

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  # Aurora Security Group
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Aurora Serverless MySQL cluster
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-SG

  # DB Subnet Group
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for Aurora Serverless MySQL cluster
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-DB-SubnetGroup

  # Aurora Serverless v2 Cluster
  AuroraDBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-mysql
      EngineVersion: 8.0.mysql_aurora.3.04.0
      EngineMode: provisioned
      ServerlessV2ScalingConfiguration:
        MinCapacity: !Ref MinCapacity
        MaxCapacity: !Ref MaxCapacity
      DBClusterIdentifier: !Ref DBClusterIdentifier
      DatabaseName: !Ref DBName
      MasterUsername: !Ref DBMasterUsername
      MasterUserPassword: !Ref DBMasterUserPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      VpcSecurityGroupIds:
        - !GetAtt AuroraSecurityGroup.GroupId
      BackupRetentionPeriod: 7
      DeletionProtection: false
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Cluster

  # Aurora Serverless v2 Instance
  AuroraDBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-instance
      DBClusterIdentifier: !Ref AuroraDBCluster
      Engine: aurora-mysql
      DBInstanceClass: db.serverless
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Instance

  # Aurora Serverless v2 Instance
  AuroraDBReaderInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-reader
      DBClusterIdentifier: !Ref AuroraDBCluster
      Engine: aurora-mysql
      DBInstanceClass: db.serverless
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Reader-Instance

  # EC2 Security Group
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow HTTP access from anywhere
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref YourIp
          Description: Allow SSH access from your IP
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SG

  # EC2 Key Pair
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: !Ref KeyPairName
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-KeyPair

  # IAM Role for EC2 Instance with SSM Managed Instance
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      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/CloudWatchAgentServerPolicy
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SSM-Role

  # Instance Profile for EC2 Instance
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2InstanceRole

  # EC2 Instance
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref AmazonLinux2023AMI
      KeyName: !Ref EC2KeyPair
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds:
        - !GetAtt EC2SecurityGroup.GroupId
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo dnf update -y
          sudo dnf upgrade -y
          sudo dnf install -y httpd php php-mysqli php-pdo_mysql php-pdo php-phar php-mbstring php-zlib php-ctype php-session php-json php-xml php-libxml php-openssl php-zip php-curl php-fileinfo php-intl php-gd php-sodium

          sudo rm -rf /var/www/html/
          sudo wget https://ja.wordpress.org/latest-ja.zip
          sudo unzip latest-ja.zip -d /var/www/
          sudo mv /var/www/wordpress/ /var/www/html/
          sudo chown -R apache:ec2-user /var/www/html/
          sudo chmod 755 /var/www/html/
          sudo systemctl enable httpd --now
          sudo curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          sudo chmod +x wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp
          sudo find /var/www/html -type f -exec chmod 664 {} \;
          sudo find /var/www/html -type d -exec chmod 775 {} \;
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-Instance
        - Key: Ec2InstanceFailure
          Value: Allowed

  # SSM VPC Endpoints (Conditional)
  SSMVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  SSMMessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  EC2MessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  VPCEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPCEndpoint-SG

Conditions:
  CreateVPCEndpoints: !Equals [!Ref CreateSSMVPCEndpoints, 'true']

Outputs:
  VpcId:
    Description: VPC ID
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VpcId

  AuroraClusterEndpoint:
    Description: Aurora Serverless Cluster Endpoint
    Value: !GetAtt AuroraDBCluster.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraEndpoint

  AuroraClusterPort:
    Description: Aurora Serverless Cluster Port
    Value: !GetAtt AuroraDBCluster.Endpoint.Port
    Export:
      Name: !Sub ${AWS::StackName}-AuroraPort

  AuroraReaderInstanceId:
    Description: Aurora Reader Instance ID
    Value: !Ref AuroraDBReaderInstance
    Export:
      Name: !Sub ${AWS::StackName}-AuroraReaderInstanceId

  AuroraClusterReaderEndpoint:
    Description: Aurora Cluster Reader Endpoint
    Value: !GetAtt AuroraDBCluster.ReadEndpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraClusterReaderEndpoint

  EC2InstanceId:
    Description: EC2 Instance ID
    Value: !Ref EC2Instance
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceId

  EC2PublicIP:
    Description: Public IP address of the EC2 instance
    Value: !GetAtt EC2Instance.PublicIp
    Export:
      Name: !Sub ${AWS::StackName}-EC2PublicIP

  EC2PublicDNS:
    Description: Public DNS name of the EC2 instance
    Value: !GetAtt EC2Instance.PublicDnsName
    Export:
      Name: !Sub ${AWS::StackName}-EC2PublicDNS
      
  KeyPairName:
    Description: Name of the created EC2 KeyPair
    Value: !Ref EC2KeyPair
    Export:
      Name: !Sub ${AWS::StackName}-KeyPairName

  EC2InstanceRoleArn:
    Description: ARN of the IAM Role attached to the EC2 instance
    Value: !GetAtt EC2InstanceRole.Arn
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceRoleArn\

<3> のテンプレートも WordPress の内部設定としてインストールされている WordPress サイトの URL を持ってしまう点が問題であると伝えました。シングル AZ 時代の EC2 インスタンスのそのままの状態を AMI 化して起動テンプレートとして作成してもロードバランサー配下で動かす場合に支障があると具体的に伝えています。

その上で、CloudFormation スタックの変更セットとして全ての変更をしたいと伝えたところ、DependsOn も適切に記載したテンプレートが生成されました。

作成された 2 回目の変更セットとして利用するテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template with high availability improvements including ALB, Auto Scaling Group, and NAT Gateway'

Parameters:
  VpcCidr:
    Description: CIDR block for the VPC
    Type: String
    Default: 10.0.0.0/16

  PublicSubnet1Cidr:
    Description: CIDR block for Public Subnet 1
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2Cidr:
    Description: CIDR block for Public Subnet 2
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1Cidr:
    Description: CIDR block for Private Subnet 1
    Type: String
    Default: 10.0.3.0/24

  PrivateSubnet2Cidr:
    Description: CIDR block for Private Subnet 2
    Type: String
    Default: 10.0.4.0/24

  AvailabilityZone1:
    Description: Availability Zone 1
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1a

  AvailabilityZone2:
    Description: Availability Zone 2
    Type: AWS::EC2::AvailabilityZone::Name
    Default: ap-northeast-1c

  # Aurora Serverless Parameters
  DBClusterIdentifier:
    Description: DB cluster identifier
    Type: String
    Default: aurora-serverless-cluster

  DBName:
    Description: Database name
    Type: String
    Default: mydb

  DBMasterUsername:
    Description: Database master username
    Type: String
    Default: admin
    NoEcho: true

  DBMasterUserPassword:
    Description: Database master user password
    Type: String
    NoEcho: true
    MinLength: 8
    Default: Password123!

  MinCapacity:
    Description: Minimum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 1
    MinValue: 1
    MaxValue: 128

  MaxCapacity:
    Description: Maximum Aurora Capacity Units (ACUs)
    Type: Number
    Default: 8
    MinValue: 1
    MaxValue: 128

  # EC2 Parameters
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large

  KeyPairName:
    Description: Name for the EC2 KeyPair
    Type: String
    Default: MyEC2KeyPair

  YourIp:
    Description: Your current IP address for SSH access
    Type: String
    Default: 0.0.0.0/0
    
  AmazonLinux2023AMI:
    Description: Amazon Linux 2023 AMI ID
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

  CreateSSMVPCEndpoints:
    Description: Create VPC Endpoints for SSM (recommended for private subnets)
    Type: String
    Default: 'true'
    AllowedValues:
      - 'true'
      - 'false'

  # Auto Scaling Parameters
  MinSize:
    Description: Minimum number of instances in the Auto Scaling Group
    Type: Number
    Default: 2
    MinValue: 1

  MaxSize:
    Description: Maximum number of instances in the Auto Scaling Group
    Type: Number
    Default: 4
    MinValue: 1

  DesiredCapacity:
    Description: Desired number of instances in the Auto Scaling Group
    Type: Number
    Default: 2
    MinValue: 1

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPC

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-IGW

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # Public Subnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PublicSubnet1Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PublicSubnet2Cidr
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone1
      CidrBlock: !Ref PrivateSubnet1Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Ref AvailabilityZone2
      CidrBlock: !Ref PrivateSubnet2Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  # Public Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicRT

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  # NAT Gateway for Private Subnets
  NatGatewayEIP1:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-NAT-EIP1

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP1.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-NAT1

  # Private Route Tables with NAT Gateway
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateRT2

  PrivateRouteToInternet1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateRouteToInternet2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

  # Aurora Security Group
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Aurora Serverless MySQL cluster
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-SG

  # DB Subnet Group
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for Aurora Serverless MySQL cluster
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-DB-SubnetGroup

  # Aurora Serverless v2 Cluster
  AuroraDBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-mysql
      EngineVersion: 8.0.mysql_aurora.3.04.0
      EngineMode: provisioned
      ServerlessV2ScalingConfiguration:
        MinCapacity: !Ref MinCapacity
        MaxCapacity: !Ref MaxCapacity
      DBClusterIdentifier: !Ref DBClusterIdentifier
      DatabaseName: !Ref DBName
      MasterUsername: !Ref DBMasterUsername
      MasterUserPassword: !Ref DBMasterUserPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      VpcSecurityGroupIds:
        - !GetAtt AuroraSecurityGroup.GroupId
      BackupRetentionPeriod: 7
      DeletionProtection: false
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Cluster

  # Aurora Serverless v2 Instance (Writer)
  AuroraDBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-instance
      DBClusterIdentifier: !Ref AuroraDBCluster
      Engine: aurora-mysql
      DBInstanceClass: db.serverless
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Instance

  # Aurora Serverless v2 Instance (Reader)
  AuroraDBReaderInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: !Sub ${DBClusterIdentifier}-reader
      DBClusterIdentifier: !Ref AuroraDBCluster
      Engine: aurora-mysql
      DBInstanceClass: db.serverless
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-Aurora-Reader-Instance
  # ALB Security Group
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for ALB
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow HTTP access from anywhere
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: Allow HTTPS access from anywhere
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-ALB-SG

  # Web Server Security Group
  WebServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Web Servers in private subnet
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
          Description: Allow HTTP access from ALB only
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref VpcCidr
          Description: Allow SSH access from within VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-WebServer-SG

  # EC2 Key Pair
  EC2KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: !Ref KeyPairName
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-KeyPair

  # IAM Role for EC2 Instance with SSM Managed Instance
  EC2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      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/CloudWatchAgentServerPolicy
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SSM-Role

  # Instance Profile for EC2 Instance
  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2InstanceRole

  # Application Load Balancer
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${AWS::StackName}-ALB
      Scheme: internet-facing
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '60'
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-ALB

  # ALB Target Group
  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${AWS::StackName}-TG
      Port: 80
      Protocol: HTTP
      VpcId: !Ref VPC
      HealthCheckPath: /
      HealthCheckIntervalSeconds: 5
      HealthCheckTimeoutSeconds: 2
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      TargetType: instance
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-TG

  # ALB Listener
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # Create AMI from existing EC2 instance
  CreateAMI:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt CreateAMIFunction.Arn
      InstanceId: !Ref EC2Instance
      Name: !Sub ${AWS::StackName}-WordPress-AMI
      Description: AMI created from WordPress EC2 instance

  # Lambda function to create AMI
  CreateAMIFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.9
      Timeout: 300
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import time

          def handler(event, context):
            if event['RequestType'] == 'Delete':
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              return

            instance_id = event['ResourceProperties']['InstanceId']
            ami_name = event['ResourceProperties']['Name']
            ami_description = event['ResourceProperties'].get('Description', '')
            
            ec2 = boto3.client('ec2')
            
            try:
              # Create AMI
              response = ec2.create_image(
                InstanceId=instance_id,
                Name=ami_name,
                Description=ami_description,
                NoReboot=True
              )
              
              ami_id = response['ImageId']
              
              # Wait for AMI to be available
              waiter = ec2.get_waiter('image_available')
              waiter.wait(ImageIds=[ami_id])
              
              responseData = {'AMI': ami_id}
              cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, ami_id)
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})

  # IAM Role for Lambda function
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CreateAMIPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:CreateImage
                  - ec2:DescribeImages
                  - ec2:CreateTags
                Resource: '*'

  # EC2 Security Group (for original instance)
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for original EC2 instance
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow HTTP access from anywhere
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref YourIp
          Description: Allow SSH access from your IP
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-SG

  # EC2 Instance (Original WordPress instance)
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref AmazonLinux2023AMI
      KeyName: !Ref EC2KeyPair
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds:
        - !GetAtt EC2SecurityGroup.GroupId
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo dnf update -y
          sudo dnf upgrade -y
          sudo dnf install -y httpd php php-mysqli php-pdo_mysql php-pdo php-phar php-mbstring php-zlib php-ctype php-session php-json php-xml php-libxml php-openssl php-zip php-curl php-fileinfo php-intl php-gd php-sodium

          sudo rm -rf /var/www/html/
          sudo wget https://ja.wordpress.org/latest-ja.zip
          sudo unzip latest-ja.zip -d /var/www/
          sudo mv /var/www/wordpress/ /var/www/html/
          sudo chown -R apache:apache /var/www/html/
          sudo chmod 755 /var/www/html/
          sudo systemctl enable httpd --now
          sudo curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          sudo chmod +x wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp
          sudo find /var/www/html -type f -exec chmod 664 {} \;
          sudo find /var/www/html -type d -exec chmod 775 {} \;
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2-Instance
        - Key: Ec2InstanceFailure
          Value: Allowed

  # Launch Template for Auto Scaling Group
  WebServerLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    DependsOn: CreateAMI
    Properties:
      LaunchTemplateName: !Sub ${AWS::StackName}-WebServer-LT
      VersionDescription: Initial version
      LaunchTemplateData:
        ImageId: !GetAtt CreateAMI.AMI
        InstanceType: !Ref InstanceType
        KeyName: !Ref KeyPairName
        SecurityGroupIds:
          - !Ref WebServerSecurityGroup
        IamInstanceProfile:
          Name: !Ref EC2InstanceProfile
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # Update system
            sudo dnf update -y
            
            # Make sure Apache and PHP are running
            sudo systemctl enable httpd --now
            
            # Update WordPress configuration with ALB DNS name
            cd /var/www/html/
            sudo wp option update home "http://${ApplicationLoadBalancer.DNSName}" --allow-root
            sudo wp option update siteurl "http://${ApplicationLoadBalancer.DNSName}" --allow-root
            
            # Ensure proper permissions
            sudo chown -R apache:apache /var/www/html/
            sudo find /var/www/html -type f -exec chmod 664 {} \;
            sudo find /var/www/html -type d -exec chmod 775 {} \;

  # Auto Scaling Group
  WebServerAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub ${AWS::StackName}-WebServer-ASG
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      DesiredCapacity: !Ref DesiredCapacity
      LaunchTemplate:
        LaunchTemplateId: !Ref WebServerLaunchTemplate
        Version: !GetAtt WebServerLaunchTemplate.LatestVersionNumber
      VPCZoneIdentifier:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      TargetGroupARNs:
        - !Ref ALBTargetGroup
      HealthCheckType: ELB
      HealthCheckGracePeriod: 60
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-WebServer
          PropagateAtLaunch: true
        - Key: Ec2InstanceFailure
          Value: Allowed
          PropagateAtLaunch: true

  # SSM VPC Endpoints (Conditional)
  SSMVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  SSMMessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  EC2MessagesVPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: CreateVPCEndpoints
    Properties:
      VpcEndpointType: Interface
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroupIds:
        - !GetAtt VPCEndpointSecurityGroup.GroupId
      PrivateDnsEnabled: true

  VPCEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !Ref VpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-VPCEndpoint-SG

Conditions:
  CreateVPCEndpoints: !Equals [!Ref CreateSSMVPCEndpoints, 'true']

Outputs:
  VpcId:
    Description: VPC ID
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VpcId

  AuroraClusterEndpoint:
    Description: Aurora Serverless Cluster Endpoint
    Value: !GetAtt AuroraDBCluster.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraEndpoint

  AuroraClusterPort:
    Description: Aurora Serverless Cluster Port
    Value: !GetAtt AuroraDBCluster.Endpoint.Port
    Export:
      Name: !Sub ${AWS::StackName}-AuroraPort

  AuroraReaderEndpoint:
    Description: Aurora Reader Instance Endpoint
    Value: !GetAtt AuroraDBReaderInstance.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraReaderEndpoint

  AuroraClusterReaderEndpoint:
    Description: Aurora Cluster Reader Endpoint
    Value: !GetAtt AuroraDBCluster.ReadEndpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-AuroraClusterReaderEndpoint

  ALBDNSName:
    Description: DNS name of the Application Load Balancer
    Value: !GetAtt ApplicationLoadBalancer.DNSName
    Export:
      Name: !Sub ${AWS::StackName}-ALBDNSName

  ALBCanonicalHostedZoneID:
    Description: Canonical Hosted Zone ID of the Application Load Balancer
    Value: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID
    Export:
      Name: !Sub ${AWS::StackName}-ALBCanonicalHostedZoneID

  WebServerAMI:
    Description: AMI ID created from the WordPress EC2 instance
    Value: !GetAtt CreateAMI.AMI
    Export:
      Name: !Sub ${AWS::StackName}-WebServerAMI

  AutoScalingGroupName:
    Description: Name of the Auto Scaling Group
    Value: !Ref WebServerAutoScalingGroup
    Export:
      Name: !Sub ${AWS::StackName}-ASGName

  EC2InstanceId:
    Description: EC2 Instance ID
    Value: !Ref EC2Instance
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceId

  KeyPairName:
    Description: Name of the created EC2 KeyPair
    Value: !Ref EC2KeyPair
    Export:
      Name: !Sub ${AWS::StackName}-KeyPairName

  EC2InstanceRoleArn:
    Description: ARN of the IAM Role attached to the EC2 instance
    Value: !GetAtt EC2InstanceRole.Arn
    Export:
      Name: !Sub ${AWS::StackName}-EC2InstanceRoleArn

ここまで完成し、実行後に CloudFormation スタックを削除するだけで環境が一式削除できるところまで確認し、課金回避のためのあと片付けも問題なさそうであることを確認しました。

出来上がった init.yaml、2 度の変更セットとその間に実行したい AWS FIS の実験と変更セットの目的を伝え、handson ディレクトリにあるシナリオのベースも作成しました。
その上で、GUI や CLI で実行しようとしていたAWS FIS の体験部分を手で差し込んでいった感じです。

作業自体は夜な夜なちまちまと作っていましたが、CloudFormation の実行時間など AWS 側を触っている時間・待ち時間を除外すると 2人日もかかっていないくらいです。

また、せっかくここまで生成 AI に頼りきったシナリオ作成をしたので、README.md やその中で使う構成図も Amazon Q Developer for CLI で生成しながらまとめました。

リポジトリの内容でハンズオンイベント参加者全員が完走しましたので、実行可能な手順となっているはずですが、イベント中以下の状況に見舞われた方がいらっしゃいました。

<発生したトラブル>
scenario-02.md の Webサーバへの接続後、インスタンス内部での操作 手順を実行後に発生しています。具体的には WordPress 側で「データベースに接続ができない」「重大な問題が発生している」などのエラーが出て、WordPress のフロント側、管理画面ともに閲覧できなくなる状況が発生しました。

その際、/var/www/html 配下から LudicrousDB プラグインに関連するファイルを削除し、手順のコードブロックごとのコピーとせず、1 行ずつ実行する形で回避できています。

所感

今回やった内容は自分の中に作りたいイメージが固まっていて、億劫がらなければ自分自身でも書けた内容だったかと考えています。

そのため、指示の出し方も明確に行えていたと考えられ、また出力された内容についても理解がしやすく採用可否を判断しやすかったと考えています。

Amazon Q Developer for CLI には、私が回避したかった「CloudFormation テンプレートを書く」という行為をそのまま請け負ってもらった感覚です。

ただ、生成 AI のクリエイティブに任せて、私自身が全く知らないジャンルについて生成されたものに対しては判断がつきかねるとは思い、少々私が誘導した感は否めません。

また、今回、要件定義のようなものをファイルで残しておらず、それを参照させた上で一連の生成をしていません。
全てを Amazon Q Developer for CLI との対話で実装していたので、私自身・生成 AI 側を問わず、それぞれの思考のプロセスがあやふやになってしまう点はデメリットだと感じました。

今回は偶然 iTerm2 のログ出力をする設定にしていたためどのようなプロンプトを投げたかを振り返ることができました。ただ、何らか記録するような労力を払わない限り出来上がったものの Know Why が残らないのは問題となる可能性を感じました。それが、結果オーライで動けばいいものだったとしても。

余談

所感の最後の Know Why が残らない懸念は、新しく公開された Kiro が推奨する Spec-Driven Development に期待が持てます。

本エントリを執筆時点でエラーが多発 1 して実装には至っていません。現状 Kiro に以下のプロンプトだけを与えて生成された requirementdesigntasks は以下のようになっています。

ぜひ Kiro でも実装して、どのように仕上がるのか、Amazon Q Developer for CLI で自然言語の対話で作成したものとどのように異なるのかを試してみたいと思っています。

Kiro に投入したプロンプト

AWS FIS(AWS Fault Injection Service)の機能を理解するための初学者向けのハンズオンシナリオを作成したいです。

Kiro が生成した資料

requirement.md

クリックで全景が表示されます。
requirement.md
# 要件文書

## 概要

AWS FIS(AWS Fault Injection Service)の機能を理解するための初学者向けハンズオンシナリオを作成します。このハンズオンでは、実際にAWSリソースに対して障害を注入し、システムの耐障害性を学習できる実践的な教材を提供します。

## 要件

### 要件1

**ユーザーストーリー:** AWS初学者として、FISの基本概念を理解したいので、分かりやすい説明とサンプルコードが欲しい

#### 受け入れ基準

1. WHEN ユーザーがハンズオン資料を開いた時 THEN システムは FISの基本概念(カオスエンジニアリング、障害注入の目的)を説明する
2. WHEN ユーザーが前提知識を確認する時 THEN システムは 必要なAWSサービスの知識レベルを明示する
3. IF ユーザーがFIS初心者の場合 THEN システムは 用語集と基本的な仕組みの説明を提供する

### 要件2

**ユーザーストーリー:** 学習者として、実際にFISを使って障害注入を体験したいので、段階的なハンズオン手順が欲しい

#### 受け入れ基準

1. WHEN ユーザーがハンズオンを開始する時 THEN システムは 必要なAWSリソースのセットアップ手順を提供する
2. WHEN ユーザーが各ステップを実行する時 THEN システムは 期待される結果と確認方法を明示する
3. WHEN ユーザーが障害注入を実行する時 THEN システムは 安全な実験環境での実行方法を説明する
4. IF 実験が失敗した場合 THEN システムは トラブルシューティングガイドを提供する

### 要件3

**ユーザーストーリー:** 実践的な学習者として、複数の障害パターンを試したいので、様々なシナリオが用意されていて欲しい

#### 受け入れ基準

1. WHEN ユーザーが基本シナリオを完了した時 THEN システムは EC2インスタンス停止の実験手順を提供する
2. WHEN ユーザーが中級シナリオに進む時 THEN システムは ネットワーク遅延注入の実験手順を提供する
3. WHEN ユーザーが上級シナリオに挑戦する時 THEN システムは RDS障害注入の実験手順を提供する
4. IF ユーザーが各シナリオを完了した場合 THEN システムは 学習した内容の振り返りと次のステップを提案する

### 要件4

**ユーザーストーリー:** コスト意識のある学習者として、実験にかかる費用を把握したいので、料金情報と節約方法が知りたい

#### 受け入れ基準

1. WHEN ユーザーがハンズオンを開始する前に THEN システムは 予想される料金と無料利用枠の情報を表示する
2. WHEN ユーザーが実験を終了する時 THEN システムは リソースのクリーンアップ手順を提供する
3. IF ユーザーが長時間実験を継続する場合 THEN システムは コスト最適化のアドバイスを提供する

### 要件5

**ユーザーストーリー:** 学習効果を高めたい受講者として、実験結果を分析し理解を深めたいので、監視とログ分析の方法が知りたい

#### 受け入れ基準

1. WHEN ユーザーが障害注入を実行した時 THEN システムは CloudWatchでの監視方法を説明する
2. WHEN ユーザーが実験結果を確認する時 THEN システムは ログの見方と分析ポイントを提供する
3. WHEN ユーザーが学習を振り返る時 THEN システムは 実験から得られた知見をまとめる方法を説明する
4. IF ユーザーが応用的な分析を行いたい場合 THEN システムは X-Rayトレーシングとの連携方法を提案する

design.md

クリックで全景が表示されます。
design.md
# 設計文書

## 概要

AWS FIS(AWS Fault Injection Service)の初学者向けハンズオンシナリオの設計文書です。段階的な学習アプローチを採用し、理論から実践まで体系的に学習できる構成とします。

## アーキテクチャ

### 学習フロー設計

	```mermaid
	graph TD
    	A[事前準備] --> B[基礎知識学習]
    	B --> C[環境セットアップ]
	    C --> D[基本シナリオ: EC2停止]
    	D --> E[中級シナリオ: ネットワーク遅延]
    	E --> F[上級シナリオ: RDS障害]
    	F --> G[監視・分析]
    	G --> H[クリーンアップ]
    	H --> I[振り返り・次のステップ]
	```

### システム構成

	```mermaid
	graph LR
	    subgraph "実験環境"
	        EC2[EC2インスタンス]
	        ALB[Application Load Balancer]
	        RDS[(RDS Database)]
	        VPC[VPC/サブネット]
	    end
	    
	    subgraph "監視・ログ"
	        CW[CloudWatch]
	        XRay[X-Ray]
	        Logs[CloudWatch Logs]
	    end
	    
	    subgraph "FIS実験"
	        FIS[AWS FIS]
	        Templates[実験テンプレート]
	    end
	    
	    FIS --> EC2
	    FIS --> ALB
	    FIS --> RDS
	    EC2 --> CW
	    ALB --> CW
	    RDS --> CW
	    EC2 --> Logs
	```

## コンポーネントと インターフェース

### 1. ドキュメント構造

	```
	aws-fis-hands-on/
	├── README.md                    # 全体概要とナビゲーション
	├── docs/
	│   ├── 01-prerequisites.md      # 前提知識・準備
	│   ├── 02-fis-basics.md        # FIS基礎知識
	│   ├── 03-environment-setup.md # 環境構築
	│   ├── 04-basic-scenario.md    # 基本シナリオ
	│   ├── 05-network-scenario.md  # ネットワークシナリオ
	│   ├── 06-database-scenario.md # データベースシナリオ
	│   ├── 07-monitoring.md        # 監視・分析
	│   ├── 08-cleanup.md           # クリーンアップ
	│   └── 09-next-steps.md        # 振り返り・発展
	├── templates/
	│   ├── cloudformation/         # インフラ構築テンプレート
	│   ├── fis-experiments/        # FIS実験テンプレート
	│   └── sample-apps/            # サンプルアプリケーション
	├── scripts/
	│   ├── setup.sh               # 環境セットアップスクリプト
	│   ├── deploy-app.sh          # アプリケーションデプロイ
	│   └── cleanup.sh             # リソースクリーンアップ
	└── assets/
	    ├── images/                # 図表・スクリーンショット
	    └── diagrams/              # アーキテクチャ図
	```

### 2. 実験シナリオ設計

#### 基本シナリオ: EC2インスタンス停止
- **目的**: FISの基本操作を学習
- **対象リソース**: 単一EC2インスタンス
- **実験内容**: インスタンス強制停止
- **学習ポイント**: 
  - FIS実験テンプレートの作成
  - 実験実行とモニタリング
  - 自動復旧の確認

#### 中級シナリオ: ネットワーク遅延注入
- **目的**: ネットワーク障害の影響を理解
- **対象リソース**: EC2インスタンス(複数)
- **実験内容**: パケット遅延・損失の注入
- **学習ポイント**:
  - アプリケーションレベルでの影響確認
  - タイムアウト設定の重要性
  - 負荷分散の効果

#### 上級シナリオ: RDS障害注入
- **目的**: データベース障害への対応を学習
- **対象リソース**: RDS Multi-AZ構成
- **実験内容**: プライマリDB強制フェイルオーバー
- **学習ポイント**:
  - データベース可用性設計
  - アプリケーションの接続処理
  - 復旧時間の測定

### 3. サンプルアプリケーション設計

#### アプリケーション仕様
- **言語**: Python (Flask) または Node.js (Express)
- **機能**:
  - 簡単なWeb API(ヘルスチェック、データ取得)
  - データベース接続(読み書き)
  - ログ出力(構造化ログ)
  - メトリクス出力(カスタムメトリクス)

#### インフラ構成
- **EC2**: t3.micro × 2台(Multi-AZ配置)
- **ALB**: アプリケーション負荷分散
- **RDS**: MySQL 8.0(Multi-AZ、t3.micro)
- **VPC**: パブリック/プライベートサブネット構成

## データモデル

### FIS実験テンプレート構造

	```json
	{
	  "description": "実験の説明",
	  "roleArn": "FIS実行ロールのARN",
	  "actions": {
	    "action-id": {
	      "actionId": "aws:ec2:stop-instances",
	      "parameters": {
	        "startInstancesAfterDuration": "PT10M"
	      },
	      "targets": {
	        "Instances": "target-instances"
	      }
	    }
	  },
	  "targets": {
	    "target-instances": {
	      "resourceType": "aws:ec2:instance",
	      "resourceTags": {
	        "Environment": "fis-hands-on"
	      },
	      "selectionMode": "PERCENT(50)"
	    }
	  },
	  "stopConditions": [
	    {
	      "source": "aws:cloudwatch:alarm",
	      "value": "alarm-arn"
	    }
	  ]
	}
	```

### 監視データ構造

	```yaml
	metrics:
	  - name: "application.response_time"
	    unit: "milliseconds"
	    dimensions:
	      - endpoint
	      - instance_id
	  - name: "application.error_rate"
	    unit: "percent"
	    dimensions:
	      - endpoint
	      - error_type

	logs:
	  - name: "application.log"
	    format: "json"
	    fields:
	      - timestamp
	      - level
	      - message
	      - request_id
	      - duration
	```

## エラーハンドリング

### 1. 実験実行時のエラー対応

#### 権限エラー
- **症状**: FIS実験が開始できない
- **原因**: IAMロールの権限不足
- **対処**: 必要な権限の追加手順を提供

#### リソース不足エラー
- **症状**: EC2インスタンス起動失敗
- **原因**: 利用可能なインスタンスタイプなし
- **対処**: 代替インスタンスタイプの提案

#### ネットワークエラー
- **症状**: アプリケーションへの接続失敗
- **原因**: セキュリティグループ設定ミス
- **対処**: 設定確認チェックリスト

### 2. 実験中の安全装置

#### 自動停止条件
- CloudWatchアラームによる実験停止
- 最大実行時間の制限
- 手動停止手順の明示

#### ロールバック手順
- 各実験ステップでの復旧方法
- 緊急時の完全リセット手順
- データ損失防止のための事前バックアップ

## テスト戦略

### 1. ドキュメントテスト
- **手順の正確性**: 各ステップの実行可能性確認
- **スクリーンショット**: UI変更に対する画像更新
- **リンク確認**: 外部リンクの有効性チェック

### 2. 実験テスト
- **基本シナリオ**: 新規AWS環境での実行テスト
- **エラーケース**: 意図的な設定ミスでの動作確認
- **復旧テスト**: 各実験後の正常復旧確認

### 3. コスト検証
- **料金計算**: 実際の実行コスト測定
- **最適化**: 不要なリソース使用の削減
- **無料枠活用**: 可能な限り無料枠内での実行

### 4. ユーザビリティテスト
- **初学者レビュー**: AWS初心者による手順実行
- **フィードバック収集**: 理解しにくい箇所の特定
- **改善反映**: ユーザビリティ向上のための修正

## 実装上の考慮事項

### セキュリティ
- 実験環境の分離(本番環境への影響防止)
- 最小権限の原則に基づくIAMロール設計
- 機密情報のハードコーディング防止

### 可用性
- 実験失敗時の自動復旧メカニズム
- 複数リージョンでの動作確認
- 依存サービスの障害対応

### 保守性
- テンプレートのバージョン管理
- ドキュメントの定期更新プロセス
- コミュニティフィードバックの反映仕組み

### 拡張性
- 新しい実験シナリオの追加容易性
- 他のAWSサービスとの連携拡張
- 多言語対応の考慮

tasks.md

クリックで全景が表示されます。
tasks.md
# 実装計画

- [ ] 1. プロジェクト基盤とドキュメント構造の作成
  - プロジェクトのディレクトリ構造を作成し、基本的なREADMEファイルを実装
  - 全体のナビゲーション構造と学習フローを明確に定義
  - _要件: 1.1, 1.2_

- [ ] 2. 前提知識と基礎概念ドキュメントの作成
  - [ ] 2.1 前提知識・準備ドキュメントの実装
    - 必要なAWSサービスの知識レベルを明示するドキュメントを作成
    - 事前準備(AWSアカウント、CLI設定等)の手順を実装
    - _要件: 1.1, 1.3_
  
  - [ ] 2.2 FIS基礎知識ドキュメントの実装
    - カオスエンジニアリングと障害注入の基本概念を説明するドキュメントを作成
    - FISの仕組みと用語集を含む初心者向け説明を実装
    - _要件: 1.1, 1.3_

- [ ] 3. インフラストラクチャテンプレートの実装
  - [ ] 3.1 CloudFormationテンプレートの作成
    - VPC、サブネット、セキュリティグループの基本構成を定義するテンプレートを実装
    - EC2インスタンス(Multi-AZ)とALBの構成テンプレートを作成
    - _要件: 2.1, 2.2_
  
  - [ ] 3.2 RDSデータベース構成の実装
    - Multi-AZ構成のRDS MySQL設定テンプレートを作成
    - データベース接続とセキュリティ設定を実装
    - _要件: 3.3_

- [ ] 4. サンプルアプリケーションの実装
  - [ ] 4.1 基本Webアプリケーションの作成
    - Flask(Python)またはExpress(Node.js)ベースの簡単なWeb APIを実装
    - ヘルスチェックエンドポイントとデータ取得APIを作成
    - _要件: 2.1, 2.2_
  
  - [ ] 4.2 データベース連携機能の実装
    - RDSとの接続処理と基本的なCRUD操作を実装
    - 接続エラーハンドリングとリトライ機能を追加
    - _要件: 3.3_
  
  - [ ] 4.3 ログとメトリクス出力の実装
    - 構造化ログ出力(JSON形式)を実装
    - CloudWatchカスタムメトリクスの送信機能を追加
    - _要件: 5.1, 5.2_

- [ ] 5. 環境セットアップ自動化の実装
  - [ ] 5.1 セットアップスクリプトの作成
    - インフラ構築を自動化するシェルスクリプトを実装
    - 必要なAWSリソースの作成と設定を自動化
    - _要件: 2.1, 2.2_
  
  - [ ] 5.2 アプリケーションデプロイスクリプトの実装
    - EC2インスタンスへのアプリケーション自動デプロイを実装
    - 依存関係のインストールと設定を自動化
    - _要件: 2.1, 2.2_

- [ ] 6. FIS実験テンプレートの実装
  - [ ] 6.1 基本シナリオ(EC2停止)テンプレートの作成
    - EC2インスタンス停止実験のJSONテンプレートを実装
    - 安全装置(停止条件、自動復旧)を含む設定を作成
    - _要件: 2.1, 2.2, 2.4_
  
  - [ ] 6.2 中級シナリオ(ネットワーク遅延)テンプレートの作成
    - ネットワーク遅延・パケット損失注入のテンプレートを実装
    - 複数インスタンスを対象とした実験設定を作成
    - _要件: 3.1, 3.2_
  
  - [ ] 6.3 上級シナリオ(RDS障害)テンプレートの作成
    - RDS強制フェイルオーバー実験のテンプレートを実装
    - Multi-AZ構成での障害注入設定を作成
    - _要件: 3.3, 3.4_

- [ ] 7. ハンズオン手順ドキュメントの実装
  - [ ] 7.1 環境構築手順ドキュメントの作成
    - ステップバイステップの環境セットアップ手順を実装
    - 各ステップの期待される結果と確認方法を明記
    - _要件: 2.1, 2.2_
  
  - [ ] 7.2 基本実験シナリオ手順の実装
    - EC2停止実験の詳細な実行手順を作成
    - 実験結果の確認方法とトラブルシューティングを実装
    - _要件: 2.1, 2.2, 2.4_
  
  - [ ] 7.3 中級・上級実験シナリオ手順の実装
    - ネットワーク遅延とRDS障害実験の手順を作成
    - 各実験の学習ポイントと分析方法を実装
    - _要件: 3.1, 3.2, 3.3, 3.4_

- [ ] 8. 監視・分析機能の実装
  - [ ] 8.1 CloudWatch監視設定の作成
    - アプリケーションメトリクスとアラーム設定を実装
    - ダッシュボード作成用のテンプレートを作成
    - _要件: 5.1, 5.2_
  
  - [ ] 8.2 ログ分析手順の実装
    - CloudWatch Logsでの分析方法を説明するドキュメントを作成
    - 実験結果の分析ポイントと可視化方法を実装
    - _要件: 5.2, 5.3_
  
  - [ ] 8.3 X-Rayトレーシング連携の実装
    - アプリケーションでのX-Ray設定を実装
    - 分散トレーシングによる障害影響分析の手順を作成
    - _要件: 5.4_

- [ ] 9. コスト管理とクリーンアップの実装
  - [ ] 9.1 料金情報ドキュメントの作成
    - 各実験シナリオの予想コストを計算し文書化
    - 無料利用枠の活用方法と節約のコツを実装
    - _要件: 4.1, 4.3_
  
  - [ ] 9.2 自動クリーンアップスクリプトの実装
    - 全AWSリソースを安全に削除するスクリプトを作成
    - 削除前の確認機能と段階的削除を実装
    - _要件: 4.2_

- [ ] 10. エラーハンドリングとトラブルシューティングの実装
  - [ ] 10.1 トラブルシューティングガイドの作成
    - よくある問題と解決方法をまとめたドキュメントを実装
    - 権限エラー、リソース不足等の対処法を作成
    - _要件: 2.4_
  
  - [ ] 10.2 実験失敗時の復旧手順の実装
    - 各実験シナリオでの緊急停止・復旧手順を作成
    - データ損失防止のためのバックアップ・復元手順を実装
    - _要件: 2.4_

- [ ] 11. 学習効果向上機能の実装
  - [ ] 11.1 振り返り・まとめドキュメントの作成
    - 各実験から得られる知見をまとめるテンプレートを実装
    - 学習内容の定着を促進するチェックリストを作成
    - _要件: 5.3_
  
  - [ ] 11.2 次のステップガイドの実装
    - より高度なカオスエンジニアリング手法への発展ガイドを作成
    - 実際のプロダクション環境での適用に向けた推奨事項を実装
    - _要件: 3.4_

- [ ] 12. 統合テストとドキュメント最終化
  - [ ] 12.1 エンドツーエンドテストの実装
    - 新規AWS環境での全手順実行テストを実施
    - 各実験シナリオの動作確認とコスト検証を実行
    - _要件: 2.2, 2.4_
  
  - [ ] 12.2 ドキュメント品質向上の実装
    - スクリーンショット更新と外部リンク確認を実施
    - 初学者レビューに基づく改善点の反映を実装
    - _要件: 1.1, 1.2, 1.3_
  1. 発生しているエラー The model you've selected is experiencing a high volume of traffic. Try changing the model and re-running your prompt.

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?