Skip to content

Commit

Permalink
ECS infrastructure with the Flagsmith task definition (#4)
Browse files Browse the repository at this point in the history
* ecs infra with flagsmith task

* readme cosmetic change

* run terraform fmt

* general clean up, tightened IAM permissions, ALB listener & SG fix, Postgress 14

* set Fargate defaults, Django application settings
  • Loading branch information
deltacodepl authored Jun 28, 2023
1 parent 1063b38 commit c38d6f9
Show file tree
Hide file tree
Showing 21 changed files with 716 additions and 0 deletions.
64 changes: 64 additions & 0 deletions flagsmith-on-ecs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Flagsmith AWS Starter kit

## Deploying Flagsmith to AWS ECS (with whitenoise)

### This is an example, how Flagsmith can be hosted,
### This is not production ready solution

## AWS infrastructure consists of:
- IAM
- Route53 subdomain
- Networking:
- VPC
- Public and private subnets
- Routing tables
- Internet Gateway
- Security Groups
- Load Balancers, Listeners, and Target Groups
- IAM Roles and Policies
- ECS:
- Task Definition with flagsmith:latest image
- Cluster
- Service
- RDS
- Secrets with Parameter Store
- Logs

## How to use this project

1. Register AWS Route53 Hosted Zone \
for example ```yourdomain.com```
![Route53 hosted zone](img/route53.png)
2. Generate certificate with AWS Certificate Manager \
with ```*.yourdomain.com``` pattern
![Certificate](img/AWS_certificate_manager.png)
3. Define your variables like hosted zone domain and more desired settings in **terraform.tfvars** file
```bash
route53_hosted_zone = "yourdomain.com"
app_name = "flagsmith"
app_environment = "dev"
region = "eu-central-1"
allowed_hosts = "*"
```

Currently containers are run on Fargate SPOT instances:
ecs_service.tf:
```bash
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 100
}
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 0
}
```
4. Generate plan for infrastructure ```terraform plan -out flagsmith.tfplan```
5. Apply infrastructure by running ```terraform apply flagsmith.tfplan```

After a few minutes flagsmith will be available at your domain

![Flagsmith online](img/flagsmith.png)


you can delete a whole setup by running ```terraform destroy --auto-approve```
Binary file added flagsmith-on-ecs/img/AWS_certificate_manager.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flagsmith-on-ecs/img/flagsmith.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flagsmith-on-ecs/img/route53.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions flagsmith-on-ecs/terraform/01_provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
terraform {
required_version = ">=1.3.6"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~>4.63.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.5.1"
}
}
}

provider "aws" {
region = var.region

default_tags {
tags = {
Environment = var.app_environment
Project = var.app_name
}
}
}
44 changes: 44 additions & 0 deletions flagsmith-on-ecs/terraform/02_vpc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# POC VPC

data "aws_availability_zones" "available" {
state = "available"
}

locals {
azs = slice(data.aws_availability_zones.available.names, 0, 2)

}

module "vpc" {
# https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
source = "terraform-aws-modules/vpc/aws"
version = "3.18.1"

name = "${var.app_name}-${var.app_environment}-vpc"
cidr = var.vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 10)]
public_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 1)]

enable_nat_gateway = true
single_nat_gateway = true
one_nat_gateway_per_az = false
enable_dns_support = true
enable_dns_hostnames = true


# for auto service discovery
# tags = {

# }

# public_subnet_tags = {

# }

# private_subnet_tags = {

# }
}


