Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from aws_cdk import (
- Stack,
- RemovalPolicy,
- Duration,
- CfnOutput,
- Fn,
- aws_ec2 as ec2,
- aws_iam as iam,
- aws_rds as rds,
- aws_s3 as s3,
- aws_ecr as ecr,
- aws_autoscaling as autoscaling,
- aws_elasticloadbalancingv2 as elbv2,
- aws_secretsmanager as secretsmanager,
- aws_logs as logs,
- # aws_grafana as grafana, # Grafana not used in the final setup, commented out
- aws_cloudfront as cloudfront,
- aws_cloudfront_origins as origins,
- aws_ssm as ssm
- )
- from constructs import Construct
- import os
- import json
- class CalorieMitraStack(Stack):
- def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
- super().__init__(scope, construct_id, **kwargs)
- # --- Get context variables ---
- app_name = self.node.try_get_context("app_name") or "CalorieMitra"
- app_port = self.node.try_get_context("app_port") or 8000
- container_port = 8000 # Set based on your Dockerfile EXPOSE
- docker_image_tag_context = self.node.try_get_context("docker_image_tag") or "latest"
- app_secret_arn = self.node.try_get_context("app_secret_arn")
- acm_certificate_arn = self.node.try_get_context("acm_certificate_arn")
- if not app_secret_arn:
- raise ValueError("Missing required secret ARN in CDK context (app_secret_arn)")
- ssm_image_tag_parameter_name = f"/app/{app_name.lower()}/image-tag"
- # --- Networking ---
- vpc = ec2.Vpc(
- self, f"{app_name}Vpc",
- max_azs=2, cidr="10.10.0.0/16", nat_gateways=1,
- subnet_configuration=[
- ec2.SubnetConfiguration(name="public", subnet_type=ec2.SubnetType.PUBLIC, cidr_mask=24),
- ec2.SubnetConfiguration(name="private", subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS, cidr_mask=24),
- ]
- )
- # --- Security Groups ---
- alb_sg = ec2.SecurityGroup(self, f"{app_name}AlbSg", vpc=vpc, description="ALB SG", allow_all_outbound=True)
- alb_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(80), "Allow HTTP")
- if acm_certificate_arn:
- alb_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(443), "Allow HTTPS")
- ec2_sg = ec2.SecurityGroup(self, f"{app_name}Ec2Sg", vpc=vpc, description="EC2 SG", allow_all_outbound=True)
- ec2_sg.add_ingress_rule(alb_sg, ec2.Port.tcp(app_port), f"Allow traffic from ALB on port {app_port}")
- ec2_sg.add_ingress_rule(ec2.Peer.any_ipv4(), ec2.Port.tcp(443), "Allow HTTPS for SSM") # For SSM Agent
- rds_sg = ec2.SecurityGroup(self, f"{app_name}RdsSg", vpc=vpc, description="RDS SG", allow_all_outbound=True)
- rds_sg.add_ingress_rule(ec2_sg, ec2.Port.tcp(5432), "Allow traffic from EC2")
- # --- ECR Repository ---
- ecr_repository = ecr.Repository(
- self, f"{app_name}EcrRepo", repository_name=f"{app_name.lower()}-app-repo",
- removal_policy=RemovalPolicy.RETAIN, image_tag_mutability=ecr.TagMutability.MUTABLE
- )
- # --- S3 Bucket ---
- meal_images_bucket = s3.Bucket(
- self, f"{app_name}MealImagesBucket", block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
- encryption=s3.BucketEncryption.S3_MANAGED, removal_policy=RemovalPolicy.RETAIN, auto_delete_objects=False,
- lifecycle_rules=[s3.LifecycleRule(id="MoveToIA", enabled=True, transitions=[
- s3.Transition(storage_class=s3.StorageClass.INFREQUENT_ACCESS, transition_after=Duration.days(90))])]
- )
- # --- Cloudfront Distribution with OAC ---
- cfn_oac = cloudfront.CfnOriginAccessControl(self, f"{app_name}OAC",
- origin_access_control_config=cloudfront.CfnOriginAccessControl.OriginAccessControlConfigProperty(
- name=f"{app_name}-OAC-{self.region}", origin_access_control_origin_type="s3",
- signing_behavior="always", signing_protocol="sigv4", description="OAC for S3 Bucket"))
- cloudfront_distribution = cloudfront.Distribution(
- self, f"{app_name}CloudfrontDistribution",
- default_behavior=cloudfront.BehaviorOptions(
- origin=origins.S3Origin(meal_images_bucket),
- viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
- cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED),
- comment=f"{app_name} CDN")
- cfn_distribution = cloudfront_distribution.node.default_child
- cfn_distribution.add_property_override("DistributionConfig.Origins.0.OriginAccessControlId", cfn_oac.attr_id)
- cfn_distribution.add_property_override("DistributionConfig.Origins.0.S3OriginConfig.OriginAccessIdentity", "")
- meal_images_bucket.add_to_resource_policy(
- iam.PolicyStatement(actions=["s3:GetObject"], resources=[meal_images_bucket.arn_for_objects("*")],
- principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")],
- conditions={"StringEquals": { "AWS:SourceArn": f"arn:aws:cloudfront::{self.account}:distribution/{cloudfront_distribution.distribution_id}" }}))
- # --- RDS PostgreSQL Instance ---
- db_instance_name = "defaultdb"
- db_instance = rds.DatabaseInstance(
- self, f"{app_name}RdsInstance", engine=rds.DatabaseInstanceEngine.postgres(version=rds.PostgresEngineVersion.VER_15),
- instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM),
- vpc=vpc, vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
- security_groups=[rds_sg], allocated_storage=50, storage_type=rds.StorageType.GP3,
- database_name=db_instance_name, multi_az=False, backup_retention=Duration.days(7), delete_automated_backups=False,
- deletion_protection=True, copy_tags_to_snapshot=True, enable_performance_insights=True,
- removal_policy=RemovalPolicy.RETAIN)
- # --- SSM Parameter for Docker Image Tag ---
- ssm_image_tag_parameter = ssm.StringParameter(
- self, f"{app_name}ImageTagParam", parameter_name=ssm_image_tag_parameter_name,
- string_value=docker_image_tag_context, description=f"Docker image tag for {app_name}",
- tier=ssm.ParameterTier.STANDARD)
- # --- IAM Role for EC2 Instances ---
- ec2_role = iam.Role(
- self, f"{app_name}Ec2Role",
- assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
- managed_policies=[
- iam.ManagedPolicy.from_aws_managed_policy_name("AmazonSSMManagedInstanceCore"),
- iam.ManagedPolicy.from_aws_managed_policy_name("CloudWatchAgentServerPolicy"),
- iam.ManagedPolicy.from_aws_managed_policy_name("AmazonEC2ContainerRegistryReadOnly")
- ]
- )
- ec2_role.add_to_policy(iam.PolicyStatement(
- actions=[
- "logs:CreateLogGroup",
- "logs:CreateLogStream",
- "logs:PutLogEvents",
- "logs:DescribeLogStreams",
- "logs:DescribeLogGroups"
- ],
- resources=[
- f"arn:aws:logs:{self.region}:{self.account}:log-group:/app/{app_name.lower()}/*",
- f"arn:aws:logs:{self.region}:{self.account}:log-group:/app/{app_name.lower()}/*:*",
- f"arn:aws:logs:{self.region}:{self.account}:log-group:/var/log/user-data*",
- f"arn:aws:logs:{self.region}:{self.account}:log-group:/var/log/user-data*:*"
- ]
- ))
- ec2_role.add_to_policy(iam.PolicyStatement(
- actions=["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
- resources=[f"arn:aws:ssm:{self.region}:{self.account}:parameter{ssm_image_tag_parameter_name}"]
- ))
- ec2_role.add_to_policy(iam.PolicyStatement(
- actions=["autoscaling:SetInstanceHealth", "autoscaling:DescribeAutoScalingInstances"],
- resources=["*"]
- ))
- ecr_repository.grant_pull(ec2_role)
- meal_images_bucket.grant_read_write(ec2_role)
- app_secret = secretsmanager.Secret.from_secret_complete_arn(self, "AppSecret", app_secret_arn)
- app_secret.grant_read(ec2_role)
- ssm_image_tag_parameter.grant_read(ec2_role)
- # --- CloudWatch Log Group for Application Logs ---
- app_log_group = logs.LogGroup(
- self, f"{app_name}AppLogGroup",
- log_group_name=f"/app/{app_name.lower()}/docker-logs",
- retention=logs.RetentionDays.TWO_WEEKS,
- removal_policy=RemovalPolicy.DESTROY
- )
- app_log_group.grant_write(ec2_role)
- # --- User Data Script (Streamlined for Custom AMI) ---
- # Pass the log group name to the user data script
- app_log_group_name = app_log_group.log_group_name
- # Define your custom AMI ID here
- custom_ami_id = "ami-04c5ae52f42a72334" # <-- YOUR CUSTOM AMI ID
- user_data_script = f"""#!/bin/bash -xeu
- # -x: print commands, -e: exit on error, -u: treat unset variables as error
- set -o pipefail
- # Redirect stdout/stderr to a log file and to the system logger
- exec > >(tee /var/log/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1
- # --- Basic Logging Function (Output to stderr) ---
- log() {{
- # Use >&2 to redirect echo output to standard error
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >&2
- }}
- log "--- UserData Script Start (Using Custom AMI: {custom_ami_id}) ---"
- # --- Essential Variables (Injected by CDK f-string) ---
- APP_PORT="{app_port}"
- CONTAINER_PORT="{container_port}"
- AWS_REGION="{self.region}"
- AWS_ACCOUNT_ID="{self.account}"
- APP_SECRET_ARN="{app_secret_arn}"
- SSM_PARAM_NAME="{ssm_image_tag_parameter_name}"
- ECR_REPOSITORY_NAME="{ecr_repository.repository_name}"
- DB_ENDPOINT_ADDRESS="{db_instance.db_instance_endpoint_address}"
- DB_ENDPOINT_PORT="{db_instance.db_instance_endpoint_port}"
- DB_NAME="{db_instance_name}"
- S3_BUCKET="{meal_images_bucket.bucket_name}"
- CLOUDFRONT_DOMAIN="{cloudfront_distribution.distribution_domain_name}"
- APP_LOG_GROUP_NAME="{app_log_group_name}" # Use the variable passed from CDK
- # -----------------------------------------------------
- # --- Error Handling Setup ---
- handle_error() {{
- local error_msg="$1"
- local exit_code="${{2:-1}}"
- log "ERROR: $error_msg (Exit Code: $exit_code)"
- TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60" || true)
- INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id || curl -s http://169.254.169.254/latest/meta-data/instance-id || echo "unknown")
- if [[ "$INSTANCE_ID" != "unknown" ]] && command -v aws &> /dev/null; then
- log "Attempting to signal ASG Unhealthy for instance $INSTANCE_ID in region $AWS_REGION..."
- aws autoscaling set-instance-health --instance-id "$INSTANCE_ID" --health-status Unhealthy --region "$AWS_REGION" || \\
- log "Warning: Failed to set instance health via AWS CLI. Check IAM permissions (autoscaling:SetInstanceHealth)."
- else
- log "Warning: Could not get Instance ID or AWS CLI not available. Cannot signal ASG."
- fi
- exit $exit_code
- }}
- trap 'handle_error "Script failed on line $LINENO" $?' ERR
- # --- Verify Prerequisites (Should be installed in AMI) ---
- log "Verifying prerequisite tools (should be installed in AMI)..."
- command -v docker >/dev/null || handle_error "Docker command not found. AMI build might have failed."
- command -v aws >/dev/null || handle_error "AWS CLI command not found. AMI build might have failed."
- command -v jq >/dev/null || handle_error "jq command not found. AMI build might have failed."
- command -v git >/dev/null || handle_error "git command not found. AMI build might have failed."
- command -v nc >/dev/null || handle_error "netcat (nc) command not found. AMI build might have failed."
- log "Prerequisite tools verified."
- # --- Configure and Start Docker ---
- log "Configuring Docker log driver (awslogs)..."
- sudo mkdir -p /etc/docker # Ensure directory exists
- sudo tee /etc/docker/daemon.json > /dev/null <<EOF
- {{
- "log-driver": "awslogs",
- "log-opts": {{
- "awslogs-region": "$AWS_REGION",
- "awslogs-group": "$APP_LOG_GROUP_NAME",
- "awslogs-create-group": "true",
- "tag": "{{{{.Name}}}}/{{{{.ID}}}}"
- }}
- }}
- EOF
- log "Docker daemon.json configured for awslogs."
- log "Ensuring Docker service is enabled and restarting it..."
- # Docker service should be enabled by the AMI build process
- # Restart is needed to apply the daemon.json configuration
- sudo systemctl restart docker
- sleep 5 # Give docker a moment to restart
- sudo systemctl is-active --quiet docker || handle_error "Docker service failed to restart after configuration. Check /etc/docker/daemon.json syntax and system logs (journalctl -u docker.service)."
- sudo usermod -a -G docker ubuntu || log "Warning: Failed to add 'ubuntu' user to docker group."
- log "Docker service configured and running."
- # --- Fetch Secrets Function (No internal success log before echo) ---
- function get_secret {{
- local secret_arn="$1"
- local retries=3
- local count=0
- local delay=5
- local secret_json=""
- while [ $count -lt $retries ]; do
- secret_json=$(aws secretsmanager get-secret-value --secret-id "$secret_arn" --region "$AWS_REGION" --query SecretString --output text 2>/var/log/awscli_secret_error.log)
- local aws_exit_code=$?
- if [ $aws_exit_code -eq 0 ] && [ -n "$secret_json" ] && [ "$secret_json" != "null" ]; then
- if echo "$secret_json" | jq -e . > /dev/null; then
- rm -f /var/log/awscli_secret_error.log
- echo "$secret_json" # Echo ONLY the JSON to stdout
- return 0
- else
- log "Warning: Fetched secret for $secret_arn is not valid JSON (attempt $((count+1))/$retries). Retrying..."
- fi
- else
- log "Warning: Failed to fetch secret $secret_arn (AWS CLI exit code: $aws_exit_code, attempt $((count+1))/$retries). Check /var/log/awscli_secret_error.log. Retrying..."
- fi
- count=$((count+1))
- sleep $delay
- done
- log "ERROR: Failed to fetch valid secret JSON for $secret_arn after $retries attempts."
- return 1
- }}
- log "Fetching App secrets from ARN: $APP_SECRET_ARN"
- APP_SECRET_JSON=$(get_secret "$APP_SECRET_ARN") || handle_error "Failed to fetch App secrets from $APP_SECRET_ARN after retries" $?
- log "Successfully fetched and validated secret JSON from: $APP_SECRET_ARN"
- # Parse secrets using jq
- log "Parsing essential secrets..."
- GEMINI_API_KEY=$(echo "$APP_SECRET_JSON" | jq -r '.GEMINI_API_KEY // ""')
- ENDPOINT=$(echo "$APP_SECRET_JSON" | jq -r '.ENDPOINT // ""')
- FIREBASE_PROJECT_ID=$(echo "$APP_SECRET_JSON" | jq -r '.FIREBASE_PROJECT_ID // ""')
- FIREBASE_CLIENT_EMAIL=$(echo "$APP_SECRET_JSON" | jq -r '.FIREBASE_CLIENT_EMAIL // ""')
- FIREBASE_PRIVATE_KEY=$(echo "$APP_SECRET_JSON" | jq -r '.FIREBASE_PRIVATE_KEY // ""')
- DB_USERNAME=$(echo "$APP_SECRET_JSON" | jq -r '.DB_USERNAME // ""')
- DB_PASSWORD=$(echo "$APP_SECRET_JSON" | jq -r '.DB_PASSWORD // ""')
- if [ -z "$DB_USERNAME" ] || [ -z "$DB_PASSWORD" ]; then
- log "Warning: DB_USERNAME or DB_PASSWORD missing from secrets."
- fi
- log "Secrets parsed."
- # --- Fetch Docker Image Tag from SSM ---
- log "Fetching image tag from SSM parameter: $SSM_PARAM_NAME"
- TARGET_IMAGE_TAG=$(aws ssm get-parameter --name "$SSM_PARAM_NAME" --query "Parameter.Value" --output text --region "$AWS_REGION" 2>/var/log/awscli_ssm_error.log)
- ssm_exit_code=$?
- if [ $ssm_exit_code -ne 0 ] || [ -z "$TARGET_IMAGE_TAG" ] || [ "$TARGET_IMAGE_TAG" == "None" ]; then
- log "Warning: Failed to get tag from SSM $SSM_PARAM_NAME. Using 'latest' as fallback."
- TARGET_IMAGE_TAG="latest"
- rm -f /var/log/awscli_ssm_error.log
- fi
- log "Using image tag: $TARGET_IMAGE_TAG"
- # --- ECR Login ---
- ECR_LOGIN_SERVER="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
- log "Logging into ECR: $ECR_LOGIN_SERVER"
- aws ecr get-login-password --region "$AWS_REGION" | sudo docker login --username AWS --password-stdin "$ECR_LOGIN_SERVER" || \\
- handle_error "Failed to log into ECR server $ECR_LOGIN_SERVER"
- log "ECR login successful."
- # --- Prepare and Run Container ---
- ECR_REPO_URI="$ECR_LOGIN_SERVER/$ECR_REPOSITORY_NAME"
- IMAGE_URI="$ECR_REPO_URI:$TARGET_IMAGE_TAG"
- log "Stopping and removing existing container named 'myapp' (if any)..."
- sudo docker stop myapp 2>/dev/null || true
- sudo docker rm -f myapp 2>/dev/null || true
- log "Pulling image: $IMAGE_URI"
- PULL_SUCCESS="false"
- for i in {{1..3}}; do
- if sudo docker pull "$IMAGE_URI"; then
- log "Image pull successful (attempt $i)."
- PULL_SUCCESS="true"
- break
- else
- log "Warning: Pull attempt $i failed for $IMAGE_URI."
- if [ $i -lt 3 ]; then
- log "Retrying pull in 5 seconds..."
- sleep 5;
- else
- log "Error: Final pull attempt failed.";
- fi
- fi
- done
- if [ "$PULL_SUCCESS" != "true" ]; then
- handle_error "FATAL: Failed to pull image $IMAGE_URI after 3 attempts."
- fi
- log "Image pull successful."
- log "Running container 'myapp' from image $IMAGE_URI..."
- # Build DATABASE_URL
- DB_URL_CREDENTIALS=""
- if [ -n "$DB_USERNAME" ]; then
- DB_URL_CREDENTIALS="$DB_USERNAME:$DB_PASSWORD@"
- fi
- DATABASE_URL="postgresql://$DB_URL_CREDENTIALS$DB_ENDPOINT_ADDRESS:$DB_ENDPOINT_PORT/$DB_NAME"
- # Build docker run options array
- declare -a docker_env_opts
- docker_env_opts+=( "-e" "DATABASE_HOST=$DB_ENDPOINT_ADDRESS" )
- docker_env_opts+=( "-e" "DATABASE_PORT=$DB_ENDPOINT_PORT" )
- docker_env_opts+=( "-e" "DATABASE_NAME=$DB_NAME" )
- docker_env_opts+=( "-e" "DATABASE_URL=$DATABASE_URL" )
- docker_env_opts+=( "-e" "S3_BUCKET_NAME=$S3_BUCKET" )
- docker_env_opts+=( "-e" "CLOUDFRONT_DOMAIN_NAME=$CLOUDFRONT_DOMAIN" )
- docker_env_opts+=( "-e" "AWS_REGION=$AWS_REGION" )
- docker_env_opts+=( "-e" "NODE_ENV=production" )
- docker_env_opts+=( "-e" "PORT=$CONTAINER_PORT" )
- [ -n "$DB_USERNAME" ] && docker_env_opts+=( "-e" "DATABASE_USERNAME=$DB_USERNAME" )
- [ -n "$DB_PASSWORD" ] && docker_env_opts+=( "-e" "DATABASE_PASSWORD=$DB_PASSWORD" )
- [ -n "$GEMINI_API_KEY" ] && docker_env_opts+=( "-e" "GEMINI_API_KEY=$GEMINI_API_KEY" )
- [ -n "$ENDPOINT" ] && docker_env_opts+=( "-e" "ENDPOINT=$ENDPOINT" )
- [ -n "$FIREBASE_PROJECT_ID" ] && docker_env_opts+=( "-e" "FIREBASE_PROJECT_ID=$FIREBASE_PROJECT_ID" )
- [ -n "$FIREBASE_CLIENT_EMAIL" ] && docker_env_opts+=( "-e" "FIREBASE_CLIENT_EMAIL=$FIREBASE_CLIENT_EMAIL" )
- [ -n "$FIREBASE_PRIVATE_KEY" ] && docker_env_opts+=( "-e" "FIREBASE_PRIVATE_KEY=$FIREBASE_PRIVATE_KEY" )
- # Execute docker run
- # No need to specify --log-driver here since it's set in daemon.json
- sudo docker run -d --name myapp \\
- --restart always \\
- -p "$APP_PORT":"$CONTAINER_PORT" \\
- "${{docker_env_opts[@]}}" \\
- "$IMAGE_URI" || handle_error "Failed to execute 'docker run' command."
- # Brief pause and check if container is running
- log "Waiting 10 seconds for container to stabilize..."
- sleep 10
- if ! sudo docker ps -q --filter name=^/myapp$; then
- log "--- Docker Status After Failed Start ---"
- sudo docker ps -a || log "Warning: Failed to run 'docker ps -a'."
- log "--- Last 100 lines of 'myapp' logs (if exists - may not show if driver failed) ---"
- # Note: docker logs might not work correctly if the awslogs driver itself had issues initializing
- sudo docker logs --tail 100 myapp || log "Could not get logs for 'myapp' via docker logs command."
- handle_error "Container 'myapp' is not running shortly after start. Check CloudWatch Logs ($APP_LOG_GROUP_NAME) and system logs (/var/log/syslog or journalctl -u docker.service)."
- fi
- log "Container 'myapp' started successfully. Logs should appear in CloudWatch Group: $APP_LOG_GROUP_NAME"
- log "--- UserData Script Finished Successfully ---"
- exit 0
- """
- # --- User Data Object ---
- user_data = ec2.UserData.custom(user_data_script)
- # --- Use the Custom AMI ---
- # Replace the Ubuntu AMI lookup with generic_linux using your specific AMI ID
- custom_calorie_mitra_ami = ec2.MachineImage.generic_linux({
- # Map the region where the stack is deployed to your AMI ID
- self.region: custom_ami_id
- })
- # Note: Ensure your custom AMI (ami-04c5ae52f42a72334) exists in the region(s)
- # where you deploy this stack. If deploying to multiple regions, you'll need
- # the corresponding AMI ID for each region in this map.
- # --- EC2 Launch Template (Using Custom AMI) ---
- launch_template = ec2.LaunchTemplate(
- self, f"{app_name}LaunchTemplate",
- instance_type=ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM), # Ensure AMI architecture matches instance type
- machine_image=custom_calorie_mitra_ami, # <-- USE YOUR CUSTOM AMI HERE
- role=ec2_role,
- security_group=ec2_sg,
- user_data=user_data, # Assign the updated UserData object here
- block_devices=[ec2.BlockDevice(
- device_name="/dev/sda1", # Verify root device name for your custom AMI if needed
- volume=ec2.BlockDeviceVolume.ebs(
- volume_size=20,
- volume_type=ec2.EbsDeviceVolumeType.GP3,
- delete_on_termination=True
- )
- )]
- )
- # --- Define Auto Scaling Group ---
- asg = autoscaling.AutoScalingGroup(
- self, f"{app_name}Asg",
- vpc=vpc,
- launch_template=launch_template,
- vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
- min_capacity=1, max_capacity=4, desired_capacity=1,
- health_check=autoscaling.HealthCheck.elb(grace=Duration.minutes(10))
- )
- asg.scale_on_cpu_utilization(f"{app_name}CpuScaling", target_utilization_percent=80)
- # --- Application Load Balancer ---
- alb = elbv2.ApplicationLoadBalancer(self, f"{app_name}Alb", vpc=vpc, internet_facing=True, security_group=alb_sg)
- # --- Target Group ---
- target_group = elbv2.ApplicationTargetGroup(
- self, f"{app_name}TargetGroup",
- vpc=vpc,
- port=app_port,
- protocol=elbv2.ApplicationProtocol.HTTP,
- target_type=elbv2.TargetType.INSTANCE,
- targets=[asg],
- health_check=elbv2.HealthCheck(
- path="/healthz",
- port=str(app_port),
- protocol=elbv2.Protocol.HTTP,
- interval=Duration.seconds(60),
- timeout=Duration.seconds(30),
- healthy_threshold_count=2,
- unhealthy_threshold_count=10,
- healthy_http_codes="200-299"
- ),
- deregistration_delay=Duration.seconds(60)
- )
- # --- ALB Listener ---
- if acm_certificate_arn:
- certificate = elbv2.ListenerCertificate.from_arn(acm_certificate_arn)
- https_listener = alb.add_listener(f"{app_name}HttpsListener", port=443,
- protocol=elbv2.ApplicationProtocol.HTTPS, certificates=[certificate],
- default_target_groups=[target_group])
- alb.add_redirect(source_protocol=elbv2.ApplicationProtocol.HTTP, source_port=80,
- target_protocol=elbv2.ApplicationProtocol.HTTPS, target_port=443)
- else:
- http_listener = alb.add_listener(f"{app_name}HttpListener", port=80,
- protocol=elbv2.ApplicationProtocol.HTTP, default_target_groups=[target_group])
- # --- Outputs ---
- CfnOutput(self, "Region", value=self.region)
- CfnOutput(self, "VpcId", value=vpc.vpc_id)
- CfnOutput(self, "EcrRepositoryUri", value=ecr_repository.repository_uri)
- CfnOutput(self, "MealImagesBucketName", value=meal_images_bucket.bucket_name)
- CfnOutput(self, "CloudfrontDomainName", value=cloudfront_distribution.distribution_domain_name)
- CfnOutput(self, "LoadBalancerDns", value=alb.load_balancer_dns_name)
- CfnOutput(self, "RdsEndpointAddress", value=db_instance.db_instance_endpoint_address)
- CfnOutput(self, "RdsEndpointPort", value=db_instance.db_instance_endpoint_port)
- CfnOutput(self, "EC2RoleArn", value=ec2_role.role_arn)
- CfnOutput(self, "AppLogGroupName", value=app_log_group.log_group_name)
- CfnOutput(self, "SsmImageTagParameterName", value=ssm_image_tag_parameter.parameter_name)
- # Updated output to show the hardcoded AMI ID being used
- CfnOutput(self, "SelectedAmiId", value=custom_ami_id)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement