Skip to content

Commit

Permalink
we are doing infra as code ladies and gents
Browse files Browse the repository at this point in the history
  • Loading branch information
voynow committed Nov 9, 2024
1 parent 322cc8d commit 2651609
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 0 deletions.
201 changes: 201 additions & 0 deletions infra/app/ecs/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# ALB
resource "aws_security_group" "alb" {
name = "${var.app_name}-alb-sg"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
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 = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb" "this" {
name = "${var.app_name}-alb"
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
}
resource "aws_lb_target_group" "this" {
name = "${var.app_name}-lb-tg"
vpc_id = var.vpc_id
port = 80
protocol = "HTTP"
target_type = "ip"
health_check {
port = 80
path = "/docs"
interval = 30
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
matcher = 200
}
}
resource "aws_lb_listener" "http" {
port = "80"
protocol = "HTTP"
load_balancer_arn = aws_lb.this.arn
default_action {
target_group_arn = aws_lb_target_group.this.arn
type = "forward"
}
depends_on = [aws_lb_target_group.this]
}
resource "aws_lb_listener_rule" "this" {
listener_arn = aws_lb_listener.http.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
condition {
path_pattern {
values = ["*"]
}
}
}

# IAM
data "aws_iam_policy_document" "ecs_assume_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ecs_execution_role" {
name = "${var.app_name}-execution-role"
assume_role_policy = data.aws_iam_policy_document.ecs_assume_policy.json
}
resource "aws_iam_policy" "ecs_execution_policy" {
name = "${var.app_name}-ecs-execution-role-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect : "Allow",
Action : [
"ecr:*",
"ecs:*",
"elasticloadbalancing:*",
"cloudwatch:*",
"logs:*"
],
Resource : "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy_attach" {
role = aws_iam_role.ecs_execution_role.name
policy_arn = aws_iam_policy.ecs_execution_policy.arn
}

# ECS
resource "aws_cloudwatch_log_group" "ecs" {
name = "/aws/ecs/${var.app_name}/cluster"
}
resource "aws_ecs_task_definition" "api" {
family = "${var.app_name}-api-task"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
execution_role_arn = aws_iam_role.ecs_execution_role.arn
task_role_arn = aws_iam_role.ecs_execution_role.arn
cpu = 256
memory = 512
container_definitions = jsonencode([
{
name = "${var.app_name}-api-container"
image = "${var.image}"
command = ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "80"]
portMappings = [
{
hostPort = 80
containerPort = 80
protocol = "tcp"
}
],
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.ecs.name
awslogs-stream-prefix = "ecs"
awslogs-region = var.region
}
}
environment = [
{
name = "SUPABASE_URL",
value = var.supabase_url
},
{
name = "SUPABASE_KEY",
value = var.supabase_key
}
]
}
])
}

# Cluster
resource "aws_ecs_cluster" "this" {
name = "${var.app_name}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}

# Security Group and Service
resource "aws_security_group" "ecs" {
name = "${var.app_name}-ecs-sg"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
}
resource "aws_ecs_service" "api" {
name = "${var.app_name}-ecs-service"
cluster = aws_ecs_cluster.this.name
launch_type = "FARGATE"
desired_count = length(var.private_subnet_ids)
task_definition = aws_ecs_task_definition.api.arn
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.ecs.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.this.arn
container_name = "${var.app_name}-api-container"
container_port = "80"
}
lifecycle {
ignore_changes = [
desired_count,
]
}
depends_on = [aws_lb_listener_rule.this]
}

3 changes: 3 additions & 0 deletions infra/app/ecs/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "alb_dns_name" {
value = aws_lb.this.dns_name
}
34 changes: 34 additions & 0 deletions infra/app/ecs/variable.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
variable "app_name" {
description = "Name of the app."
type = string
}
variable "region" {
description = "AWS region to deploy the network to."
type = string
}
variable "image" {
description = "Image used to start the container. Should be in repository-url/image:tag format."
type = string
}
variable "vpc_id" {
description = "ID of the VPC where the ECS will be hosted."
type = string
}
variable "public_subnet_ids" {
description = "IDs of public subnets where the ALB will be attached to."
type = list(string)
}
variable "private_subnet_ids" {
description = "IDs of private subnets where the ECS service will be deployed to."
type = list(string)
}
variable "supabase_url" {
type = string
description = "Supabase URL for the application"
sensitive = true
}
variable "supabase_key" {
type = string
description = "Supabase API key"
sensitive = true
}
33 changes: 33 additions & 0 deletions infra/app/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
provider "aws" {
region = var.region
default_tags {
tags = {
app = var.app_name
}
}
}

module "network" {
source = "./network"
app_name = var.app_name
region = var.region
}

module "ecs" {
source = "./ecs"
app_name = var.app_name
region = var.region
image = var.image
supabase_url = var.supabase_url
supabase_key = var.supabase_key
vpc_id = module.network.vpc.id
public_subnet_ids = [for s in module.network.public_subnets : s.id]
private_subnet_ids = [for s in module.network.private_subnets : s.id]
depends_on = [module.network]
}


# Outputs
output "alb_dns_name" {
value = module.ecs.alb_dns_name
}
75 changes: 75 additions & 0 deletions infra/app/network/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Define provider
provider "aws" {
region = var.region
default_tags {
tags = {
app = var.app_name
}
}
}

# Create VPC and IGW
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr_block
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
}

# Create public subnets
resource "aws_subnet" "public_subnets" {
count = length(var.availability_zones)
vpc_id = aws_vpc.this.id
cidr_block = var.public_cidr_blocks[count.index]
availability_zone = var.availability_zones[count.index]
}

# Create routing tables for public subnets
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
}
resource "aws_route_table_association" "publics" {
count = length(var.availability_zones)
subnet_id = element(aws_subnet.public_subnets.*.id, count.index)
route_table_id = aws_route_table.public.id
}


# Create Elastic IPs and NAT Gateways
resource "aws_eip" "eips" {
count = length(var.availability_zones)
domain = "vpc"
}
resource "aws_nat_gateway" "this" {
count = length(var.availability_zones)
subnet_id = element(aws_subnet.public_subnets.*.id, count.index)
allocation_id = element(aws_eip.eips.*.id, count.index)
}

# Create private subnets
resource "aws_subnet" "private_subnets" {
count = length(var.availability_zones)
vpc_id = aws_vpc.this.id
cidr_block = var.private_cidr_blocks[count.index]
availability_zone = var.availability_zones[count.index]
}

# Create routing tables for private subnets
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = element(aws_nat_gateway.this.*.id, count.index)
}
}
resource "aws_route_table_association" "privates" {
count = length(var.availability_zones)
subnet_id = element(aws_subnet.private_subnets.*.id, count.index)
route_table_id = element(aws_route_table.private.*.id, count.index)
}

9 changes: 9 additions & 0 deletions infra/app/network/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "vpc" {
value = aws_vpc.this
}
output "public_subnets" {
value = aws_subnet.public_subnets
}
output "private_subnets" {
value = aws_subnet.private_subnets
}
22 changes: 22 additions & 0 deletions infra/app/network/variable.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
variable "app_name" {
type = string
}
variable "region" {
type = string
}
variable "vpc_cidr_block" {
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1f"]
}
variable "public_cidr_blocks" {
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "private_cidr_blocks" {
type = list(string)
default = ["10.0.11.0/24", "10.0.12.0/24"]
}
Loading

0 comments on commit 2651609

Please sign in to comment.