Skip to content

Instantly share code, notes, and snippets.

@gunzip
Last active April 30, 2023 09:08
Show Gist options
  • Save gunzip/29750547cbf5d83c8911fce9473ad3e9 to your computer and use it in GitHub Desktop.
Save gunzip/29750547cbf5d83c8911fce9473ad3e9 to your computer and use it in GitHub Desktop.
terraform sample
# 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