70 changes: 70 additions & 0 deletions flagsmith-on-ecs/terraform/03_securitygroups.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ALB Security Group (Traffic Internet -> ALB)
resource "aws_security_group" "load-balancer" {
name = "load_balancer_security_group"
description = "Controls access to the ALB"
vpc_id = module.vpc.vpc_id

# used for redirection to https
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"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

# ECS Security group (traffic ALB -> ECS)
resource "aws_security_group" "ecs" {
name = "ecs_security_group"
description = "Allows inbound access from the ALB only"
vpc_id = module.vpc.vpc_id

ingress {
from_port = 8000
to_port = 8000
protocol = "tcp"
security_groups = [aws_security_group.load-balancer.id]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

# RDS Security Group (traffic ECS -> RDS)
resource "aws_security_group" "rds" {
name = "rds-security-group"
description = "Allows inbound access from ECS only"
vpc_id = module.vpc.vpc_id

ingress {
protocol = "tcp"
from_port = "5432"
to_port = "5432"
security_groups = [aws_security_group.ecs.id]
}

egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
86 changes: 86 additions & 0 deletions flagsmith-on-ecs/terraform/04_loadbalancer.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
data "aws_acm_certificate" "certificate" {
domain = "*.${data.aws_route53_zone.selected.name}"
statuses = ["ISSUED"]
}

# Load Balancer
resource "aws_lb" "ecs-alb" {
name = "${local.ecs_cluster_name}-alb"
load_balancer_type = "application"
internal = false
security_groups = [aws_security_group.load-balancer.id]
subnets = module.vpc.public_subnets
}

# Target group
resource "aws_alb_target_group" "default-target-group" {
name = "${var.app_environment}-${var.app_name}-tg"
port = 8000
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
target_type = "ip"

health_check {
path = var.health_check_path
port = "traffic-port"
healthy_threshold = 5
unhealthy_threshold = 2
timeout = 2
interval = 5
matcher = "200"
}
}

# Listener, redirects HTTP to HTTPS
resource "aws_alb_listener" "ecs-alb-http-listener" {
load_balancer_arn = aws_lb.ecs-alb.id
port = "80"
protocol = "HTTP"

default_action {
type = "redirect"

redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}

# Listener (redirects traffic from the load balancer to the target group)
# Check for latest ssl policy https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies
resource "aws_alb_listener" "ecs-alb-https-listener" {
load_balancer_arn = aws_lb.ecs-alb.id
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = data.aws_acm_certificate.certificate.arn
depends_on = [aws_alb_target_group.default-target-group]

default_action {
type = "fixed-response"

fixed_response {
content_type = "text/plain"
message_body = "Bad Request"
status_code = "400"
}
}

}
# matches header with configured flagsmith subdomain
resource "aws_alb_listener_rule" "host_header" {
listener_arn = aws_alb_listener.ecs-alb-https-listener.arn
priority = 10

condition {
host_header {
values = ["${var.app_name}.${data.aws_route53_zone.selected.name}"]
}
}
action {
type = "forward"
target_group_arn = aws_alb_target_group.default-target-group.arn
}
}
53 changes: 53 additions & 0 deletions flagsmith-on-ecs/terraform/05_iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
resource "aws_iam_role" "ecs_host_role" {
name = "${var.app_name}-ecs-host-role-${var.app_environment}"
assume_role_policy = file("policies/ecs-task-role.json")
}

resource "aws_iam_role_policy" "ecs-host-role-policy" {
name = "${var.app_name}-ecs-host-role-policy"

policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"cloudwatch:PutMetricData",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
"Resource" : "*"
},
{
Effect = "Allow"
Action = ["ssm:GetParameters"],
Resource = ["arn:aws:ssm:${var.region}:${local.AWS_ACCOUNT_ID}:parameter/${var.app_name}/*"]
}
]
}
)
role = aws_iam_role.ecs_host_role.id
}

resource "aws_iam_role" "ecs_task" {
name = "${var.app_name}-ecs-task"
assume_role_policy = file("policies/ecs-task-role.json")
}

resource "aws_iam_role_policy" "ecs_task" {
name = "${var.app_name}-ecs-task-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["ssm:GetParameters"],
Resource = ["arn:aws:ssm:${var.region}:${local.AWS_ACCOUNT_ID}:parameter/${var.app_name}/*"]
}
]
})
role = aws_iam_role.ecs_task.id
}
9 changes: 9 additions & 0 deletions flagsmith-on-ecs/terraform/06_logs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "aws_cloudwatch_log_group" "django-log-group" {
name = "/ecs/flagsmith"
retention_in_days = var.log_retention_in_days
}

resource "aws_cloudwatch_log_stream" "django-log-stream" {
name = "flagsmith-log-stream"
log_group_name = aws_cloudwatch_log_group.django-log-group.name
}
17 changes: 17 additions & 0 deletions flagsmith-on-ecs/terraform/07_route53.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
data "aws_route53_zone" "selected" {
name = var.route53_hosted_zone
private_zone = false
}

resource "aws_route53_record" "subdomain" {
zone_id = data.aws_route53_zone.selected.zone_id
name = "${var.app_name}.${data.aws_route53_zone.selected.name}"
type = "A"

alias {
name = aws_lb.ecs-alb.dns_name
zone_id = aws_lb.ecs-alb.zone_id
evaluate_target_health = true
}

}
3 changes: 3 additions & 0 deletions flagsmith-on-ecs/terraform/08_ecs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_ecs_cluster" "flagsmith" {
name = local.ecs_cluster_name
}
Loading

0 comments on commit c38d6f9

Please sign in to comment.