Create a VPC with private subnets and NAT gateways using the CLI - AWS SDK Code Examples

There are more AWS SDK examples available in the AWS Doc SDK Examples GitHub repo.

Create a VPC with private subnets and NAT gateways using the CLI

The following code example shows how to:

  • Create a VPC with private subnets and NAT gateways using the CLI.

  • Set up the necessary components including VPC, subnets, route tables, and NAT gateways.

  • Configure security groups and IAM roles for proper access and security.

  • Use CLI commands to automate the creation and configuration of these resources.

Bash
AWS CLI with Bash script
Note

There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials repository.

#!/bin/bash # VPC with Private Subnets and NAT Gateways (IMDSv2 Compliant Version) # This script creates a VPC with public and private subnets in two Availability Zones, # NAT gateways, an internet gateway, route tables, a VPC endpoint for S3, # security groups, a launch template, an Auto Scaling group, and an Application Load Balancer. # Set up logging LOG_FILE="vpc-private-subnets-nat.log" exec > >(tee -a "$LOG_FILE") 2>&1 # Cleanup function to delete all created resources cleanup_resources() { echo "Cleaning up resources..." # Delete Auto Scaling group if it exists if [ -n "${ASG_NAME:-}" ]; then echo "Deleting Auto Scaling group: $ASG_NAME" aws autoscaling delete-auto-scaling-group --auto-scaling-group-name "$ASG_NAME" --force-delete echo "Waiting for Auto Scaling group to be deleted..." aws autoscaling wait auto-scaling-groups-deleted --auto-scaling-group-names "$ASG_NAME" fi # Delete load balancer if it exists if [ -n "${LB_ARN:-}" ]; then echo "Deleting load balancer: $LB_ARN" aws elbv2 delete-load-balancer --load-balancer-arn "$LB_ARN" # Wait for load balancer to be deleted sleep 30 fi # Delete target group if it exists if [ -n "${TARGET_GROUP_ARN:-}" ]; then echo "Deleting target group: $TARGET_GROUP_ARN" aws elbv2 delete-target-group --target-group-arn "$TARGET_GROUP_ARN" fi # Delete launch template if it exists if [ -n "${LAUNCH_TEMPLATE_NAME:-}" ]; then echo "Deleting launch template: $LAUNCH_TEMPLATE_NAME" aws ec2 delete-launch-template --launch-template-name "$LAUNCH_TEMPLATE_NAME" fi # Delete NAT Gateways if they exist if [ -n "${NAT_GW1_ID:-}" ]; then echo "Deleting NAT Gateway 1: $NAT_GW1_ID" aws ec2 delete-nat-gateway --nat-gateway-id "$NAT_GW1_ID" fi if [ -n "${NAT_GW2_ID:-}" ]; then echo "Deleting NAT Gateway 2: $NAT_GW2_ID" aws ec2 delete-nat-gateway --nat-gateway-id "$NAT_GW2_ID" fi # Wait for NAT Gateways to be deleted if [ -n "${NAT_GW1_ID:-}" ] || [ -n "${NAT_GW2_ID:-}" ]; then echo "Waiting for NAT Gateways to be deleted..." sleep 60 fi # Release Elastic IPs if they exist if [ -n "${EIP1_ALLOC_ID:-}" ]; then echo "Releasing Elastic IP 1: $EIP1_ALLOC_ID" aws ec2 release-address --allocation-id "$EIP1_ALLOC_ID" fi if [ -n "${EIP2_ALLOC_ID:-}" ]; then echo "Releasing Elastic IP 2: $EIP2_ALLOC_ID" aws ec2 release-address --allocation-id "$EIP2_ALLOC_ID" fi # Delete VPC endpoint if it exists if [ -n "${VPC_ENDPOINT_ID:-}" ]; then echo "Deleting VPC endpoint: $VPC_ENDPOINT_ID" aws ec2 delete-vpc-endpoints --vpc-endpoint-ids "$VPC_ENDPOINT_ID" fi # Delete security groups if they exist if [ -n "${APP_SG_ID:-}" ]; then echo "Deleting application security group: $APP_SG_ID" aws ec2 delete-security-group --group-id "$APP_SG_ID" fi if [ -n "${LB_SG_ID:-}" ]; then echo "Deleting load balancer security group: $LB_SG_ID" aws ec2 delete-security-group --group-id "$LB_SG_ID" fi # Detach and delete Internet Gateway if it exists if [ -n "${IGW_ID:-}" ] && [ -n "${VPC_ID:-}" ]; then echo "Detaching Internet Gateway: $IGW_ID from VPC: $VPC_ID" aws ec2 detach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" echo "Deleting Internet Gateway: $IGW_ID" aws ec2 delete-internet-gateway --internet-gateway-id "$IGW_ID" fi # Delete route table associations and route tables if they exist if [ -n "${PUBLIC_RT_ASSOC1_ID:-}" ]; then echo "Disassociating public route table from subnet 1: $PUBLIC_RT_ASSOC1_ID" aws ec2 disassociate-route-table --association-id "$PUBLIC_RT_ASSOC1_ID" fi if [ -n "${PUBLIC_RT_ASSOC2_ID:-}" ]; then echo "Disassociating public route table from subnet 2: $PUBLIC_RT_ASSOC2_ID" aws ec2 disassociate-route-table --association-id "$PUBLIC_RT_ASSOC2_ID" fi if [ -n "${PRIVATE_RT1_ASSOC_ID:-}" ]; then echo "Disassociating private route table 1: $PRIVATE_RT1_ASSOC_ID" aws ec2 disassociate-route-table --association-id "$PRIVATE_RT1_ASSOC_ID" fi if [ -n "${PRIVATE_RT2_ASSOC_ID:-}" ]; then echo "Disassociating private route table 2: $PRIVATE_RT2_ASSOC_ID" aws ec2 disassociate-route-table --association-id "$PRIVATE_RT2_ASSOC_ID" fi if [ -n "${PUBLIC_RT_ID:-}" ]; then echo "Deleting public route table: $PUBLIC_RT_ID" aws ec2 delete-route-table --route-table-id "$PUBLIC_RT_ID" fi if [ -n "${PRIVATE_RT1_ID:-}" ]; then echo "Deleting private route table 1: $PRIVATE_RT1_ID" aws ec2 delete-route-table --route-table-id "$PRIVATE_RT1_ID" fi if [ -n "${PRIVATE_RT2_ID:-}" ]; then echo "Deleting private route table 2: $PRIVATE_RT2_ID" aws ec2 delete-route-table --route-table-id "$PRIVATE_RT2_ID" fi # Delete subnets if they exist if [ -n "${PUBLIC_SUBNET1_ID:-}" ]; then echo "Deleting public subnet 1: $PUBLIC_SUBNET1_ID" aws ec2 delete-subnet --subnet-id "$PUBLIC_SUBNET1_ID" fi if [ -n "${PUBLIC_SUBNET2_ID:-}" ]; then echo "Deleting public subnet 2: $PUBLIC_SUBNET2_ID" aws ec2 delete-subnet --subnet-id "$PUBLIC_SUBNET2_ID" fi if [ -n "${PRIVATE_SUBNET1_ID:-}" ]; then echo "Deleting private subnet 1: $PRIVATE_SUBNET1_ID" aws ec2 delete-subnet --subnet-id "$PRIVATE_SUBNET1_ID" fi if [ -n "${PRIVATE_SUBNET2_ID:-}" ]; then echo "Deleting private subnet 2: $PRIVATE_SUBNET2_ID" aws ec2 delete-subnet --subnet-id "$PRIVATE_SUBNET2_ID" fi # Delete VPC if it exists if [ -n "${VPC_ID:-}" ]; then echo "Deleting VPC: $VPC_ID" aws ec2 delete-vpc --vpc-id "$VPC_ID" fi echo "Cleanup completed." } # Error handling function handle_error() { echo "ERROR: $1" echo "Attempting to clean up resources..." cleanup_resources exit 1 } # Function to check command success check_command() { if [ $? -ne 0 ]; then handle_error "$1" fi } # Generate a random identifier for resource names RANDOM_ID=$(openssl rand -hex 4) echo "Using random identifier: $RANDOM_ID" # Create VPC echo "Creating VPC..." VPC_RESULT=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=ProductionVPC-$RANDOM_ID}]") check_command "Failed to create VPC" VPC_ID=$(echo "$VPC_RESULT" | jq -r '.Vpc.VpcId') echo "VPC created with ID: $VPC_ID" # Get Availability Zones echo "Getting Availability Zones..." AZ_RESULT=$(aws ec2 describe-availability-zones --query 'AvailabilityZones[0:2].ZoneName' --output text) check_command "Failed to get Availability Zones" # Convert space-separated output to array read -r -a AZS <<< "$AZ_RESULT" AZ1=${AZS[0]} AZ2=${AZS[1]} echo "Using Availability Zones: $AZ1 and $AZ2" # Create subnets echo "Creating subnets..." PUBLIC_SUBNET1_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.0.0/24 --availability-zone "$AZ1" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PublicSubnet1-$RANDOM_ID}]") check_command "Failed to create public subnet 1" PUBLIC_SUBNET1_ID=$(echo "$PUBLIC_SUBNET1_RESULT" | jq -r '.Subnet.SubnetId') PRIVATE_SUBNET1_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.1.0/24 --availability-zone "$AZ1" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PrivateSubnet1-$RANDOM_ID}]") check_command "Failed to create private subnet 1" PRIVATE_SUBNET1_ID=$(echo "$PRIVATE_SUBNET1_RESULT" | jq -r '.Subnet.SubnetId') PUBLIC_SUBNET2_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.2.0/24 --availability-zone "$AZ2" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PublicSubnet2-$RANDOM_ID}]") check_command "Failed to create public subnet 2" PUBLIC_SUBNET2_ID=$(echo "$PUBLIC_SUBNET2_RESULT" | jq -r '.Subnet.SubnetId') PRIVATE_SUBNET2_RESULT=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block 10.0.3.0/24 --availability-zone "$AZ2" --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=PrivateSubnet2-$RANDOM_ID}]") check_command "Failed to create private subnet 2" PRIVATE_SUBNET2_ID=$(echo "$PRIVATE_SUBNET2_RESULT" | jq -r '.Subnet.SubnetId') echo "Subnets created with IDs:" echo "Public Subnet 1: $PUBLIC_SUBNET1_ID" echo "Private Subnet 1: $PRIVATE_SUBNET1_ID" echo "Public Subnet 2: $PUBLIC_SUBNET2_ID" echo "Private Subnet 2: $PRIVATE_SUBNET2_ID" # Create Internet Gateway echo "Creating Internet Gateway..." IGW_RESULT=$(aws ec2 create-internet-gateway --tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=ProductionIGW-$RANDOM_ID}]") check_command "Failed to create Internet Gateway" IGW_ID=$(echo "$IGW_RESULT" | jq -r '.InternetGateway.InternetGatewayId') echo "Internet Gateway created with ID: $IGW_ID" # Attach Internet Gateway to VPC echo "Attaching Internet Gateway to VPC..." aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" check_command "Failed to attach Internet Gateway to VPC" # Create route tables echo "Creating route tables..." PUBLIC_RT_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PublicRouteTable-$RANDOM_ID}]") check_command "Failed to create public route table" PUBLIC_RT_ID=$(echo "$PUBLIC_RT_RESULT" | jq -r '.RouteTable.RouteTableId') PRIVATE_RT1_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PrivateRouteTable1-$RANDOM_ID}]") check_command "Failed to create private route table 1" PRIVATE_RT1_ID=$(echo "$PRIVATE_RT1_RESULT" | jq -r '.RouteTable.RouteTableId') PRIVATE_RT2_RESULT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=PrivateRouteTable2-$RANDOM_ID}]") check_command "Failed to create private route table 2" PRIVATE_RT2_ID=$(echo "$PRIVATE_RT2_RESULT" | jq -r '.RouteTable.RouteTableId') echo "Route tables created with IDs:" echo "Public Route Table: $PUBLIC_RT_ID" echo "Private Route Table 1: $PRIVATE_RT1_ID" echo "Private Route Table 2: $PRIVATE_RT2_ID" # Add route to Internet Gateway in public route table echo "Adding route to Internet Gateway in public route table..." aws ec2 create-route --route-table-id "$PUBLIC_RT_ID" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW_ID" check_command "Failed to add route to Internet Gateway" # Associate subnets with route tables echo "Associating subnets with route tables..." PUBLIC_RT_ASSOC1_RESULT=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT_ID" --subnet-id "$PUBLIC_SUBNET1_ID") check_command "Failed to associate public subnet 1 with route table" PUBLIC_RT_ASSOC1_ID=$(echo "$PUBLIC_RT_ASSOC1_RESULT" | jq -r '.AssociationId') PUBLIC_RT_ASSOC2_RESULT=$(aws ec2 associate-route-table --route-table-id "$PUBLIC_RT_ID" --subnet-id "$PUBLIC_SUBNET2_ID") check_command "Failed to associate public subnet 2 with route table" PUBLIC_RT_ASSOC2_ID=$(echo "$PUBLIC_RT_ASSOC2_RESULT" | jq -r '.AssociationId') PRIVATE_RT1_ASSOC_RESULT=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT1_ID" --subnet-id "$PRIVATE_SUBNET1_ID") check_command "Failed to associate private subnet 1 with route table" PRIVATE_RT1_ASSOC_ID=$(echo "$PRIVATE_RT1_ASSOC_RESULT" | jq -r '.AssociationId') PRIVATE_RT2_ASSOC_RESULT=$(aws ec2 associate-route-table --route-table-id "$PRIVATE_RT2_ID" --subnet-id "$PRIVATE_SUBNET2_ID") check_command "Failed to associate private subnet 2 with route table" PRIVATE_RT2_ASSOC_ID=$(echo "$PRIVATE_RT2_ASSOC_RESULT" | jq -r '.AssociationId') echo "Route table associations created with IDs:" echo "Public Subnet 1 Association: $PUBLIC_RT_ASSOC1_ID" echo "Public Subnet 2 Association: $PUBLIC_RT_ASSOC2_ID" echo "Private Subnet 1 Association: $PRIVATE_RT1_ASSOC_ID" echo "Private Subnet 2 Association: $PRIVATE_RT2_ASSOC_ID" # Create NAT Gateways echo "Creating NAT Gateways..." # Allocate Elastic IPs for NAT Gateways echo "Allocating Elastic IPs for NAT Gateways..." EIP1_RESULT=$(aws ec2 allocate-address --domain vpc --tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=NAT1-EIP-$RANDOM_ID}]") check_command "Failed to allocate Elastic IP 1" EIP1_ALLOC_ID=$(echo "$EIP1_RESULT" | jq -r '.AllocationId') EIP2_RESULT=$(aws ec2 allocate-address --domain vpc --tag-specifications "ResourceType=elastic-ip,Tags=[{Key=Name,Value=NAT2-EIP-$RANDOM_ID}]") check_command "Failed to allocate Elastic IP 2" EIP2_ALLOC_ID=$(echo "$EIP2_RESULT" | jq -r '.AllocationId') echo "Elastic IPs allocated with IDs:" echo "EIP 1 Allocation ID: $EIP1_ALLOC_ID" echo "EIP 2 Allocation ID: $EIP2_ALLOC_ID" # Create NAT Gateways echo "Creating NAT Gateway in public subnet 1..." NAT_GW1_RESULT=$(aws ec2 create-nat-gateway --subnet-id "$PUBLIC_SUBNET1_ID" --allocation-id "$EIP1_ALLOC_ID" --tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=NAT-Gateway1-$RANDOM_ID}]") check_command "Failed to create NAT Gateway 1" NAT_GW1_ID=$(echo "$NAT_GW1_RESULT" | jq -r '.NatGateway.NatGatewayId') echo "Creating NAT Gateway in public subnet 2..." NAT_GW2_RESULT=$(aws ec2 create-nat-gateway --subnet-id "$PUBLIC_SUBNET2_ID" --allocation-id "$EIP2_ALLOC_ID" --tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=NAT-Gateway2-$RANDOM_ID}]") check_command "Failed to create NAT Gateway 2" NAT_GW2_ID=$(echo "$NAT_GW2_RESULT" | jq -r '.NatGateway.NatGatewayId') echo "NAT Gateways created with IDs:" echo "NAT Gateway 1: $NAT_GW1_ID" echo "NAT Gateway 2: $NAT_GW2_ID" # Wait for NAT Gateways to be available echo "Waiting for NAT Gateways to be available..." aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW1_ID" check_command "NAT Gateway 1 did not become available" aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW2_ID" check_command "NAT Gateway 2 did not become available" echo "NAT Gateways are now available" # Add routes to NAT Gateways in private route tables echo "Adding routes to NAT Gateways in private route tables..." aws ec2 create-route --route-table-id "$PRIVATE_RT1_ID" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW1_ID" check_command "Failed to add route to NAT Gateway 1" aws ec2 create-route --route-table-id "$PRIVATE_RT2_ID" --destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT_GW2_ID" check_command "Failed to add route to NAT Gateway 2" # Create VPC Endpoint for S3 echo "Creating VPC Endpoint for S3..." S3_PREFIX_LIST_ID=$(aws ec2 describe-prefix-lists --filters "Name=prefix-list-name,Values=com.amazonaws.$(aws configure get region).s3" --query 'PrefixLists[0].PrefixListId' --output text) check_command "Failed to get S3 prefix list ID" VPC_ENDPOINT_RESULT=$(aws ec2 create-vpc-endpoint --vpc-id "$VPC_ID" --service-name "com.amazonaws.$(aws configure get region).s3" --route-table-ids "$PRIVATE_RT1_ID" "$PRIVATE_RT2_ID" --tag-specifications "ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=S3-Endpoint-$RANDOM_ID}]") check_command "Failed to create VPC endpoint for S3" VPC_ENDPOINT_ID=$(echo "$VPC_ENDPOINT_RESULT" | jq -r '.VpcEndpoint.VpcEndpointId') echo "VPC Endpoint created with ID: $VPC_ENDPOINT_ID" # Create security groups echo "Creating security groups..." LB_SG_RESULT=$(aws ec2 create-security-group --group-name "LoadBalancerSG-$RANDOM_ID" --description "Security group for the load balancer" --vpc-id "$VPC_ID" --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=LoadBalancerSG-$RANDOM_ID}]") check_command "Failed to create load balancer security group" LB_SG_ID=$(echo "$LB_SG_RESULT" | jq -r '.GroupId') # Allow inbound HTTP traffic from anywhere to the load balancer aws ec2 authorize-security-group-ingress --group-id "$LB_SG_ID" --protocol tcp --port 80 --cidr 0.0.0.0/0 check_command "Failed to authorize ingress to load balancer security group" APP_SG_RESULT=$(aws ec2 create-security-group --group-name "AppServerSG-$RANDOM_ID" --description "Security group for the application servers" --vpc-id "$VPC_ID" --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=AppServerSG-$RANDOM_ID}]") check_command "Failed to create application server security group" APP_SG_ID=$(echo "$APP_SG_RESULT" | jq -r '.GroupId') # Allow inbound HTTP traffic from the load balancer security group to the application servers aws ec2 authorize-security-group-ingress --group-id "$APP_SG_ID" --protocol tcp --port 80 --source-group "$LB_SG_ID" check_command "Failed to authorize ingress to application server security group" echo "Security groups created with IDs:" echo "Load Balancer Security Group: $LB_SG_ID" echo "Application Server Security Group: $APP_SG_ID" # Create a launch template echo "Creating launch template..." # Create user data script with IMDSv2 support cat > user-data.sh << 'EOF' #!/bin/bash yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd # Use IMDSv2 with session token TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") AZ=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone) HOSTNAME=$(hostname -f) echo "<h1>Hello from $HOSTNAME in $AZ</h1>" > /var/www/html/index.html EOF # Encode user data USER_DATA=$(base64 -w 0 user-data.sh) # Get latest Amazon Linux 2 AMI echo "Getting latest Amazon Linux 2 AMI..." AMI_ID=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-*-x86_64-gp2" "Name=state,Values=available" --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text) check_command "Failed to get latest Amazon Linux 2 AMI" echo "Using AMI: $AMI_ID" # Create launch template with IMDSv2 required LAUNCH_TEMPLATE_NAME="AppServerTemplate-$RANDOM_ID" echo "Creating launch template: $LAUNCH_TEMPLATE_NAME" aws ec2 create-launch-template \ --launch-template-name "$LAUNCH_TEMPLATE_NAME" \ --version-description "Initial version" \ --tag-specifications "ResourceType=launch-template,Tags=[{Key=Name,Value=$LAUNCH_TEMPLATE_NAME}]" \ --launch-template-data "{ \"NetworkInterfaces\": [{ \"DeviceIndex\": 0, \"Groups\": [\"$APP_SG_ID\"], \"DeleteOnTermination\": true }], \"ImageId\": \"$AMI_ID\", \"InstanceType\": \"t3.micro\", \"UserData\": \"$USER_DATA\", \"MetadataOptions\": { \"HttpTokens\": \"required\", \"HttpEndpoint\": \"enabled\" }, \"TagSpecifications\": [{ \"ResourceType\": \"instance\", \"Tags\": [{ \"Key\": \"Name\", \"Value\": \"AppServer-$RANDOM_ID\" }] }] }" check_command "Failed to create launch template" # Create target group echo "Creating target group..." TARGET_GROUP_NAME="AppTargetGroup-$RANDOM_ID" TARGET_GROUP_RESULT=$(aws elbv2 create-target-group \ --name "$TARGET_GROUP_NAME" \ --protocol HTTP \ --port 80 \ --vpc-id "$VPC_ID" \ --target-type instance \ --health-check-protocol HTTP \ --health-check-path "/" \ --health-check-port traffic-port) check_command "Failed to create target group" TARGET_GROUP_ARN=$(echo "$TARGET_GROUP_RESULT" | jq -r '.TargetGroups[0].TargetGroupArn') echo "Target group created with ARN: $TARGET_GROUP_ARN" # Create load balancer echo "Creating load balancer..." LB_NAME="AppLoadBalancer-$RANDOM_ID" LB_RESULT=$(aws elbv2 create-load-balancer \ --name "$LB_NAME" \ --subnets "$PUBLIC_SUBNET1_ID" "$PUBLIC_SUBNET2_ID" \ --security-groups "$LB_SG_ID" \ --tags "Key=Name,Value=$LB_NAME") check_command "Failed to create load balancer" LB_ARN=$(echo "$LB_RESULT" | jq -r '.LoadBalancers[0].LoadBalancerArn') echo "Load balancer created with ARN: $LB_ARN" # Wait for load balancer to be active echo "Waiting for load balancer to be active..." aws elbv2 wait load-balancer-available --load-balancer-arns "$LB_ARN" check_command "Load balancer did not become available" # Create listener echo "Creating listener..." LISTENER_RESULT=$(aws elbv2 create-listener \ --load-balancer-arn "$LB_ARN" \ --protocol HTTP \ --port 80 \ --default-actions "Type=forward,TargetGroupArn=$TARGET_GROUP_ARN") check_command "Failed to create listener" LISTENER_ARN=$(echo "$LISTENER_RESULT" | jq -r '.Listeners[0].ListenerArn') echo "Listener created with ARN: $LISTENER_ARN" # Create Auto Scaling group echo "Creating Auto Scaling group..." ASG_NAME="AppAutoScalingGroup-$RANDOM_ID" aws autoscaling create-auto-scaling-group \ --auto-scaling-group-name "$ASG_NAME" \ --launch-template "LaunchTemplateName=$LAUNCH_TEMPLATE_NAME,Version=\$Latest" \ --min-size 2 \ --max-size 4 \ --desired-capacity 2 \ --vpc-zone-identifier "$PRIVATE_SUBNET1_ID,$PRIVATE_SUBNET2_ID" \ --target-group-arns "$TARGET_GROUP_ARN" \ --health-check-type ELB \ --health-check-grace-period 300 \ --tags "Key=Name,Value=AppServer-$RANDOM_ID,PropagateAtLaunch=true" check_command "Failed to create Auto Scaling group" echo "Auto Scaling group created with name: $ASG_NAME" # Get load balancer DNS name LB_DNS_NAME=$(aws elbv2 describe-load-balancers --load-balancer-arns "$LB_ARN" --query 'LoadBalancers[0].DNSName' --output text) check_command "Failed to get load balancer DNS name" echo "" echo "===========================================" echo "DEPLOYMENT COMPLETE" echo "===========================================" echo "VPC ID: $VPC_ID" echo "Public Subnet 1: $PUBLIC_SUBNET1_ID (AZ: $AZ1)" echo "Private Subnet 1: $PRIVATE_SUBNET1_ID (AZ: $AZ1)" echo "Public Subnet 2: $PUBLIC_SUBNET2_ID (AZ: $AZ2)" echo "Private Subnet 2: $PRIVATE_SUBNET2_ID (AZ: $AZ2)" echo "NAT Gateway 1: $NAT_GW1_ID" echo "NAT Gateway 2: $NAT_GW2_ID" echo "Load Balancer: $LB_NAME" echo "Auto Scaling Group: $ASG_NAME" echo "" echo "Your application will be available at: http://$LB_DNS_NAME" echo "It may take a few minutes for the instances to launch and pass health checks." echo "" # Add health check monitoring echo "===========================================" echo "MONITORING INSTANCE HEALTH AND LOAD BALANCER" echo "===========================================" echo "Waiting for instances to launch and pass health checks..." echo "This may take 3-5 minutes. Checking every 30 seconds..." # Monitor instance health and load balancer accessibility MAX_ATTEMPTS=10 ATTEMPT=1 HEALTHY_INSTANCES=0 while [ $ATTEMPT -le $MAX_ATTEMPTS ] && [ $HEALTHY_INSTANCES -lt 2 ]; do echo "Check attempt $ATTEMPT of $MAX_ATTEMPTS..." # Check Auto Scaling group instances echo "Checking Auto Scaling group instances..." ASG_INSTANCES=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "$ASG_NAME" --query 'AutoScalingGroups[0].Instances[*].[InstanceId,HealthStatus]' --output json) echo "ASG Instances status:" echo "$ASG_INSTANCES" | jq -r '.[] | "Instance: \(.[0]), Health: \(.[1])"' # Check target group health echo "Checking target group health..." TARGET_HEALTH=$(aws elbv2 describe-target-health --target-group-arn "$TARGET_GROUP_ARN" --output json) echo "Target health status:" echo "$TARGET_HEALTH" | jq -r '.TargetHealthDescriptions[] | "Instance: \(.Target.Id), State: \(.TargetHealth.State), Reason: \(.TargetHealth.Reason // "N/A"), Description: \(.TargetHealth.Description // "N/A")"' # Count healthy instances HEALTHY_INSTANCES=$(echo "$TARGET_HEALTH" | jq -r '[.TargetHealthDescriptions[] | select(.TargetHealth.State=="healthy")] | length') echo "Number of healthy instances: $HEALTHY_INSTANCES of 2 expected" # Check if we have healthy instances if [ $HEALTHY_INSTANCES -ge 2 ]; then echo "All instances are healthy!" # Test load balancer accessibility echo "Testing load balancer accessibility..." HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://$LB_DNS_NAME") if [ "$HTTP_STATUS" = "200" ]; then echo "Load balancer is accessible! HTTP Status: $HTTP_STATUS" echo "You can access your application at: http://$LB_DNS_NAME" # Try to get the content to verify IMDSv2 is working echo "Fetching content to verify IMDSv2 functionality..." CONTENT=$(curl -s "http://$LB_DNS_NAME") echo "Response from server:" echo "$CONTENT" # Check if the content contains the expected pattern if [[ "$CONTENT" == *"Hello from"* && "$CONTENT" == *"in"* ]]; then echo "IMDSv2 is working correctly! The instance was able to access metadata using the token-based approach." else echo "Warning: Content doesn't match expected pattern. IMDSv2 functionality could not be verified." fi break else echo "Load balancer returned HTTP status: $HTTP_STATUS" echo "Will try again in 30 seconds..." fi else echo "Waiting for instances to become healthy..." echo "Will check again in 30 seconds..." fi ATTEMPT=$((ATTEMPT+1)) if [ $ATTEMPT -le $MAX_ATTEMPTS ]; then sleep 30 fi done if [ $HEALTHY_INSTANCES -lt 2 ]; then echo "Warning: Not all instances are healthy after maximum attempts." echo "You may need to wait longer or check for configuration issues." fi echo "To test your application, run:" echo "curl http://$LB_DNS_NAME" echo "" echo "===========================================" echo "CLEANUP CONFIRMATION" echo "===========================================" echo "Do you want to clean up all created resources? (y/n): " read -r CLEANUP_CHOICE if [[ "$CLEANUP_CHOICE" =~ ^[Yy]$ ]]; then cleanup_resources echo "All resources have been deleted." else echo "Resources will not be deleted. You can manually delete them later." echo "To delete resources, run this script again and choose to clean up." fi