Skip to content

Commit a3de49a

Browse files
committed
Initial commit
1 parent bedc3b7 commit a3de49a

10 files changed

+311
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
# terraform-aws-ecs-service
1+
# terraform-aws-ecs-service
2+
3+
This is a highly-opinionated ECS Service module for the Synapse Platform. It currently does NOT support blue-green deploys, autoscaling, customizing container sizes, or sidecar containers. It is also overly restrictive with the task role permissions.

alb_target_group.tf

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
resource "aws_lb_target_group" "this" {
2+
name_prefix = substr(var.service_name, 0, 6)
3+
port = 80
4+
deregistration_delay = 5
5+
protocol = "HTTP"
6+
target_type = "ip"
7+
vpc_id = var.vpc_id
8+
# tags = var.tags # TODO
9+
10+
health_check {
11+
enabled = true
12+
interval = 5
13+
path = var.health_check_path
14+
port = var.container_port
15+
protocol = "HTTP"
16+
timeout = 3
17+
healthy_threshold = 2
18+
unhealthy_threshold = 3
19+
matcher = "200"
20+
}
21+
22+
stickiness {
23+
enabled = true
24+
type = "lb_cookie"
25+
cookie_duration = 86400
26+
}
27+
}
28+
29+
resource "aws_lb_listener_rule" "this" {
30+
listener_arn = var.listener_arn
31+
32+
action {
33+
type = "forward"
34+
target_group_arn = aws_lb_target_group.this.arn
35+
}
36+
37+
condition {
38+
host_header {
39+
values = [var.hostname]
40+
}
41+
}
42+
}

db.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module "database" {
2+
count = var.use_database_cluster ? 1 : 0
3+
source = "git::https://github.com/synapsestudios/terraform-aws-rds-aurora-cluster.git?ref=a8f4ea6a6a5886b2bfeb43d1403a7abfe8e3f0e2"
4+
availability_zones = var.azs
5+
database_subnets = var.subnets
6+
db_cluster_parameter_group_name = "default.aurora-postgresql14"
7+
instance_class = "db.t4g.medium"
8+
name = "backend"
9+
vpc_id = var.vpc_id
10+
database_name = "backend"
11+
}

ecs_service.tf

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
resource "aws_ecs_service" "this" {
2+
name = var.service_name
3+
launch_type = "FARGATE"
4+
cluster = var.cluster_arn
5+
task_definition = aws_ecs_task_definition.service.arn
6+
desired_count = var.ecs_desired_count
7+
8+
load_balancer {
9+
target_group_arn = aws_lb_target_group.this.arn
10+
container_name = var.service_name
11+
container_port = var.container_port
12+
}
13+
14+
network_configuration {
15+
subnets = var.subnets
16+
security_groups = [aws_security_group.ecs_task.id]
17+
# If you are using Fargate tasks, in order for the task to pull the container image it must either use a public subnet and be assigned a
18+
# public IP address or a private subnet that has a route to the internet or a NAT gateway that can route requests to the internet.
19+
assign_public_ip = false
20+
}
21+
22+
# This allows dynamic scaling and external deployments
23+
lifecycle {
24+
ignore_changes = [
25+
desired_count,
26+
task_definition,
27+
load_balancer
28+
]
29+
}
30+
31+
deployment_controller {
32+
type = "ECS"
33+
}
34+
}

ecs_task_definitions.tf

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
resource "aws_ecs_task_definition" "service" {
3+
family = var.service_name
4+
container_definitions = "[${module.service_container_definition.json_map_encoded}]"
5+
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
6+
task_role_arn = aws_iam_role.ecs_task_role.arn
7+
network_mode = "awsvpc"
8+
memory = 2048
9+
cpu = 1024
10+
requires_compatibilities = ["FARGATE"]
11+
# tags = var.tags # TODO
12+
}
13+
14+
resource "aws_cloudwatch_log_group" "service" {
15+
name_prefix = var.service_name
16+
# tags = var.tags # TODO
17+
}
18+
19+
module "service_container_definition" {
20+
source = "cloudposse/ecs-container-definition/aws"
21+
version = "0.58.1"
22+
23+
container_name = var.service_name
24+
container_image = "${var.ecr_host}/${var.service_name}:60"
25+
container_memory = 2048
26+
essential = true
27+
environment = var.environment_variables
28+
port_mappings = [{ hostPort = var.container_port, containerPort = var.container_port, protocol = "tcp" }]
29+
command = var.command
30+
secrets = var.use_database_cluster ? concat([{
31+
name = "DATABASE_URL"
32+
valueFrom = module.database[0].connection_string_arn
33+
}], var.container_secrets) : var.container_secrets
34+
35+
log_configuration = {
36+
logDriver = "awslogs"
37+
secretOptions = null,
38+
options = {
39+
awslogs-group = aws_cloudwatch_log_group.service.name
40+
awslogs-region = data.aws_region.current.name
41+
awslogs-stream-prefix = "ecs"
42+
}
43+
}
44+
}

