├── .gitignore ├── README.md ├── docker-compose.yml ├── src ├── Dockerfile ├── app │ ├── __init__.py │ ├── blueprints │ │ ├── __init__.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ └── views.py │ │ └── web │ │ │ ├── __init__.py │ │ │ ├── templates │ │ │ ├── home.html │ │ │ └── status.html │ │ │ └── views.py │ ├── models.py │ └── tasks.py ├── entrypoint.sh ├── instance │ └── config.py ├── requirements.txt └── run.py ├── terraform ├── alb.tf ├── main.tf ├── network.tf ├── outputs.tf ├── rds.tf └── variables.tf └── vars.env /.gitignore: -------------------------------------------------------------------------------- 1 | # mac 2 | .DS_Store 3 | 4 | # pycharm 5 | .idea/ 6 | 7 | # Compiled files 8 | *.tfstate 9 | *.tfstate.backup 10 | .terraform.tfstate.lock.info 11 | *.tfvars 12 | 13 | # Module directory 14 | .terraform/ 15 | 16 | # py 17 | __pycache__ 18 | .pyc 19 | ./__pycache__ 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Demo project for deploying apps into AWS 2 | 3 | ### 0. Contents 4 | - `src/` 5 | - contains a simple Python Flask application, which is hooked up to Postgres Database 6 | - the application was updated to utilize "threads" and "js polling" for long-running background tasks 7 | - `terraform/` 8 | - contains the terraform code necessary to deploy the application into AWS 9 | - infrastructure components are all automatically provisioned when `terraform apply` is run 10 | 11 | ### 1. Prereqs 12 | - docker 13 | - terraform 14 | - aws account with aws-cli installed and configured 15 | 16 | ### 2. How-to 17 | - work-in-progress... 18 | 19 | 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3.5' 3 | 4 | services: 5 | 6 | flask: 7 | build: 8 | context: . 9 | dockerfile: ./src/Dockerfile 10 | container_name: flask-app 11 | env_file: 12 | - vars.env 13 | ports: 14 | - "8080:8080" 15 | volumes: 16 | - "./src/:/flask-postgres/src/" 17 | entrypoint: "dockerize -wait tcp://flask-app-db:5432 -timeout 60s" 18 | command: ["/bin/bash", "-c", "entrypoint.sh"] 19 | 20 | postgres: 21 | image: postgres:11 22 | container_name: flask-app-db 23 | hostname: flask-app-db 24 | env_file: 25 | - vars.env 26 | ports: 27 | - "5432:5432" 28 | # volumes: 29 | # - ./data:/var/lib/postgres 30 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | 3 | RUN apt-get update && apt-get install -y wget 4 | 5 | ENV DOCKERIZE_VERSION v0.6.1 6 | RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 7 | && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \ 8 | && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz 9 | 10 | COPY ./src/requirements.txt /tmp/requirements.txt 11 | RUN pip install -r /tmp/requirements.txt --upgrade pip 12 | 13 | COPY ./src/entrypoint.sh /usr/local/bin/ 14 | RUN chmod u+x /usr/local/bin/entrypoint.sh 15 | 16 | COPY ./src/app/ /${APP_HOME}/app 17 | COPY ./src/instance /${APP_HOME}/instance 18 | COPY ./src/run.py /${APP_HOME} 19 | 20 | WORKDIR /${APP_HOME} 21 | EXPOSE ${APP_PORT} 22 | 23 | ENTRYPOINT ["/bin/bash", "-c", "entrypoint.sh"] 24 | -------------------------------------------------------------------------------- /src/app/__init__.py: -------------------------------------------------------------------------------- 1 | # app/__init__.py 2 | from flask import Flask 3 | import logging 4 | 5 | logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s') 6 | logging.getLogger(__name__).setLevel(logging.INFO) 7 | 8 | 9 | def create_app(): 10 | app = Flask(__name__, instance_relative_config=True) 11 | app.config.from_pyfile('config.py') 12 | from app.models import db 13 | db.init_app(app) 14 | from app.tasks import tm 15 | tm.init_app(app, db) 16 | with app.app_context(): 17 | db.create_all() 18 | from app.blueprints import web, api 19 | app.register_blueprint(web) 20 | app.register_blueprint(api, url_prefix='/api') 21 | return app 22 | 23 | -------------------------------------------------------------------------------- /src/app/blueprints/__init__.py: -------------------------------------------------------------------------------- 1 | from .api.views import api 2 | from .web.views import web 3 | -------------------------------------------------------------------------------- /src/app/blueprints/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pogzyb/deploy-flask-into-aws-with-terraform/fa0a9547cb4dc16bbe4c91674bb4466d10568eb3/src/app/blueprints/api/__init__.py -------------------------------------------------------------------------------- /src/app/blueprints/api/views.py: -------------------------------------------------------------------------------- 1 | # app/blueprints/api/views.py 2 | from flask import ( 3 | Blueprint, 4 | jsonify, 5 | request 6 | ) 7 | from uuid import uuid1 8 | import logging 9 | 10 | from app.models import Wiki 11 | from app.tasks import tm 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | # define "api" blueprint 17 | api = Blueprint('api', __name__) 18 | 19 | 20 | @api.route('/new', methods=['POST']) 21 | def new(): 22 | data = dict(request.json) 23 | data['uid'] = str(uuid1()) 24 | term = data.get('term') 25 | tm.create_task(data=data) 26 | payload = { 27 | 'term': term, 28 | 'info': f'check the status for this term @ "/api/one/{term}"' 29 | } 30 | return jsonify({'status': 'success', 'data': payload}), 201 31 | 32 | 33 | @api.route('/one/', methods=['GET']) 34 | def one(term): 35 | wiki = Wiki.query.filter_by(term=term).first() 36 | if not wiki: 37 | return jsonify({'status': 'not found'}), 404 38 | payload = { 39 | 'term': wiki.term, 40 | 'status': wiki.status.value, 41 | 'messages': wiki.messages, 42 | 'links': wiki.links, 43 | } 44 | return jsonify({'status': 'success', 'data': payload}), 200 45 | 46 | 47 | @api.route('/all', methods=['GET']) 48 | def everything(): 49 | payload = [] 50 | all_wikis = Wiki.query.all() 51 | for wiki in all_wikis: 52 | individual = { 53 | 'term': wiki.term, 54 | 'status': wiki.status.value, 55 | 'messages': wiki.messages, 56 | 'links': wiki.links, 57 | } 58 | payload.append(individual) 59 | return jsonify({'status': 'success', 'data': payload}), 200 60 | 61 | 62 | @api.route('/poll/', methods=['GET']) 63 | def poll(term: str): 64 | wiki = tm.get_record(term) 65 | payload = { 66 | 'messages': wiki.messages, 67 | 'status': wiki.status.value 68 | } 69 | return jsonify({'status': 'success', 'poll': payload}), 200 70 | -------------------------------------------------------------------------------- /src/app/blueprints/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pogzyb/deploy-flask-into-aws-with-terraform/fa0a9547cb4dc16bbe4c91674bb4466d10568eb3/src/app/blueprints/web/__init__.py -------------------------------------------------------------------------------- /src/app/blueprints/web/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pogzyb · flask-psql-terraform-aws 10 | 11 | 12 | 13 | 14 | 16 | 17 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |

