Deploying Flask Application on AWS EC2 using Load Balancer (ELB) and Auto Scaling Group (ASG)

  • How to create our Amazon Machine Image (AMI).
  • How to use Terraform to deploy our Infrastructure as code (IaC).
  • Create and deploy a Flask application using Gunicorn and Nginx.
  • How to use Elastic Load Balancer, Auto Scaling Group and EC2 instances for a classic architecture solution.
# security_groups.tfresource "aws_security_group" "security_group_ec2_instances" {
name = "security-group-ec2-instances"
description = "Security group for EC2 instances..."

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"]
}

ingress {
from_port = 22
to_port = 22
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"]
}
}
$ terraform init
$ terraform apply
$ chmod 0400 /home/alejandro/Downloads/ec2-access-key.pem
$ ssh ec2-user@3.82.209.22 -i /home/alejandro/Downloads/ec2-access-key.pem
$ sudo yum update  // --> First, perform the system update...
$ sudo yum install python3
$ git clone https://github.com/alekglez/flask_app_deploy_ec2_elb_asg.git// Rename the folder...
$ mv flask_app_deploy_ec2_elb_asg flask-application
$ cd flask-application# Install and create virtual environment...
$ sudo pip3 install virtualenv
$ virtualenv --python=python3 .venv
$ source .venv/bin/activate
# Install requirements...
$ pip install -r requirements.txt
$ sudo amazon-linux-extras install nginx1.12
$ sudo touch /etc/systemd/system/gunicorn.service
$ sudo nano /etc/systemd/system/gunicorn.service
#  /etc/systemd/system/gunicorn.service[Unit]
Description=gunicorn daemon for serve flask application...
After=network.target
[Service]
User=ec2-user
Group=nginx
WorkingDirectory=/home/ec2-user/flask-application
ExecStart=/home/ec2-user/flask-application/.venv/bin/gunicorn --workers 3 --bind unix:/home/ec2-user/flask-application/flask-application.sock wsgi:app
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable gunicorn
$ sudo systemctl start gunicorn
# Run this command...
$ sudo systemctl status gunicorn
# You should see something like that...
gunicorn.service - gunicorn daemon for serve flask application...
Loaded: loaded (/etc/systemd/system/gunicorn.service; enabled; vendor preset: disabled)
Active: active (running) since sáb 2020-01-25 12:56:07 UTC; 9s ago
Main PID: 3914 (gunicorn)
CGroup: /system.slice/gunicorn.service
├─3914 /home/ec2-user/flask-application/.venv/bin/python3 /home/ec2-user/flask-application/.venv/bin/gunicorn --workers 3 --bind unix:/home/ec2-user/flask-application/flask-application.sock wsgi:app
├─3917 /home/ec2-user/flask-application/.venv/bin/python3 /home/ec2-user/flask-application/.venv/bin/gunicorn --workers 3 --bind unix:/home/ec2-user/flask-application/flask-application.sock wsgi:app
├─3918 /home/ec2-user/flask-application/.venv/bin/python3 /home/ec2-user/flask-application/.venv/bin/gunicorn --workers 3 --bind unix:/home/ec2-user/flask-application/flask-application.sock wsgi:app
└─3919 /home/ec2-user/flask-application/.venv/bin/python3 /home/ec2-user/flask-application/.venv/bin/gunicorn --workers 3 --bind unix:/home/ec2-user/flask-application/flask-application.sock wsgi:app
$ sudo nano /etc/nginx/nginx.conf
server {
...
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://unix:/home/ec2-user/flask-application/flask-application.sock;
}

