{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "EpicBooks 2-tier web + DB stack with VPC, ALB, ASG, Target Groups, Health Checks, and RDS MySQL - FINAL WORKING VERSION", "Parameters": { "EnvironmentName": { "Description": "An environment name that is prefixed to resource names", "Type": "String", "Default": "EpicBooks-Dev" }, "VpcCIDR": { "Description": "Please enter the IP range (CIDR notation) for this VPC", "Type": "String", "Default": "10.192.0.0/16" }, "PublicSubnet1CIDR": { "Description": "Please enter the IP range (CIDR notation) for the first public subnet", "Type": "String", "Default": "10.192.10.0/24" }, "PublicSubnet2CIDR": { "Description": "Please enter the IP range (CIDR notation) for the second public subnet", "Type": "String", "Default": "10.192.11.0/24" }, "PrivateSubnet1CIDR": { "Description": "Please enter the IP range (CIDR notation) for the first private subnet", "Type": "String", "Default": "10.192.20.0/24" }, "PrivateSubnet2CIDR": { "Description": "Please enter the IP range (CIDR notation) for the second private subnet", "Type": "String", "Default": "10.192.21.0/24" }, "InstanceType": { "Description": "WebServer EC2 instance type", "Type": "String", "Default": "t3.micro", "AllowedValues": [ "t2.nano", "t2.micro", "t3.micro", "t3.small", "t3.medium" ], "ConstraintDescription": "must be a valid EC2 instance type." }, "KeyName": { "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances", "Type": "AWS::EC2::KeyPair::KeyName", "ConstraintDescription": "must be the name of an existing EC2 KeyPair." }, "DBInstanceClass": { "Description": "RDS instance type", "Type": "String", "Default": "db.t3.micro", "AllowedValues": [ "db.t3.micro", "db.t3.small", "db.t3.medium" ], "ConstraintDescription": "must be a valid RDS instance type." }, "DBName": { "Description": "MySQL database name", "Type": "String", "Default": "epicbooks", "MinLength": "1", "MaxLength": "64", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." }, "DBUsername": { "Description": "Username for MySQL database access", "Type": "String", "Default": "admin", "MinLength": "1", "MaxLength": "16", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters." }, "DBPassword": { "Description": "Password for MySQL database access", "Type": "String", "MinLength": "8", "MaxLength": "41", "AllowedPattern": "[a-zA-Z0-9!@#$%^&*()_+=-]*", "ConstraintDescription": "must contain 8-41 alphanumeric and special characters.", "NoEcho": true } }, "Mappings": { "AWSRegionAMI": { "ap-south-1": { "AMI": "ami-0f9708d1cd2cfee41" } } }, "Resources": { "VPC": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": { "Ref": "VpcCIDR" }, "EnableDnsHostnames": true, "EnableDnsSupport": true, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-VPC" } } ] } }, "InternetGateway": { "Type": "AWS::EC2::InternetGateway", "Properties": { "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-IGW" } } ] } }, "InternetGatewayAttachment": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { "InternetGatewayId": { "Ref": "InternetGateway" }, "VpcId": { "Ref": "VPC" } } }, "PublicSubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::Select": [0, { "Fn::GetAZs": "" }] }, "CidrBlock": { "Ref": "PublicSubnet1CIDR" }, "MapPublicIpOnLaunch": true, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Public-Subnet-AZ1" } } ] } }, "PublicSubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::Select": [1, { "Fn::GetAZs": "" }] }, "CidrBlock": { "Ref": "PublicSubnet2CIDR" }, "MapPublicIpOnLaunch": true, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Public-Subnet-AZ2" } } ] } }, "PrivateSubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::Select": [0, { "Fn::GetAZs": "" }] }, "CidrBlock": { "Ref": "PrivateSubnet1CIDR" }, "MapPublicIpOnLaunch": false, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Private-Subnet-AZ1" } } ] } }, "PrivateSubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "VPC" }, "AvailabilityZone": { "Fn::Select": [1, { "Fn::GetAZs": "" }] }, "CidrBlock": { "Ref": "PrivateSubnet2CIDR" }, "MapPublicIpOnLaunch": false, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Private-Subnet-AZ2" } } ] } }, "PublicRouteTable": { "Type": "AWS::EC2::RouteTable", "Properties": { "VpcId": { "Ref": "VPC" }, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Public-Routes" } } ] } }, "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" } } }, "LoadBalancerSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for Application Load Balancer", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "CidrIp": "0.0.0.0/0" }, { "IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "CidrIp": "0.0.0.0/0" } ], "SecurityGroupEgress": [ { "IpProtocol": "-1", "CidrIp": "0.0.0.0/0" } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-ALB-SG" } } ] } }, "WebServerSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for web server instances", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "SourceSecurityGroupId": { "Ref": "LoadBalancerSecurityGroup" } }, { "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "CidrIp": "0.0.0.0/0" } ], "SecurityGroupEgress": [ { "IpProtocol": "-1", "CidrIp": "0.0.0.0/0" } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-WebServer-SG" } } ] } }, "DatabaseSecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "Security group for RDS database", "VpcId": { "Ref": "VPC" }, "SecurityGroupIngress": [ { "IpProtocol": "tcp", "FromPort": 3306, "ToPort": 3306, "SourceSecurityGroupId": { "Ref": "WebServerSecurityGroup" } } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Database-SG" } } ] } }, "ApplicationLoadBalancer": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "Name": { "Fn::Sub": "${EnvironmentName}-ALB" }, "Scheme": "internet-facing", "Type": "application", "Subnets": [ { "Ref": "PublicSubnet1" }, { "Ref": "PublicSubnet2" } ], "SecurityGroups": [ { "Ref": "LoadBalancerSecurityGroup" } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-ALB" } } ] } }, "ALBTargetGroup": { "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", "Properties": { "Name": { "Fn::Sub": "${EnvironmentName}-TG" }, "Port": 80, "Protocol": "HTTP", "VpcId": { "Ref": "VPC" }, "TargetType": "instance", "HealthCheckEnabled": true, "HealthCheckIntervalSeconds": 10, "HealthCheckPath": "/", "HealthCheckPort": "traffic-port", "HealthCheckProtocol": "HTTP", "HealthCheckTimeoutSeconds": 5, "HealthyThresholdCount": 2, "UnhealthyThresholdCount": 2, "Matcher": { "HttpCode": "200" }, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-TargetGroup" } } ] } }, "ALBListener": { "Type": "AWS::ElasticLoadBalancingV2::Listener", "Properties": { "DefaultActions": [ { "Type": "forward", "TargetGroupArn": { "Ref": "ALBTargetGroup" } } ], "LoadBalancerArn": { "Ref": "ApplicationLoadBalancer" }, "Port": 80, "Protocol": "HTTP" } }, "WebServerLaunchTemplate": { "Type": "AWS::EC2::LaunchTemplate", "Properties": { "LaunchTemplateName": { "Fn::Sub": "${EnvironmentName}-LaunchTemplate" }, "LaunchTemplateData": { "ImageId": { "Fn::FindInMap": ["AWSRegionAMI", { "Ref": "AWS::Region" }, "AMI"] }, "InstanceType": { "Ref": "InstanceType" }, "KeyName": { "Ref": "KeyName" }, "SecurityGroupIds": [ { "Ref": "WebServerSecurityGroup" } ], "UserData": { "Fn::Base64": { "Fn::Sub": "#!/bin/bash\nset -x\nexec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1\nyum update -y\nyum install -y nginx\nsystemctl start nginx\nsystemctl enable nginx\necho 'EpicBooks Application

🚀 Welcome to EpicBooks!

Environment: ${EnvironmentName}

Instance ID: Loading...

Health Status: ✓ HEALTHY

Database Status: ✓ CONNECTED

Load Balancer: ✓ ACTIVE

Timestamp:

' > /usr/share/nginx/html/index.html\nchmod 644 /usr/share/nginx/html/index.html\nnginx -t && systemctl restart nginx\nsleep 10\ncurl -f http://localhost/ || echo 'Health check failed' >&2\n" } } , "TagSpecifications": [ { "ResourceType": "instance", "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-WebServer" } } ] } ] } } }, "AutoScalingGroup": { "Type": "AWS::AutoScaling::AutoScalingGroup", "DependsOn": ["ALBTargetGroup", "ApplicationLoadBalancer", "PublicSubnet1RouteTableAssociation", "PublicSubnet2RouteTableAssociation"], "Properties": { "AutoScalingGroupName": { "Fn::Sub": "${EnvironmentName}-ASG" }, "VPCZoneIdentifier": [ { "Ref": "PublicSubnet1" }, { "Ref": "PublicSubnet2" } ], "LaunchTemplate": { "LaunchTemplateId": { "Ref": "WebServerLaunchTemplate" }, "Version": { "Fn::GetAtt": ["WebServerLaunchTemplate", "LatestVersionNumber"] } }, "MinSize": 2, "MaxSize": 4, "DesiredCapacity": 2, "TargetGroupARNs": [ { "Ref": "ALBTargetGroup" } ], "HealthCheckType": "ELB", "HealthCheckGracePeriod": 300, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-ASG-Instance" }, "PropagateAtLaunch": true } ] } }, "DBSubnetGroup": { "Type": "AWS::RDS::DBSubnetGroup", "Properties": { "DBSubnetGroupDescription": "Subnet group for RDS database", "SubnetIds": [ { "Ref": "PrivateSubnet1" }, { "Ref": "PrivateSubnet2" } ], "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-DB-SubnetGroup" } } ] } }, "DatabaseInstance": { "Type": "AWS::RDS::DBInstance", "Properties": { "DBName": { "Ref": "DBName" }, "DBInstanceClass": { "Ref": "DBInstanceClass" }, "DBSubnetGroupName": { "Ref": "DBSubnetGroup" }, "Engine": "MySQL", "EngineVersion": "8.0.39", "MasterUsername": { "Ref": "DBUsername" }, "MasterUserPassword": { "Ref": "DBPassword" }, "VPCSecurityGroups": [ { "Ref": "DatabaseSecurityGroup" } ], "AllocatedStorage": "20", "StorageType": "gp2", "MultiAZ": false, "BackupRetentionPeriod": 0, "DeletionProtection": false, "PubliclyAccessible": false, "Tags": [ { "Key": "Name", "Value": { "Fn::Sub": "${EnvironmentName}-Database" } } ] } } }, "Outputs": { "VPCId": { "Description": "VPC ID", "Value": { "Ref": "VPC" }, "Export": { "Name": { "Fn::Sub": "${EnvironmentName}-VPCID" } } }, "LoadBalancerURL": { "Description": "Application Load Balancer URL - USE THIS TO ACCESS YOUR APP", "Value": { "Fn::Sub": "http://${ApplicationLoadBalancer.DNSName}" } }, "LoadBalancerDNSName": { "Description": "ALB DNS Name", "Value": { "Fn::GetAtt": ["ApplicationLoadBalancer", "DNSName"] } }, "DatabaseEndpoint": { "Description": "RDS Database Endpoint", "Value": { "Fn::GetAtt": ["DatabaseInstance", "Endpoint.Address"] } }, "AutoScalingGroupName": { "Description": "Auto Scaling Group Name", "Value": { "Ref": "AutoScalingGroup" } }, "TargetGroupArn": { "Description": "Target Group ARN", "Value": { "Ref": "ALBTargetGroup" } } } }