Wiki-Scraper!

51 |

This is just a simple Flask Web Application for demo purposes.
Try it! Enter something in the search box...

52 |

Source code can be found on github

53 |
54 | 55 | 56 |
57 |
58 | {% if items['alert'] %} 59 | 65 | {% endif %} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {% for item in items['wikis'] %} 75 | 76 | 77 | 78 | 79 | {% endfor %} 80 |
Recent Search TermsStatus
{{ item.term }}{{ item.status.value }}
81 |
82 |
83 | 84 |
85 |
86 | Version 0.1.0 · Release 7/30/2019 87 | Home 88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 99 | -------------------------------------------------------------------------------- /src/app/blueprints/web/templates/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pogzyb · flask-psql-terraform-aws 10 | 11 | 12 | 13 | 14 | 16 | 17 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |

Status Page for {{ items['wiki'].term }}

51 |

Wikipedia scraping could take a few seconds to complete...

52 |
53 |
54 | 58 |
59 |
60 |
61 |
62 |
63 | Version 0.1.0 · Release 7/30/2019 64 | Home 65 |
66 |
67 | 68 | 69 | 70 | 71 | 72 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/app/blueprints/web/views.py: -------------------------------------------------------------------------------- 1 | # app/blueprints/web/views.py 2 | from flask import ( 3 | Blueprint, 4 | request, 5 | render_template, 6 | url_for, 7 | redirect 8 | ) 9 | from uuid import uuid1 10 | import logging 11 | 12 | from app.tasks import tm 13 | from app.models import Wiki 14 | 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | # define "web" blueprint 19 | web = Blueprint('web', __name__, template_folder='templates') 20 | 21 | 22 | @web.route('/', methods=['GET', 'POST']) 23 | def home(): 24 | """ 25 | This is the homepage of the app. 26 | Right now, it displays every "Wiki" search done in the database 27 | """ 28 | wikis = Wiki.query.all() 29 | return render_template('home.html', items={'wikis': wikis, 'alert': None}) 30 | 31 | 32 | @web.route('/background', methods=['POST']) 33 | def background(): 34 | try: 35 | form = dict(request.form) 36 | form['uid'] = str(uuid1()) 37 | exists = Wiki.query.filter_by(term=form['term']).first() 38 | if exists: 39 | return redirect(url_for('.home')) 40 | else: 41 | tm.create_task(data=form) 42 | return redirect(url_for('.status', term=form.get('term'))) 43 | except Exception as e: 44 | people = Wiki.query.all() 45 | return render_template('home.html', items={'people': people, 'alert': ('danger', f'{e}')}) 46 | 47 | 48 | @web.route('/status/', methods=['GET']) 49 | def status(term: str): 50 | record = tm.get_record(term) 51 | if not record: 52 | wikis = Wiki.query.all() 53 | return render_template('home.html', items={ 54 | 'wikis': wikis, 55 | 'alert': ('danger', f'Could not find a task for {term}') 56 | }) 57 | else: 58 | return render_template('status.html', items={'wiki': record}) 59 | -------------------------------------------------------------------------------- /src/app/models.py: -------------------------------------------------------------------------------- 1 | # app/models.py 2 | import sqlalchemy as sa 3 | from sqlalchemy.dialects.postgresql import ARRAY 4 | from flask_sqlalchemy import SQLAlchemy 5 | from enum import Enum 6 | 7 | 8 | db = SQLAlchemy() 9 | 10 | 11 | class Status(Enum): 12 | pending = 'Pending' 13 | complete = 'Complete' 14 | failed = 'Failed' 15 | 16 | 17 | class Wiki(db.Model): 18 | __tablename__ = 'wiki' 19 | id = db.Column(db.Integer, primary_key=True) 20 | uid = db.Column(db.String(64), nullable=False) 21 | term = db.Column(db.String(250), unique=True, nullable=False) 22 | status = db.Column(sa.Enum(Status), nullable=False, info={'enum_class': Status}) 23 | messages = db.Column(ARRAY(sa.String(250))) 24 | links = db.Column(ARRAY(sa.String(250))) 25 | -------------------------------------------------------------------------------- /src/app/tasks.py: -------------------------------------------------------------------------------- 1 | # app/tasks.py 2 | from concurrent.futures import ThreadPoolExecutor, Future 3 | from threading import RLock 4 | from flask import Flask 5 | from flask_sqlalchemy import SQLAlchemy 6 | from typing import Dict, Any 7 | import random 8 | import logging 9 | import datetime 10 | import time 11 | 12 | from app.models import Wiki, Status 13 | 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class TaskManager(object): 19 | """ 20 | Manages kicking off "Tasks" and returning tracking their results 21 | """ 22 | def __init__(self, max_workers: int = 2): 23 | self.task_status_map = {} 24 | self._pool = ThreadPoolExecutor(max_workers=max_workers) 25 | self._lock = RLock() 26 | self._app = None 27 | self._db = None 28 | 29 | def init_app(self, app: Flask, db: SQLAlchemy) -> None: 30 | self._app = app 31 | self._db = db 32 | 33 | def create_task(self, data) -> None: 34 | term = data.get('term') 35 | task = Task( 36 | uid=data.get('uid'), 37 | app_ref=self._app, 38 | db_ref=self._db, 39 | tm_ref=self 40 | ) 41 | submitted_task = self._pool.submit(task.do_search, data, self._lock) 42 | logger.info(f'Submitted task for {term}') 43 | submitted_task.add_done_callback(task.done_callback) 44 | 45 | def get_record(self, term: str) -> Wiki: 46 | with self._app.app_context(): 47 | record = self._db.session.query(Wiki).filter_by(term=term).first() 48 | if not record: 49 | return None 50 | else: 51 | return record 52 | 53 | def check_done(self, term: str) -> bool: 54 | with self._app.app_context(): 55 | record = self._db.session.query(Wiki).filter_by(term=term).first() 56 | if not record: 57 | return False 58 | return record.status == Status.complete 59 | 60 | def update_status(self, uid: str, status: Status) -> None: 61 | with self._app.app_context(): 62 | record = self._db.session.query(Wiki).filter_by(uid=uid).first() 63 | record.status = status 64 | self._db.session.commit() 65 | logger.info(f'Updated status for {record.term}') 66 | 67 | def update_messages(self, term: str, message: str) -> None: 68 | with self._app.app_context(): 69 | record = self._db.session.query(Wiki).filter_by(term=term).first() 70 | modified = list(record.messages) 71 | modified.append(f'{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - {message}') 72 | record.messages = modified 73 | self._db.session.commit() 74 | logger.info(f'Updated messages for {term}') 75 | 76 | 77 | class Task(object): 78 | """ 79 | Performs the actual Task and saves results to the database 80 | """ 81 | def __init__(self, uid: str, app_ref: Flask, db_ref: SQLAlchemy, tm_ref: TaskManager): 82 | self._app = app_ref 83 | self._db = db_ref 84 | self._tm = tm_ref 85 | self._uid = uid 86 | self._status = Status.pending 87 | 88 | def do_search(self, data: Dict[str, Any], lock: RLock) -> None: 89 | with lock: 90 | term = data.get('term') 91 | logger.info(f'Acquired Locked! Saving initial data for {term}') 92 | search = Wiki( 93 | term=term, 94 | uid=self._uid, 95 | status=self._status, 96 | messages=[ 97 | f'{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} - Starting Wikipedia scrape for {term}!' 98 | ] 99 | ) 100 | with self._app.app_context(): 101 | self._db.session.add(search) 102 | self._db.session.commit() 103 | logger.info(f'Releasing Lock! Saved {term}') 104 | 105 | # search and parse Wikipedia 106 | time.sleep(random.randint(0, 10)) 107 | self._tm.update_messages(term, 'Almost done!') 108 | time.sleep(random.randint(0, 10)) 109 | self._status = Status.complete 110 | 111 | def done_callback(self, fut_obj: Future) -> None: 112 | fut_obj.result() 113 | logger.info(f'Attempting to update status to {self._status.value}') 114 | self._tm.update_status(self._uid, self._status) 115 | 116 | 117 | tm = TaskManager() 118 | -------------------------------------------------------------------------------- /src/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # use gunicorn in production 4 | gunicorn --bind 0.0.0.0:${APP_PORT} run:app 5 | 6 | # use flask cli for development 7 | # flask run --host 0.0.0.0 --port ${APP_PORT} -------------------------------------------------------------------------------- /src/instance/config.py: -------------------------------------------------------------------------------- 1 | # instance/config.py 2 | import os 3 | 4 | SECRET_KEY = os.getenv('APP_SECRET_KEY') 5 | BASEDIR = os.path.dirname(os.path.abspath(__file__)) 6 | SQLALCHEMY_TRACK_MODIFICATIONS = False 7 | SQLALCHEMY_DATABASE_URI = \ 8 | f'postgres://' \ 9 | f'{os.getenv("POSTGRES_USER")}:' \ 10 | f'{os.getenv("POSTGRES_PASSWORD")}@' \ 11 | f'{os.getenv("POSTGRES_HOSTNAME")}:' \ 12 | f'{os.getenv("POSTGRES_PORT")}/' \ 13 | f'{os.getenv("POSTGRES_DATABASE")}' 14 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.1 2 | Flask-SQLAlchemy==2.4.1 3 | psycopg2-binary==2.8.4 4 | gunicorn==20.0.4 -------------------------------------------------------------------------------- /src/run.py: -------------------------------------------------------------------------------- 1 | # src/run.py 2 | # application entrypoint used by gunicorn 3 | from app import create_app 4 | import os 5 | 6 | app = create_app() 7 | 8 | if __name__ == '__main__': 9 | app.run(threaded=True, host='0.0.0.0', port=os.getenv('APP_PORT')) 10 | -------------------------------------------------------------------------------- /terraform/alb.tf: -------------------------------------------------------------------------------- 1 | /******** 2 | 3 | terraform/alb.tf contains all the necessary resources to 4 | setup the Application Load Balancer for the ECS application 5 | 6 | Resources: 7 | - Application Load Balancer 8 | - Security Groups 9 | 10 | *********/ 11 | 12 | # create a security group to access the ECS application 13 | resource "aws_security_group" "fp-alb-sg" { 14 | name = "fp-app-alb" 15 | description = "control access to the application load balancer" 16 | vpc_id = aws_vpc.fp-vpc.id 17 | 18 | ingress { 19 | from_port = 80 20 | protocol = "TCP" 21 | to_port = 80 22 | cidr_blocks = ["0.0.0.0/0"] 23 | } 24 | 25 | egress { 26 | from_port = 0 27 | protocol = "-1" 28 | to_port = 0 29 | cidr_blocks = ["0.0.0.0/0"] 30 | } 31 | } 32 | 33 | # create security group to access the ecs cluster (traffic to ecs cluster should only come from the ALB) 34 | resource "aws_security_group" "fp-ecs-sg" { 35 | name = "fp-app-ecs-from-alb" 36 | description = "control access to the ecs cluster" 37 | vpc_id = aws_vpc.fp-vpc.id 38 | 39 | ingress { 40 | from_port = var.flask_app_port 41 | protocol = "TCP" 42 | to_port = var.flask_app_port 43 | security_groups = [aws_security_group.fp-alb-sg.id] 44 | } 45 | 46 | egress { 47 | protocol = "-1" 48 | from_port = 0 49 | to_port = 0 50 | cidr_blocks = ["0.0.0.0/0"] 51 | } 52 | } 53 | 54 | # create the ALB 55 | resource "aws_alb" "fp-alb" { 56 | load_balancer_type = "application" 57 | name = "fp-alb" 58 | subnets = aws_subnet.fp-public-subnets.*.id 59 | security_groups = [aws_security_group.fp-alb-sg.id] 60 | } 61 | 62 | # point redirected traffic to the app 63 | resource "aws_alb_target_group" "fp-target-group" { 64 | name = "fp-ecs-target-group" 65 | port = 80 66 | protocol = "HTTP" 67 | vpc_id = aws_vpc.fp-vpc.id 68 | target_type = "ip" 69 | } 70 | 71 | # direct traffic through the ALB 72 | resource "aws_alb_listener" "fp-alb-listener" { 73 | load_balancer_arn = aws_alb.fp-alb.arn 74 | port = 80 75 | protocol = "HTTP" 76 | default_action { 77 | target_group_arn = aws_alb_target_group.fp-target-group.arn 78 | type = "forward" 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | profile = "default" 3 | region = "us-east-2" 4 | } 5 | 6 | /* 7 | 8 | terraform/main.tf contains only resources strictly 9 | related to deploying the application in ECS 10 | 11 | */ 12 | 13 | # random string for flask secret-key env variable 14 | resource "random_string" "flask-secret-key" { 15 | length = 16 16 | special = true 17 | override_special = "/@\" " 18 | } 19 | 20 | 21 | # create the ECS cluster 22 | resource "aws_ecs_cluster" "fp-ecs-cluster" { 23 | name = "flask-app" 24 | 25 | tags = { 26 | Name = "flask-app" 27 | } 28 | } 29 | 30 | # create and define the container task 31 | resource "aws_ecs_task_definition" "fp-ecs-task" { 32 | family = "flask-app" 33 | requires_compatibilities = ["FARGATE"] 34 | network_mode = "awsvpc" 35 | cpu = 512 36 | memory = 2048 37 | container_definitions = < number of public subnets in each availability zone 64 | resource "aws_subnet" "fp-public-subnets" { 65 | count = 2 66 | cidr_block = var.public_cidrs[count.index] 67 | vpc_id = aws_vpc.fp-vpc.id 68 | map_public_ip_on_launch = true 69 | availability_zone = data.aws_availability_zones.azs.names[count.index] 70 | 71 | tags = { 72 | Name = "flask-postgres-tf-public-${count.index + 1}" 73 | } 74 | } 75 | 76 | # create number of private subnets in each availability zone 77 | resource "aws_subnet" "fp-private-subnets" { 78 | count = 2 79 | cidr_block = var.private_cidrs[count.index] 80 | # cidr_block = cidrsubnet(aws_vpc.fp-vpc.cidr_block, 8, count.index) 81 | availability_zone = data.aws_availability_zones.azs.names[count.index] 82 | vpc_id = aws_vpc.fp-vpc.id 83 | 84 | tags = { 85 | Name = "flask-postgres-tf-private-${count.index + 1}" 86 | } 87 | } 88 | 89 | # create db subnet group 90 | resource "aws_db_subnet_group" "fp-db-subnet" { 91 | name = "postgres-db-subnet-group" 92 | subnet_ids = aws_subnet.fp-private-subnets.*.id 93 | 94 | tags = { 95 | Name = "flask-postgres-db-subnet" 96 | } 97 | } 98 | 99 | # associate the public subnets with the public route table 100 | resource "aws_route_table_association" "fp-public-rt-assc" { 101 | count = 2 102 | route_table_id = aws_route_table.fp-rt-public.id 103 | subnet_id = aws_subnet.fp-public-subnets.*.id[count.index] 104 | } 105 | 106 | # associate the private subnets with the public route table 107 | resource "aws_route_table_association" "fp-private-rt-assc" { 108 | count = 2 109 | route_table_id = aws_route_table.fp-rt-public.id 110 | subnet_id = aws_subnet.fp-private-subnets.*.id[count.index] 111 | } 112 | 113 | # create security group 114 | resource "aws_security_group" "fp-public-sg" { 115 | name = "fp-public-group" 116 | description = "access to public instances" 117 | vpc_id = aws_vpc.fp-vpc.id 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "alb-dns-name" { 2 | value = aws_alb.fp-alb.dns_name 3 | } -------------------------------------------------------------------------------- /terraform/rds.tf: -------------------------------------------------------------------------------- 1 | /******** 2 | 3 | terraform/rds.tf contains all the necessary resources to 4 | setup the RDS postgres database for the ECS application 5 | 6 | Resources: 7 | - Application Load Balancer 8 | - AWS RDS Database (Postgres) 9 | - Security Groups 10 | 11 | *********/ 12 | 13 | # create security group 14 | resource "aws_security_group" "rds-db-sg" { 15 | name = "postgres-security-group" 16 | vpc_id = aws_vpc.fp-vpc.id 17 | 18 | # Only postgres in 19 | ingress { 20 | from_port = var.postgres_db_port 21 | to_port = var.postgres_db_port 22 | protocol = "tcp" 23 | cidr_blocks = ["0.0.0.0/0"] 24 | } 25 | 26 | # Allow all outbound traffic 27 | egress { 28 | from_port = 0 29 | to_port = 0 30 | protocol = "-1" 31 | cidr_blocks = ["0.0.0.0/0"] 32 | } 33 | } 34 | 35 | # create the RDS instance 36 | resource "aws_db_instance" "fp-rds" { 37 | allocated_storage = 20 38 | storage_type = "gp2" 39 | engine = "postgres" 40 | engine_version = "11" 41 | instance_class = var.rds_instance_type 42 | name = "postgresdb" 43 | username = "root" 44 | password = "admin123" 45 | port = var.postgres_db_port 46 | vpc_security_group_ids = [aws_security_group.rds-db-sg.id] 47 | parameter_group_name = "default.postgres11" 48 | db_subnet_group_name = aws_db_subnet_group.fp-db-subnet.name 49 | publicly_accessible = false 50 | allow_major_version_upgrade = false 51 | auto_minor_version_upgrade = false 52 | apply_immediately = true 53 | storage_encrypted = false 54 | } 55 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | /******** 2 | variables 3 | *********/ 4 | 5 | variable "vpc_cidr" { 6 | description = "The CIDR Block for the SiteSeer VPC" 7 | default = "10.0.0.0/16" 8 | } 9 | 10 | variable "rt_wide_route" { 11 | description = "Route in the SiteSeer Route Table" 12 | default = "0.0.0.0/0" 13 | } 14 | 15 | variable "public_cidrs" { 16 | description = "Public Subnet CIDR Blocks" 17 | default = [ 18 | "10.0.1.0/24", 19 | "10.0.2.0/24" 20 | ] 21 | } 22 | 23 | variable "private_cidrs" { 24 | description = "Public Subnet CIDR Blocks" 25 | default = [ 26 | "10.0.3.0/24", 27 | "10.0.4.0/24" 28 | ] 29 | } 30 | 31 | variable "flask_app_image" { 32 | description = "Dockerhub image for flask-app" 33 | default = "docker.io/doodmanbro/flask-app:0.1.0" 34 | } 35 | 36 | variable "flask_app_port" { 37 | description = "Port exposed by the flask application" 38 | default = 8080 39 | } 40 | 41 | variable "flask_env" { 42 | description = "FLASK ENV variable" 43 | default = "production" 44 | } 45 | 46 | variable "flask_app" { 47 | description = "FLASK APP variable" 48 | default = "app" 49 | } 50 | 51 | variable "app_home" { 52 | description = "APP HOME variable" 53 | default = "flask-postgres/src/" 54 | } 55 | 56 | variable "postgres_db_port" { 57 | description = "Port exposed by the RDS instance" 58 | default = 5432 59 | } 60 | 61 | variable "rds_instance_type" { 62 | description = "Instance type for the RDS database" 63 | default = "db.t2.micro" 64 | } -------------------------------------------------------------------------------- /vars.env: -------------------------------------------------------------------------------- 1 | # Set timezone 2 | TZ=UTC 3 | 4 | # Database 5 | POSTGRES_USER=root 6 | POSTGRES_PASSWORD=admin123 7 | POSTGRES_DATABASE=root 8 | POSTGRES_HOSTNAME=flask-app-db 9 | POSTGRES_PORT=5432 10 | 11 | # Application 12 | APP_PORT=8080 13 | APP_HOME=flask-postgres/src/ 14 | APP_SECRET_KEY=012345678900987654321 15 | FLASK_APP=app 16 | FLASK_ENV=development --------------------------------------------------------------------------------