Introduction
Are you ready to take your Python Flask app to the next level by deploying it serverless on AWS ECS Fargate? In this blog, we’ll walk you through the entire process of deploying a Python Flask app on AWS ECS Fargate using Terraform. This approach allows you to leverage the power of containerized applications and the flexibility of serverless architecture, making your deployments more efficient and scalable.
With this step-by-step guide, you’ll learn how to automate the deployment of your Flask app on AWS Fargate. We’ll cover everything from setting up your AWS environment and writing Terraform scripts to deploying and managing your Flask app in a serverless manner. By the end of this blog, you’ll have a solid understanding of using Terraform to deploy and manage Python Flask applications on AWS ECS Fargate, ensuring a smooth and automated workflow for your cloud infrastructure.
Prerequisites
Before we start, ensure you have the following prerequisites:
AWS Account: An AWS account to deploy the resources.
Terraform Installed: Install Terraform on your local machine. You can download it from Terraform’s official site.
AWS CLI Configured: Configure the AWS CLI with your AWS credentials to allow Terraform to communicate with AWS.
Docker Installed: Install Docker to build and test your application locally.
Diagrammatic Representation
1. Develop the Python Flask App
Create a file named app.py
with the following content:
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/')
def home():
html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your YouTube Showcase</title>
<style>
header {
background-color: #ff0000; /* Customize with your channel's color */
color: #ffffff;
text-align: center;
padding: 2rem;
}
/* Reset some default styles */
body, h1, p {
margin: 0;
padding: 0;
}
/* Set a background color or image */
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
}
/* Center align content */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.video-container, .playlist-container, .shorts-container {
display: flex;
justify-content: center;
gap: 1rem;
}
.video, .playlist, .short {
flex: 1;
border: 1px solid #ddd;
padding: 1rem;
background-color: #ffffff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Footer section */
footer {
text-align: center;
padding: 1rem;
background-color: #333;
color: #ffffff;
}
</style>
</head>
<body>
<header>
<h1>DheerajTechInsight</h1>
<p class="channel-description">Welcome to my channel! Subscribe for awesome content.</p>
</header>
<div class="container">
<h1>Popular Videos</h1>
<br>
<!-- Popular YouTube Videos -->
<div class="video-container">
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/trFW03zP7Uw?si=9cXiME0hC3dwH6mi" frameborder="0" allowfullscreen></iframe>
</div>
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/lwyr6E5kaQA?si=UsV5cHJLdiDPPpqy" frameborder="0" allowfullscreen></iframe>
</div>
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/hnfeyKNJ4pM?si=qyZNMHxI5-wJYmVa" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<!-- Popular YouTube Playlists -->
<br>
<h1>Popular Playlists</h1>
<br>
<div class="playlist-container">
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?si=YLd39R6zYB6a7VuV&list=PLz8JBMMd7yjWMJ0YeOkfnTohirAZT_pBU" frameborder="0" allowfullscreen></iframe>
</div>
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?si=pmR7Ep3IH_M1UkO7&list=PLz8JBMMd7yjUukUG1M78ypP9GjWaP8rqf" frameborder="0" allowfullscreen></iframe>
</div>
<div class="video">
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?si=m707kduCb5UYnUmV&list=PLz8JBMMd7yjWA5qpXSVAcbi-_u9n6d7uw" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<!-- Popular YouTube Shorts -->
<br>
<h1>Popular Shorts</h1>
<br>
<div class="shorts-container">
<div class="short">
<iframe width="315" height="560" src="https://youtube.com/embed/73toBt5R4zE?si=4iMebKKwOPyoUcYR" frameborder="0" allowfullscreen></iframe>
</div>
<div class="short">
<iframe width="315" height="560" src="https://youtube.com/embed/L75o4sa7iXQ?si=tJv6_90r3CINw6uW" frameborder="0" allowfullscreen></iframe>
</div>
<div class="short">
<iframe width="315" height="560" src="https://youtube.com/embed/LKysazmlDxk?si=MYycCA2N1tNzNoUH" frameborder="0" allowfullscreen></iframe>
</div>
<div class="short">
<iframe width="315" height="560" src="https://youtube.com/embed/4OfEN3XGcic?si=10IE-7djtqcZuHnn" frameborder="0" allowfullscreen></iframe>
</div>
<div class="short">
<iframe width="315" height="560" src="https://youtube.com/embed/AgGPGujpJCo?si=TVZ5CmamRWv5NPXa" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
<footer>
© 2024 DheerajTechInsight | All rights reserved
</footer>
</body>
</html>
"""
return render_template_string(html_content)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
This simple Flask application will serve a welcome message on the root endpoint.
2. Develop the Dockerfile for the App
Create a file named Dockerfile
in the same directory as app.py
:
# Use the official Python image
FROM python:3-alpine3.15
# Set the working directory in the container
WORKDIR /app
# Copy the application files to the container
COPY app.py /app/
# Install dependencies
RUN pip install flask
# Expose port 80 for the Flask app
EXPOSE 80
# Command to run the application
CMD ["python", "app.py"]
This Dockerfile specifies the steps to containerize the Flask app using a lightweight Python image.
3. Build and Test the Application Locally
Build the Docker image:
docker build -t my-python-app .
Run a container from the image:
docker run -p 80:80 my-python-app
Visit http://localhost
in your browser to see the Flask app running and it will display like below
4. Deploy AWS Networking Landscape
Use the following Terraform code to set up the networking infrastructure:
# Data source to get the current AWS account ID
data "aws_caller_identity" "current" {}
#---------------------Create Custom VPC----------------------
resource "aws_vpc" "CustomVPC" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "CustomVPC"
}
}
#---------------------Create IGW And associate with VPC----------------------
resource "aws_internet_gateway" "IGW" {
vpc_id = aws_vpc.CustomVPC.id
tags = {
Name = "IGW"
}
}
#---------------------Create 2 Public Subnets----------------------
resource "aws_subnet" "PublicSubnet1" {
vpc_id = aws_vpc.CustomVPC.id
cidr_block = "10.0.0.0/18"
availability_zone = "us-west-2a"
map_public_ip_on_launch = true
tags = {
Name = "PublicSubnet1"
Type = "Public"
}
}
resource "aws_subnet" "PublicSubnet2" {
vpc_id = aws_vpc.CustomVPC.id
cidr_block = "10.0.64.0/18"
availability_zone = "us-west-2b"
map_public_ip_on_launch = true
tags = {
Name = "PublicSubnet2"
Type = "Public"
}
}
#---------------------Create Custom Public route table----------------------
resource "aws_route_table" "PublicRouteTable" {
vpc_id = aws_vpc.CustomVPC.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.IGW.id
}
tags = {
Name = "PublicRouteTable"
}
}
#---------------------Create route table association with public route table----------------------
resource "aws_route_table_association" "PublicSubnetRouteTableAssociation1" {
subnet_id = aws_subnet.PublicSubnet1.id
route_table_id = aws_route_table.PublicRouteTable.id
}
resource "aws_route_table_association" "PublicSubnetRouteTableAssociation2" {
subnet_id = aws_subnet.PublicSubnet2.id
route_table_id = aws_route_table.PublicRouteTable.id
}
#----------------------Create Target Group--------------------------
resource "aws_lb_target_group" "CustomTG" {
name = "CustomTG"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.CustomVPC.id
target_type = "ip"
health_check {
healthy_threshold = "3"
interval = "30"
protocol = "HTTP"
matcher = "200"
timeout = "3"
path = "/"
unhealthy_threshold = "2"
}
}
#---------------------Create Security Group For Load Balancer----------------------
resource "aws_security_group" "elb_sg" {
name = "allow_http_elb"
description = "Allow http inbound traffic for elb"
vpc_id = aws_vpc.CustomVPC.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-elb-security-group"
}
}
# Fetch subnet ids & Create a Load Balancer & ELB Listener
data "aws_subnets" "GetSubnet" {
depends_on = [aws_subnet.PublicSubnet1, aws_subnet.PublicSubnet2]
filter {
name = "vpc-id"
values = [aws_vpc.CustomVPC.id]
}
filter {
name = "tag:Type"
values = ["Public"]
}
}
resource "aws_alb" "CustomELB" {
name = "CustomELB"
depends_on = [aws_subnet.PublicSubnet1, aws_subnet.PublicSubnet2]
internal = false
security_groups = [aws_security_group.ecs_task_sg.id]
subnets = data.aws_subnets.GetSubnet.ids
tags = {
Name = "CustomELB"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_alb.CustomELB.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
forward {
target_group {
arn = aws_lb_target_group.CustomTG.arn
}
stickiness {
enabled = true
duration = 28800
}
}
}
}
This code sets up a custom VPC, two public subnets, an internet gateway, and route tables to route internet traffic.
5. Deploy AWS ECS Fargate to Host the Python App
Here is the Terraform code to set up ECS Fargate and deploy the Flask app:
# Set up CloudWatch group and log stream and retain logs for 30 days
resource "aws_cloudwatch_log_group" "cb_log_group" {
name = "/ecs/cb-app"
retention_in_days = 30
tags = {
Name = "cb-log-group"
}
}
resource "aws_cloudwatch_log_stream" "cb_log_stream" {
name = "cb-log-stream"
log_group_name = aws_cloudwatch_log_group.cb_log_group.name
}
# Create an ECR repository
resource "aws_ecr_repository" "my_app" {
name = "my-app"
image_tag_mutability = "MUTABLE"
force_delete = true
}
# Docker Build and Push to ECR
resource "null_resource" "docker_build_push" {
depends_on = [aws_ecr_repository.my_app]
provisioner "local-exec" {
interpreter = ["bash", "-c"]
command = <<-EOT
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin ${data.aws_caller_identity.current.account_id}.dkr.ecr.us-west-2.amazonaws.com
docker build -t my-python-app .
docker tag my-python-app:latest ${data.aws_caller_identity.current.account_id}.dkr.ecr.us-west-2.amazonaws.com/my-app:latest
docker push ${data.aws_caller_identity.current.account_id}.dkr.ecr.us-west-2.amazonaws.com/my-app:latest
EOT
}
}
# Define an IAM role for ECS task execution
resource "aws_iam_role" "custom-ecs-role" {
name = "custom-ecs-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}]
})
inline_policy {
name = "ECRPermissions"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
Resource = "*"
}
]
})
}
inline_policy {
name = "CloudWatchLogsPermissions"
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
Resource = "*"
}]
})
}
}
resource "aws_ecs_cluster" "my_cluster" {
name = "my-ecs-cluster"
}
# ECS Task security group
resource "aws_security_group" "ecs_task_sg" {
name = "ecs-task-sg"
description = "Security group for ECS tasks"
vpc_id = aws_vpc.CustomVPC.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Create an ECS service
resource "aws_ecs_service" "my_service" {
name = "my-service"
cluster = aws_ecs_cluster.my_cluster.id
task_definition = aws_ecs_task_definition.my_task.arn
launch_type = "FARGATE"
desired_count = 1
network_configuration {
subnets = data.aws_subnets.GetSubnet.ids
security_groups = [aws_security_group.ecs_task_sg.id]
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.CustomTG.arn
container_name = "ECS_Container"
container_port = 80
}
depends_on = [aws_lb_listener.http]
}
# Define the ECS task definition
resource "aws_ecs_task_definition" "my_task" {
depends_on = [null_resource.docker_build_push]
family = "ECS_Task"
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
task_role_arn = aws_iam_role.custom-ecs-role.arn
execution_role_arn = aws_iam_role.custom-ecs-role.arn
cpu = 1024
memory = 2048
container_definitions = jsonencode([{
name = "ECS_Container"
image = "${data.aws_caller_identity.current.account_id}.dkr.ecr.us-west-2.amazonaws.com/my-app:latest"
portMappings = [{
containerPort = 80
hostPort = 80
protocol = "tcp"
}]
"logConfiguration" : {
"logDriver" : "awslogs",
"options" : {
"awslogs-group" : "/ecs/cb-app",
"awslogs-region" : "us-west-2",
"awslogs-stream-prefix" : "ecs"
}
}
}])
}
# Create an Application Auto Scaling Target
resource "aws_appautoscaling_target" "ecs_service" {
max_capacity = 4
min_capacity = 1
resource_id = "service/${aws_ecs_cluster.my_cluster.name}/${aws_ecs_service.my_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
# Create an Application Auto Scaling Policy for Scaling Out
resource "aws_appautoscaling_policy" "scale_out_policy" {
name = "scale-out"
service_namespace = "ecs"
resource_id = aws_appautoscaling_target.ecs_service.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_service.scalable_dimension
policy_type = "StepScaling"
step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
}
}
# Create an Application Auto Scaling Policy for Scaling In
resource "aws_appautoscaling_policy" "scale_in_policy" {
name = "scale-in"
service_namespace = "ecs"
resource_id = aws_appautoscaling_target.ecs_service.resource_id
scalable_dimension = aws_appautoscaling_target.ecs_service.scalable_dimension
policy_type = "StepScaling"
step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Average"
step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = -1
}
}
}
# CloudWatch Alarm for Scaling Out
resource "aws_cloudwatch_metric_alarm" "scale_out_alarm" {
alarm_name = "ecs-scale-out-alarm"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "75"
alarm_description = "Alarm for scaling out ECS service"
dimensions = {
ClusterName = aws_ecs_cluster.my_cluster.name
ServiceName = aws_ecs_service.my_service.name
}
alarm_actions = [aws_appautoscaling_policy.scale_out_policy.arn]
}
# CloudWatch Alarm for Scaling In
resource "aws_cloudwatch_metric_alarm" "scale_in_alarm" {
alarm_name = "ecs-scale-in-alarm"
comparison_operator = "LessThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "25"
alarm_description = "Alarm for scaling in ECS service"
dimensions = {
ClusterName = aws_ecs_cluster.my_cluster.name
ServiceName = aws_ecs_service.my_service.name
}
alarm_actions = [aws_appautoscaling_policy.scale_in_policy.arn]
}
This Terraform code sets up an ECR repository, ECS cluster, task definition, and ECS service to deploy the Flask application.
👁🗨👁🗨 YouTube Tutorial 📽
❗️❗️Important Documentation To Be Viewed❗️❗️
⛔️ Hashicorp Terraform
⛔️ AWS CLI
⛔️ Hashicorp Terraform Extension Guide
⛔️ Terraform Autocomplete Extension Guide
⛔️ AWS Subnet
⛔️ AWS Route Table
⛔️ AWS Route Table Association
⛔️ NAT Gateway
⛔️ Elastic IP
🥁🥁 Conclusion 🥁🥁
In conclusion, deploying a serverless Python Flask app on AWS ECS Fargate using Terraform offers a powerful and scalable solution for modern web applications. By following this guide, you’ve learned how to set up Flask app on AWS ECS with Terraform, automate the deployment process, and leverage best practices for serverless Flask app AWS ECS. Whether you’re building a simple Python web app or a complex containerized Flask application, using Terraform infrastructure as code ensures a consistent and efficient deployment. Remember, deploying scalable Flask apps on AWS Fargate not only simplifies your workflow but also enhances your app’s performance and reliability. Happy deploying!
By following these steps, you can easily deploy and scale your applications on AWS ECS Fargate using Terraform.
Happy coding!
📢 Stay tuned for my next blog…..
So, did you find my content helpful? If you did or like my other content, feel free to buy me a coffee. Thanks.
Author - Dheeraj Choudhary
RELATED ARTICLES
Automate S3 Data ETL Pipelines With AWS Glue Using Terraform
Discover how to automate your S3 data ETL pipelines using AWS Glue and Terraform in this step-by-step tutorial. Learn to efficiently manage and process your data, leveraging the power of AWS Glue for seamless data transformation. Follow along as we demonstrate how to set up Terraform scripts, configure AWS Glue, and automate data workflows.
Automating AWS Infrastructure with Terraform Functions
IntroductionManaging cloud infrastructure can be complex and time-consuming. Terraform, an open-source Infrastructure as Code (IaC) tool, si ...