...
}
$ sudo usermod -a -G ec2-user nginx
$ chmod 710 /home/ec2-user
$ sudo nginx -t
$ sudo systemctl enable nginx
$ sudo systemctl start nginx
$ sudo systemctl status nginx
// Security group for EC2 instances...
resource "aws_security_group" "security_group_ec2_instances" {
name = "security-group-ec2-instances"
description = "Security group for EC2 instances..."

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.security_group_elb.id]
}

ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.security_group_elb.id]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
// Security group for Load Balancer...
resource "aws_security_group" "security_group_elb" {
name = "security-group-elb"
description = "Security group for Application Load Balancer..."

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"]
}
}
// AMI template image...
resource "aws_launch_template" "image_flask_app_ec2_instance" {
name_prefix = "flask-app-ec2-instance"
image_id = "ami-08f3c04e8b4b32ce5" // <-- Replace the Image ID!
instance_type = "t2.micro"
}
// Target group...
resource "aws_alb_target_group" "flask_app_ec2_target_group" {
name = "flask-app-ec2-target-group"
vpc_id = data.aws_vpc.default.id
protocol = "HTTP"
port = 80
}
// Auto Scaling Group...
resource "aws_autoscaling_group" "flask_app_autoscaling_group" {
name = "flask_app_autoscaling_group"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
target_group_arns = [aws_alb_target_group.flask_app_ec2_target_group.arn]

default_cooldown = 60
health_check_grace_period = 60
health_check_type = "ELB"

desired_capacity = 1

force_delete = true
max_size = 5
min_size = 1

launch_template {
id = aws_launch_template.image_flask_app_ec2_instance.id
version = "$Latest"
}

tag {
key = "asg"
value = "flask_app_autoscaling_group"
propagate_at_launch = true
}
}
// Example policy for Auto Scaling Group...
resource "aws_autoscaling_policy" "autoscaling_policy_by_requests" {
name = "autoscaling-policy-by-requests"
autoscaling_group_name = aws_autoscaling_group.flask_app_autoscaling_group.name
policy_type = "TargetTrackingScaling"

target_tracking_configuration {
target_value = 4000

predefined_metric_specification {
predefined_metric_type = "ASGAverageNetworkOut"
}
}
}
provider "aws" {
region = "us-east-1"
}
// Default VPC...
data "aws_vpc" "default" {
default = true
}
// Default subnets...
data "aws_subnet_ids" "all" {
vpc_id = data.aws_vpc.default.id
}
// Default security group...
data "aws_security_group" "default" {
vpc_id = data.aws_vpc.default.id
name = "default"
}

data "aws_elb_service_account" "main" {}
// Bucket to save the logs...
resource "aws_s3_bucket" "lb_flask_app_access_logs" {
bucket = "lb-flask-app-access-logs"
force_destroy = true
acl = "private"

policy = <<POLICY
{
"Id": "Policy",
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::lb-flask-app-access-logs/*",
"Principal": {
"AWS": [
"${data.aws_elb_service_account.main.arn}"
]
}
}
]
}
POLICY
}
// Application Load Balancer...
resource "aws_lb" "flask_app_load_balancer" {
name = "flask-app-load-balancer"
load_balancer_type = "application"

subnets = data.aws_subnet_ids.all.ids
security_groups = [aws_security_group.security_group_elb.id]

enable_cross_zone_load_balancing = true
enable_http2 = true
internal = false

access_logs {
bucket = aws_s3_bucket.lb_flask_app_access_logs.bucket
prefix = "lb-flask-app"
enabled = true
}

tags = {
Environment = "production"
}
}
// Listeners...
resource "aws_lb_listener" "lb_port_80_listener" {
load_balancer_arn = aws_lb.flask_app_load_balancer.arn
protocol = "HTTP"
port = 80

default_action {
target_group_arn = aws_alb_target_group.flask_app_ec2_target_group.arn
type = "forward"
}
}
// In your local machine (into your project folder)...
$ locust --port 8090
// Then access through:
http://localhost:8090
$ terraform destroy

--

--

--

Cloud & Solutions & Data Architect | Python Developer | Serverless Advocate

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

PyScript: Python in the browser

Colorful output in C

AZURE AD APP REGISTRATION — CREATE APPLICATION USING MS GRAPH API AND POWERSHELL

5 Fabulous Python Packages For Data-Science Nobody Knows About

Frontend Talks @SoftwareMill

Union Find and Prime Factorization

Welcome to Kontribute

The Essence of Event-Driven Architecture (EDA)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alejandro Cora González

Alejandro Cora González

Cloud & Solutions & Data Architect | Python Developer | Serverless Advocate

More from Medium

Cross Region Replication(CRR) of S3 buckets using terraform

How to Deploy a Red Hat Enterprise Linux (RHEL) EC2 Instance in AWS using Terraform

Better make for automation

How to Develop an HR App on the Cloud in 4 Hours