ecs_task_execution_role.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
resource "aws_iam_role" "ecs_task_execution_role" {
2+
name_prefix = "${substr(var.service_name, 0, 14)}-ecs-task-execution-role"
3+
4+
assume_role_policy = <<EOF
5+
{
6+
"Version": "2012-10-17",
7+
"Statement": [
8+
{
9+
"Action": "sts:AssumeRole",
10+
"Principal": {
11+
"Service": "ecs-tasks.amazonaws.com"
12+
},
13+
"Effect": "Allow",
14+
"Sid": ""
15+
}
16+
]
17+
}
18+
EOF
19+
}
20+
21+
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role" {
22+
role = aws_iam_role.ecs_task_execution_role.name
23+
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
24+
}
25+
26+
resource "aws_iam_role_policy_attachment" "secrets_manager" {
27+
role = aws_iam_role.ecs_task_execution_role.name
28+
policy_arn = "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
29+
}

ecs_task_role.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
resource "aws_iam_role" "ecs_task_role" {
2+
name_prefix = "${substr(var.service_name, 0, 24)}-ecs-task-role"
3+
assume_role_policy = data.aws_iam_policy_document.assume_ecs_role_policy.json
4+
}
5+
6+
data "aws_iam_policy_document" "assume_ecs_role_policy" {
7+
statement {
8+
actions = ["sts:AssumeRole"]
9+
principals {
10+
type = "Service"
11+
identifiers = ["ecs-tasks.amazonaws.com"]
12+
}
13+
}
14+
}
15+
16+
resource "aws_iam_role_policy_attachment" "cognito" {
17+
role = aws_iam_role.ecs_task_role.name
18+
policy_arn = "arn:aws:iam::aws:policy/AmazonCognitoPowerUser"
19+
}
20+
21+
resource "aws_iam_role_policy_attachment" "ses" {
22+
role = aws_iam_role.ecs_task_role.name
23+
policy_arn = "arn:aws:iam::aws:policy/AmazonSESFullAccess"
24+
}
25+
26+
resource "aws_iam_role_policy_attachment" "s3" {
27+
role = aws_iam_role.ecs_task_role.name
28+
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
29+
}

ecs_task_security_group.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
resource "aws_security_group" "ecs_task" {
2+
description = "ECS Tasks traffic rules"
3+
vpc_id = var.vpc_id
4+
name_prefix = "${var.service_name}-ecs-task-default"
5+
# tags = merge(var.tags, { Name = "ECSTasks" }) # TODO
6+
7+
egress {
8+
from_port = 0
9+
to_port = 0
10+
protocol = "-1"
11+
cidr_blocks = ["0.0.0.0/0"]
12+
description = "Allow outgoing connections"
13+
}
14+
}
15+
16+
resource "aws_security_group_rule" "ecs_task_alb_access" {
17+
type = "ingress"
18+
from_port = 0
19+
to_port = 0
20+
protocol = "-1"
21+
source_security_group_id = var.alb_security_group_id
22+
description = "Allow incoming connections from ALB"
23+
security_group_id = aws_security_group.ecs_task.id
24+
}

main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
data "aws_caller_identity" "current" {}
2+
data "aws_region" "current" {}

variables.tf

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
variable "vpc_id" {
2+
type = string
3+
description = "VPC to deploy into"
4+
}
5+
6+
variable "cluster_arn" {
7+
type = string
8+
description = "ECS cluster to deploy into"
9+
}
10+
11+
variable "subnets" {
12+
type = list(string)
13+
description = "List of subnet names the service will reside on."
14+
}
15+
16+
variable "ecr_host" {
17+
type = string
18+
description = "Hostname of the ECR repository with no trailing slash"
19+
}
20+
21+
variable "azs" {
22+
type = list(string)
23+
description = "Availability zones"
24+
}
25+
26+
variable "environment_variables" {
27+
type = list(object({
28+
name = string
29+
value = string
30+
}))
31+
description = "The environment variables to pass to the container. This is a list of maps."
32+
default = []
33+
}
34+
35+
variable "container_secrets" {
36+
type = list(object({
37+
name = string
38+
valueFrom = string
39+
}))
40+
description = "The Secrets to Pass to the container."
41+
default = []
42+
}
43+
44+
variable "listener_arn" {
45+
type = string
46+
description = "ALB listener ARN to add listener rule to"
47+
}
48+
49+
variable "alb_security_group_id" {
50+
type = string
51+
description = "Security Group ID for the ALB"
52+
}
53+
54+
variable "command" {
55+
type = list(string)
56+
description = "Container startup command"
57+
}
58+
59+
variable "hostname" {
60+
type = string
61+
description = "Hostname to use for listener rule"
62+
}
63+
64+
variable "service_name" {
65+
type = string
66+
description = "Service directory in the application git repo"
67+
}
68+
69+
variable "container_port" {
70+
type = number
71+
description = "Port exposed by the container"
72+
}
73+
74+
variable "health_check_path" {
75+
type = string
76+
description = "Path to use for health checks"
77+
}
78+
79+
variable "use_database_cluster" {
80+
type = bool
81+
description = "Whether or not we should create a DB cluster and inject the database connection string into the container"
82+
}
83+
84+
variable "use_hostname" {
85+
type = bool
86+
description = "Whether or not we should create a target group and listener to attach this service to a load balancer"
87+
}
88+
89+
variable "ecs_desired_count" {
90+
type = number
91+
default = 1
92+
description = "How many tasks to launch in ECS service"
93+
}

0 commit comments

Comments
 (0)