Last active
April 30, 2023 09:08
-
-
Save gunzip/29750547cbf5d83c8911fce9473ad3e9 to your computer and use it in GitHub Desktop.
terraform sample
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Create the whole infrastructure to run the Strapi CMS webserver | |
# and the static frontend that uses nextjs. | |
# Terraform provider configuration | |
variable "db_name" { | |
description = "The name of the database" | |
default = "my_database" | |
} | |
variable "db_username" { | |
description = "The username for the database" | |
default = "my_username" | |
} | |
resource "random_password" "db_password" { | |
length = 16 | |
special = true | |
} | |
variable "db_password" { | |
description = "The password for the database" | |
default = random_password.db_password.result | |
} | |
#################################### | |
resource "aws_vpc" "vpc" { | |
cidr_block = "10.0.0.0/16" | |
tags = { | |
Name = "vpc" | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_subnet" "strapi_subnet" { | |
vpc_id = aws_vpc.vpc.id | |
cidr_block = "10.0.1.0/24" | |
tags = { | |
Name = "strapi-subnet" | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_db_subnet_group" "strapi_subnet_group" { | |
name = "strapi-subnet-group" | |
subnet_ids = [aws_subnet.strapi_subnet.id] | |
description = "Strapi subnet group" | |
tags = { | |
ResourceGroup = "devportal" | |
} | |
} | |
############ | |
# Define a CloudWatch Logs log group for the ECS task | |
resource "aws_cloudwatch_log_group" "strapi_logs" { | |
name = "/ecs/strapi-logs" | |
retention_in_days = 7 | |
} | |
resource "aws_ecs_cluster" "strapi_cluster" { | |
name = "strapi-cluster" | |
} | |
# Create an IAM policy to allow ECS tasks to write logs to CloudWatch | |
resource "aws_iam_policy" "ecs_task_logging_policy" { | |
name = "ecs-task-logging-policy" | |
policy = jsonencode({ | |
Version = "2012-10-17" | |
Statement = [ | |
{ | |
Effect = "Allow" | |
Action = [ | |
"logs:CreateLogStream", | |
"logs:PutLogEvents" | |
] | |
Resource = "arn:aws:logs:*:*:*" | |
} | |
] | |
}) | |
} | |
# Create an IAM role for ECS task execution | |
resource "aws_iam_role" "ecs_task_execution_role" { | |
name = "ecs-task-execution-role" | |
assume_role_policy = jsonencode({ | |
Version = "2012-10-17" | |
Statement = [ | |
{ | |
Effect = "Allow" | |
Principal = { | |
Service = "ecs-tasks.amazonaws.com" | |
} | |
Action = "sts:AssumeRole" | |
} | |
] | |
}) | |
# Attach the task execution role to the logging policy | |
policy = aws_iam_policy.ecs_task_logging_policy.arn | |
} | |
# This allows traffic from the ALB to the ECS service | |
# and from the ECS service to the database | |
resource "aws_security_group" "ecs_security_group" { | |
name_prefix = "ecs-" | |
vpc_id = aws_vpc.vpc.id | |
ingress { | |
from_port = 0 | |
to_port = 65535 | |
protocol = "tcp" | |
cidr_blocks = [ | |
aws_vpc.vpc.cidr_block, | |
] | |
} | |
tags = { | |
Environment = "production" | |
ResourceGroup = "devportal" | |
} | |
} | |
# This defines the container that will run in the ECS service | |
resource "aws_ecs_task_definition" "strapi_task" { | |
family = "strapi-task" | |
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn | |
network_mode = "awsvpc" | |
requires_compatibilities = ["FARGATE"] | |
container_definitions = jsonencode([ | |
{ | |
name = "strapi-container" | |
image = "docker.pkg.github.com/OWNER/REPO/IMAGE:TAG" | |
portMappings = [ | |
{ | |
containerPort = 1337 | |
protocol = "tcp" | |
} | |
] | |
environment = [ | |
{ | |
name = "DATABASE_URL" | |
value = format("postgres://%s:%s@%s/%s", | |
aws_db_instance.strapi_db.username, | |
aws_db_instance.strapi_db.password, | |
aws_db_instance.strapi_db.endpoint, | |
aws_db_instance.strapi_db.db_name) | |
} | |
] | |
logConfiguration = { | |
logDriver = "awslogs" | |
options = { | |
awslogs-group = aws_cloudwatch_log_group.strapi_logs.name | |
awslogs-region = "eu-west-1" | |
awslogs-stream-prefix = "ecs" | |
} | |
} | |
} | |
]) | |
} | |
resource "aws_ecs_service" "strapi_service" { | |
name = "strapi-service" | |
cluster = aws_ecs_cluster.strapi_cluster.id | |
task_definition = aws_ecs_task_definition.strapi_task.arn | |
launch_type = "FARGATE" | |
desired_count = 1 | |
network_configuration { | |
assign_public_ip = true | |
security_groups = [aws_security_group.ecs_security_group.id] | |
subnets = [ | |
aws_subnet.strapi_subnet.id | |
] | |
} | |
load_balancer { | |
target_group_arn = aws_lb_target_group.strapi_target_group.arn | |
container_name = "strapi-container" | |
container_port = 1337 | |
} | |
} | |
resource "aws_lb_target_group" "strapi_target_group" { | |
name_prefix = "strapi-target-group" | |
port = 1337 | |
protocol = "HTTP" | |
target_type = "ip" | |
vpc_id = aws_vpc.vpc.id | |
health_check { | |
interval = 30 | |
path = "/" | |
port = "traffic-port" | |
protocol = "HTTP" | |
timeout = 10 | |
healthy_threshold = 3 | |
unhealthy_threshold = 3 | |
matcher = "200" | |
} | |
tags = { | |
Environment = "production" | |
ResourceGroup = "devportal" | |
} | |
} | |
############## | |
resource "aws_security_group" "rds_security_group" { | |
name_prefix = "rds-" | |
vpc_id = aws_vpc.vpc.id | |
ingress { | |
from_port = 5432 | |
to_port = 5432 | |
protocol = "tcp" | |
security_groups = [ | |
aws_security_group.ecs_security_group.id | |
] | |
} | |
tags = { | |
Environment = "production" | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_db_instance" "strapi_db" { | |
allocated_storage = 10 # GB | |
engine = "postgres" | |
engine_version = "13.2" | |
instance_class = "db.t2.micro" | |
identifier = "strapi-db" | |
name = var.db_name | |
username = var.db_username | |
password = var.db_password | |
skip_final_snapshot = false | |
db_subnet_group_name = aws_db_subnet_group.strapi_subnet_group.name | |
vpc_security_group_ids = [aws_security_group.rds_security_group.id] | |
backup_retention_period = 7 # retention period in days | |
preferred_backup_window = "01:00-02:00" # backup window in UTC time | |
backup_window = "02:00-03:00" # maintenance window in UTC time | |
tags = { | |
Name = "strapi-db" | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_s3_bucket" "static_files_bucket" { | |
bucket = "static-files-bucket" | |
acl = "public-read" | |
tags = { | |
Name = "static-files-bucket" | |
Environment = "production" | |
ResourceGroup = "devportal" | |
} | |
} | |
# CDN | |
resource "aws_cloudfront_distribution" "cdn" { | |
origin { | |
domain_name = aws_s3_bucket.static_files_bucket.bucket_regional_domain_name | |
origin_id = "frontend-static-files-bucket" | |
} | |
default_cache_behavior { | |
allowed_methods = ["GET", "HEAD", "OPTIONS"] | |
cached_methods = ["GET", "HEAD", "OPTIONS"] | |
target_origin_id = "S3-my-static-files-bucket" | |
forwarded_values { | |
query_string = false | |
cookies { | |
forward = "none" | |
} | |
} | |
viewer_protocol_policy = "redirect-to-https" | |
min_ttl = 0 | |
default_ttl = 3600 | |
max_ttl = 86400 | |
} | |
enabled = true | |
is_ipv6_enabled = true | |
price_class = "PriceClass_100" | |
default_root_object = "index.html" | |
tags = { | |
Name = "cloudfront-distribution" | |
Environment = "production" | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_cloudwatch_dashboard" "dashboard" { | |
dashboard_name = "my-dashboard" | |
dashboard_body = jsonencode({ | |
"widgets" : [ | |
{ | |
"type" : "metric", | |
"x" : 0, | |
"y" : 0, | |
"width" : 6, | |
"height" : 6, | |
"properties" : { | |
"metrics" : [ | |
[ | |
"AWS/EC2", | |
"CPUUtilization", | |
"InstanceId", | |
"${aws_ecs_task_definition.strapi_task.family}:*" | |
] | |
], | |
"period" : 300, | |
"stat" : "Average", | |
"region" : "eu-west-1", | |
"title" : "CPU Utilization" | |
} | |
}, | |
{ | |
"type" : "metric", | |
"x" : 0, | |
"y" : 6, | |
"width" : 6, | |
"height" : 6, | |
"properties" : { | |
"metrics" : [ | |
[ | |
"AWS/ECS", | |
"CPUUtilization", | |
"ClusterName", | |
"${aws_ecs_cluster.strapi_cluster.name}", | |
"ServiceName", | |
"${aws_ecs_service.strapi_service.name}" | |
] | |
], | |
"period" : 300, | |
"stat" : "Average", | |
"region" : "eu-west-1", | |
"title" : "ECS CPU Utilization" | |
} | |
}, | |
{ | |
"type" : "metric", | |
"x" : 6, | |
"y" : 0, | |
"width" : 6, | |
"height" : 6, | |
"properties" : { | |
"metrics" : [ | |
[ | |
"AWS/EC2", | |
"NetworkIn", | |
"InstanceId", | |
"${aws_ecs_task_definition.strapi_task.family}:*" | |
], | |
[ | |
"AWS/EC2", | |
"NetworkOut", | |
"InstanceId", | |
"${aws_ecs_task_definition.strapi_task.family}:*" | |
] | |
], | |
"period" : 300, | |
"stat" : "Sum", | |
"region" : "eu-west-1", | |
"title" : "Network Traffic" | |
} | |
}, | |
{ | |
"type" : "metric", | |
"x" : 6, | |
"y" : 6, | |
"width" : 6, | |
"height" : 6, | |
"properties" : { | |
"metrics" : [ | |
[ | |
"AWS/ECS", | |
"MemoryUtilization", | |
"ClusterName", | |
"${aws_ecs_cluster.strapi_cluster.name}", | |
"ServiceName", | |
"${aws_ecs_service.strapi_service.name}" | |
] | |
], | |
"period" : 300, | |
"stat" : "Average", | |
"region" : "eu-west-1", | |
"title" : "ECS Memory Utilization" | |
} | |
} | |
] | |
}) | |
tags = { | |
ResourceGroup = "devportal" | |
} | |
} | |
# Route53 | |
resource "aws_route53_zone" "dns_zone" { | |
name = "example.com" | |
tags = { | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_route53_record" "domain_record" { | |
zone_id = aws_route53_zone.domain.zone_id | |
name = "strapi.example.com" | |
type = "A" | |
alias { | |
name = aws_lb.alb.dns_name | |
zone_id = aws_lb.alb.zone_id | |
evaluate_target_health = false | |
} | |
tags = { | |
ResourceGroup = "devportal" | |
} | |
} | |
resource "aws_acm_certificate" "cert" { | |
domain_name = "strapi.example.com" | |
validation_method = "DNS" | |
tags = { | |
Terraform = "true" | |
} | |
lifecycle { | |
create_before_destroy = true | |
} | |
} | |
resource "aws_acm_certificate_validation" "cert_validation" { | |
certificate_arn = aws_acm_certificate.cert.arn | |
validation_record_fqdns = aws_route53_record.cert_validation.fqdn | |
} | |
resource "aws_route53_record" "cert_validation" { | |
for_each = { | |
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { | |
name = dvo.resource_record_name | |
record = dvo.resource_record_value | |
type = dvo.resource_record_type | |
} | |
} | |
allow_overwrite = true | |
name = each.value.name | |
records = [each.value.record] | |
ttl = 60 | |
type = each.value.type | |
zone_id = data.aws_route53_zone.dns_zone.zone_id | |
} | |
resource "aws_security_group" "alb" { | |
name = "alb_security_group" | |
description = "Allow inbound traffic for ALB" | |
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" "alb" { | |
name = "strapi-alb" | |
internal = false | |
load_balancer_type = "application" | |
security_groups = [aws_security_group.alb.id] | |
# security_groups = [aws_security_group.ecs_security_group.id] | |
subnets = [ | |
aws_subnet.strapi_subnet.id | |
] | |
} | |
resource "aws_lb_listener" "https" { | |
load_balancer_arn = aws_lb.alb.arn | |
port = 443 | |
protocol = "HTTPS" | |
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01" | |
certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn | |
default_action { | |
type = "forward" | |
target_group_arn = aws_lb_target_group.strapi_target_group.arn | |
} | |
} | |
resource "aws_route53_record" "strapi" { | |
name = "strapi.example.com" | |
type = "A" | |
zone_id = aws_route53_zone.dns_zone.zone_id | |
alias { | |
name = aws_lb.alb.dns_name | |
zone_id = aws_lb.alb.zone_id | |
evaluate_target_health = false | |
} | |
} | |
#################### | |
# output "strapi_public_ip" { | |
# value = aws_eip.strapi_eip.public_ip | |
# } | |
# output "strapi_dashboard_url" { | |
# value = "http://strapi.example.com:1337/admin" | |
# } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment