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 テンプレートの作成です。
- VPC 一式、Aurora MySQL サーバを構築し、別途用意する userdata を使うようにした EC2 インスタンスの構築するテンプレート
- <1> の内容から Aurora MySQL DB クラスターへのリーダーインスタンスを作成するための CloudFormation スタックの変更セット用のテンプレート
- <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 に以下のプロンプトだけを与えて生成された requirement
、design
、 tasks
は以下のようになっています。
ぜひ Kiro でも実装して、どのように仕上がるのか、Amazon Q Developer for CLI で自然言語の対話で作成したものとどのように異なるのかを試してみたいと思っています。
Kiro に投入したプロンプト
AWS FIS(AWS Fault Injection Service)の機能を理解するための初学者向けのハンズオンシナリオを作成したいです。
Kiro が生成した資料
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
クリックで全景が表示されます。
# 設計文書
## 概要
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
クリックで全景が表示されます。
# 実装計画
- [ ] 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_
-
発生しているエラー
The model you've selected is experiencing a high volume of traffic. Try changing the model and re-running your prompt.
↩