Skip to content

Commit daab3ce

Browse files
committed
Add initial configuration for ecs fargate module
1 parent 74d1679 commit daab3ce

File tree

8 files changed

+617
-2
lines changed

8 files changed

+617
-2
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
*.DS_Store
2+
errored.tfstate
3+
.terraform
4+
crash.log
5+
terraform.tfstate
6+
*.tfstate*
7+
terraform.tfvars

README.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,60 @@
1-
# terraform-ecs-fargate
2-
Terraform module to create ECS FARGATE services
1+
# terraform-aws-ecs-fargate
2+
Terraform module to create AWS ECS FARGATE services
3+
4+
## Terraform versions
5+
6+
Terraform 0.12. Pin module version to `~> v1.0`. Submit pull-requests to `master` branch.
7+
8+
## Usage
9+
10+
### Application Load Balancer
11+
12+
```hcl
13+
resource "aws_ecs_cluster" "cluster" {
14+
name = "example-ecs-cluster"
15+
}
16+
17+
module "ecs-farage" {
18+
source = "umotif-public/ecs-fargate/aws"
19+
version = "~> 1.0"
20+
21+
name_prefix = "ecs-fargate-example"
22+
vpc_id = "vpc-abasdasd132"
23+
private_subnet_ids = ["subnet-abasdasd132123", "subnet-abasdasd132123132"]
24+
lb_arn = "arn:aws:asdasdasdasdasdasad"
25+
26+
cluster_id = aws_ecs_cluster.cluster.id
27+
28+
task_container_image = "marcincuber/2048-game:latest"
29+
task_definition_cpu = 256
30+
task_definition_memory = 512
31+
32+
task_container_port = 80
33+
task_container_assign_public_ip = true
34+
35+
health_check = {
36+
port = "traffic-port"
37+
path = "/"
38+
}
39+
40+
tags = {
41+
Project = "Test"
42+
}
43+
}
44+
```
45+
46+
## Assumptions
47+
48+
Module is to be used with Terraform > 0.12.
49+
50+
## Examples
51+
52+
* [ECS Fargate](https://github.com/umotif-public/terraform-aws-ecs-fargate/tree/master/examples/core)
53+
54+
## Authors
55+
56+
Module managed by [Marcin Cuber](https://github.com/marcincuber) [linkedin](https://www.linkedin.com/in/marcincuber/).
57+
58+
## License
59+
60+
See LICENSE for full details.

data.tf

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

examples/core/main.tf

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
provider "aws" {
2+
region = "eu-west-1"
3+
}
4+
5+
#####
6+
# VPC and subnets
7+
#####
8+
module "vpc" {
9+
source = "terraform-aws-modules/vpc/aws"
10+
version = "~> 2.21"
11+
12+
name = "simple-vpc"
13+
14+
cidr = "10.0.0.0/16"
15+
16+
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
17+
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
18+
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
19+
20+
enable_nat_gateway = false
21+
}
22+
23+
#####
24+
# ALB
25+
#####
26+
module "alb" {
27+
source = "umotif-public/alb/aws"
28+
version = "~> 1.0"
29+
30+
name_prefix = "alb-example"
31+
load_balancer_type = "application"
32+
internal = false
33+
vpc_id = module.vpc.vpc_id
34+
subnets = module.vpc.public_subnets
35+
}
36+
37+
resource "aws_lb_listener" "alb_80" {
38+
load_balancer_arn = module.alb.arn
39+
port = "80"
40+
protocol = "HTTP"
41+
42+
default_action {
43+
type = "forward"
44+
target_group_arn = module.fargate.target_group_arn
45+
}
46+
}
47+
48+
#####
49+
# Security Group Config
50+
#####
51+
resource "aws_security_group_rule" "alb_ingress_80" {
52+
security_group_id = module.alb.security_group_id
53+
type = "ingress"
54+
protocol = "tcp"
55+
from_port = 80
56+
to_port = 80
57+
cidr_blocks = ["0.0.0.0/0"]
58+
ipv6_cidr_blocks = ["::/0"]
59+
}
60+
61+
resource "aws_security_group_rule" "task_ingress_80" {
62+
security_group_id = module.fargate.service_sg_id
63+
type = "ingress"
64+
protocol = "tcp"
65+
from_port = 80
66+
to_port = 80
67+
source_security_group_id = module.alb.security_group_id
68+
}
69+
70+
#####
71+
# ECS cluster and fargate
72+
#####
73+
resource "aws_ecs_cluster" "cluster" {
74+
name = "example-ecs-cluster"
75+
}
76+
77+
module "fargate" {
78+
source = "../../"
79+
80+
name_prefix = "ecs-fargate-example"
81+
vpc_id = module.vpc.vpc_id
82+
private_subnet_ids = module.vpc.public_subnets
83+
lb_arn = module.alb.arn
84+
cluster_id = aws_ecs_cluster.cluster.id
85+
86+
task_container_image = "marcincuber/2048-game:latest"
87+
task_definition_cpu = 256
88+
task_definition_memory = 512
89+
90+
task_container_port = 80
91+
task_container_assign_public_ip = true
92+
93+
health_check = {
94+
port = "traffic-port"
95+
path = "/"
96+
}
97+
}

main.tf

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#####
2+
# Cloudwatch
3+
#####
4+
resource "aws_cloudwatch_log_group" "main" {
5+
name = var.name_prefix
6+
retention_in_days = var.log_retention_in_days
7+
tags = var.tags
8+
}
9+
10+
#####
11+
# IAM - Task execution role, needed to pull ECR images etc.
12+
#####
13+
resource "aws_iam_role" "execution" {
14+
name = "${var.name_prefix}-task-execution-role"
15+
assume_role_policy = data.aws_iam_policy_document.task_assume.json
16+
}
17+
18+
resource "aws_iam_role_policy" "task_execution" {
19+
name = "${var.name_prefix}-task-execution"
20+
role = aws_iam_role.execution.id
21+
policy = data.aws_iam_policy_document.task_execution_permissions.json
22+
}
23+
24+
resource "aws_iam_role_policy" "read_repository_credentials" {
25+
count = length(var.repository_credentials) != 0 ? 1 : 0
26+
27+
name = "${var.name_prefix}-read-repository-credentials"
28+
role = aws_iam_role.execution.id
29+
policy = data.aws_iam_policy_document.read_repository_credentials.json
30+
}
31+
32+
#####
33+
# IAM - Task role, basic. Append policies to this role for S3, DynamoDB etc.
34+
#####
35+
resource "aws_iam_role" "task" {
36+
name = "${var.name_prefix}-task-role"
37+
assume_role_policy = data.aws_iam_policy_document.task_assume.json
38+
}
39+
40+
resource "aws_iam_role_policy" "log_agent" {
41+
name = "${var.name_prefix}-log-permissions"
42+
role = aws_iam_role.task.id
43+
policy = data.aws_iam_policy_document.task_permissions.json
44+
}
45+
46+
#####
47+
# Security groups
48+
#####
49+
resource "aws_security_group" "ecs_service" {
50+
vpc_id = var.vpc_id
51+
name = "${var.name_prefix}-ecs-service-sg"
52+
description = "Fargate service security group"
53+
tags = merge(
54+
var.tags,
55+
{
56+
Name = "${var.name_prefix}-sg"
57+
},
58+
)
59+
}
60+
61+
resource "aws_security_group_rule" "egress_service" {
62+
security_group_id = aws_security_group.ecs_service.id
63+
type = "egress"
64+
protocol = "-1"
65+
from_port = 0
66+
to_port = 0
67+
cidr_blocks = ["0.0.0.0/0"]
68+
ipv6_cidr_blocks = ["::/0"]
69+
}
70+
71+
#####
72+
# Load Balancer Target group
73+
#####
74+
resource "aws_lb_target_group" "task" {
75+
vpc_id = var.vpc_id
76+
protocol = var.task_container_protocol
77+
port = var.task_container_port
78+
target_type = "ip"
79+
dynamic "health_check" {
80+
for_each = [var.health_check]
81+
content {
82+
enabled = lookup(health_check.value, "enabled", null)
83+
healthy_threshold = lookup(health_check.value, "healthy_threshold", null)
84+
interval = lookup(health_check.value, "interval", null)
85+
matcher = lookup(health_check.value, "matcher", null)
86+
path = lookup(health_check.value, "path", null)
87+
port = lookup(health_check.value, "port", null)
88+
protocol = lookup(health_check.value, "protocol", null)
89+
timeout = lookup(health_check.value, "timeout", null)
90+
unhealthy_threshold = lookup(health_check.value, "unhealthy_threshold", null)
91+
}
92+
}
93+
94+
lifecycle {
95+
create_before_destroy = true
96+
}
97+
98+
tags = merge(
99+
var.tags,
100+
{
101+
Name = "${var.name_prefix}-target-${var.task_container_port}"
102+
},
103+
)
104+
}
105+
106+
#####
107+
# ECS Task/Service
108+
#####
109+
locals {
110+
task_environment = [
111+
for k, v in var.task_container_environment : {
112+
name = k
113+
value = v
114+
}
115+
]
116+
}
117+
118+
resource "aws_ecs_task_definition" "task" {
119+
family = var.name_prefix
120+
execution_role_arn = aws_iam_role.execution.arn
121+
network_mode = "awsvpc"
122+
requires_compatibilities = ["FARGATE"]
123+
cpu = var.task_definition_cpu
124+
memory = var.task_definition_memory
125+
task_role_arn = aws_iam_role.task.arn
126+
127+
container_definitions = <<EOF
128+
[{
129+
"name": "${var.container_name != "" ? var.container_name : var.name_prefix}",
130+
"image": "${var.task_container_image}",
131+
%{if var.repository_credentials != ""~}
132+
"repositoryCredentials": {
133+
"credentialsParameter": "${var.repository_credentials}"
134+
},
135+
%{~endif}
136+
"essential": true,
137+
"portMappings": [
138+
{
139+
"containerPort": ${var.task_container_port},
140+
"hostPort": ${var.task_container_port},
141+
"protocol":"tcp"
142+
}
143+
],
144+
"logConfiguration": {
145+
"logDriver": "awslogs",
146+
"options": {
147+
"awslogs-group": "${aws_cloudwatch_log_group.main.name}",
148+
"awslogs-region": "${data.aws_region.current.name}",
149+
"awslogs-stream-prefix": "container"
150+
}
151+
},
152+
"command": ${jsonencode(var.task_container_command)},
153+
"environment": ${jsonencode(local.task_environment)}
154+
}]
155+
EOF
156+
}
157+
158+
resource "aws_ecs_service" "service" {
159+
depends_on = [null_resource.lb_exists]
160+
name = var.name_prefix
161+
cluster = var.cluster_id
162+
task_definition = aws_ecs_task_definition.task.arn
163+
desired_count = var.desired_count
164+
launch_type = "FARGATE"
165+
deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
166+
deployment_maximum_percent = var.deployment_maximum_percent
167+
health_check_grace_period_seconds = var.health_check_grace_period_seconds
168+
169+
network_configuration {
170+
subnets = var.private_subnet_ids
171+
security_groups = [aws_security_group.ecs_service.id]
172+
assign_public_ip = var.task_container_assign_public_ip
173+
}
174+
175+
load_balancer {
176+
container_name = var.container_name != "" ? var.container_name : var.name_prefix
177+
container_port = var.task_container_port
178+
target_group_arn = aws_lb_target_group.task.arn
179+
}
180+
181+
deployment_controller {
182+
type = var.deployment_controller_type # CODE_DEPLOY or ECS
183+
}
184+
185+
dynamic "service_registries" {
186+
for_each = var.service_registry_arn == "" ? [] : [1]
187+
content {
188+
registry_arn = var.service_registry_arn
189+
container_port = var.task_container_port
190+
container_name = var.container_name != "" ? var.container_name : var.name_prefix
191+
}
192+
}
193+
}
194+
195+
# HACK: The workaround used in ecs/service does not work for some reason in this module, this fixes the following error:
196+
# "The target group with targetGroupArn arn:aws:elasticloadbalancing:... does not have an associated load balancer."
197+
# see https://github.com/hashicorp/terraform/issues/12634.
198+
# Service depends on this resources which prevents it from being created until the LB is ready
199+
resource "null_resource" "lb_exists" {
200+
triggers = {
201+
alb_name = var.lb_arn
202+
}
203+
}

0 commit comments

Comments
 (0)