Skip to content

Commit 39da430

Browse files
authored
Fargate Service Discovery (#39)
* feat: add fargate service discovery via route53 * chore: update documentation
1 parent 57e06c5 commit 39da430

File tree

2 files changed

+76
-30
lines changed

2 files changed

+76
-30
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ module "fargate" {
8181
auto_scaling_max_cpu_util = 60 # Number, Optional: the avg CPU utilization needed to trigger a auto scaling operation
8282
8383
allow_connections_from = ["api2"] # List[String], Optional: By default all services can only accept connections from their ALB. To explicitly allow connections from one service to another, use this label. This means that THIS service can be reached by service `api2`
84+
85+
service_discovery_enabled = true # Boolean, Optional: enables service discovery by creating a private Route53 zone. <service_name>.<cluster_name>.<terraform_workspace>.local
8486
}
8587
8688
another_service = {

main.tf

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ locals {
4545

4646
vpc_public_subnets_ids = ! var.vpc_create ? var.vpc_external_public_subnets_ids : module.vpc.public_subnets
4747

48-
services_names = keys(var.services)
49-
services = values(var.services)
48+
services = [for k, v in var.services : merge({ "name" : k }, v)]
5049
services_count = length(var.services)
50+
51+
# ⚠️ remove when https://github.com/hashicorp/terraform/issues/22560 gets fixed
52+
services_with_sd = [for s in local.services : s if lookup(s, "service_discovery_enabled", false)]
5153
}
5254

5355
data "aws_availability_zones" "this" {}
@@ -83,7 +85,7 @@ module "vpc" {
8385
resource "aws_ecr_repository" "this" {
8486
count = local.services_count > 0 ? local.services_count : 0
8587

86-
name = "${local.services_names[count.index]}-${terraform.workspace}"
88+
name = "${local.services[count.index].name}-${terraform.workspace}"
8789
}
8890

8991
data "template_file" "ecr-lifecycle" {
@@ -99,9 +101,9 @@ data "template_file" "ecr-lifecycle" {
99101
resource "aws_ecr_lifecycle_policy" "this" {
100102
count = local.services_count > 0 ? local.services_count : 0
101103

102-
repository = element(aws_ecr_repository.this[*].name, count.index)
104+
repository = aws_ecr_repository.this[count.index].name
103105

104-
policy = element(data.template_file.ecr-lifecycle[*].rendered, count.index)
106+
policy = data.template_file.ecr-lifecycle[count.index].rendered
105107
}
106108

107109
# ECS CLUSTER
@@ -129,7 +131,7 @@ data "template_file" "tasks" {
129131
template = file("${path.cwd}/${local.services[count.index].task_definition}")
130132

131133
vars = {
132-
container_name = local.services_names[count.index]
134+
container_name = local.services[count.index].name
133135
container_port = local.services[count.index].container_port
134136
repository_url = aws_ecr_repository.this[count.index].repository_url
135137
log_group = aws_cloudwatch_log_group.this[count.index].name
@@ -140,7 +142,7 @@ data "template_file" "tasks" {
140142
resource "aws_ecs_task_definition" "this" {
141143
count = local.services_count > 0 ? local.services_count : 0
142144

143-
family = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}"
145+
family = "${var.name}-${terraform.workspace}-${local.services[count.index].name}"
144146
container_definitions = data.template_file.tasks[count.index].rendered
145147
requires_compatibilities = ["FARGATE"]
146148
network_mode = "awsvpc"
@@ -159,7 +161,7 @@ data "aws_ecs_task_definition" "this" {
159161
resource "aws_cloudwatch_log_group" "this" {
160162
count = local.services_count > 0 ? local.services_count : 0
161163

162-
name = "/ecs/${var.name}-${local.services_names[count.index]}"
164+
name = "/ecs/${var.name}-${local.services[count.index].name}"
163165

164166
retention_in_days = lookup(local.services[count.index], "logs_retention_days", var.cloudwatch_logs_default_retention_days)
165167
}
@@ -196,7 +198,7 @@ resource "aws_security_group" "services" {
196198
count = local.services_count > 0 ? local.services_count : 0
197199

198200
vpc_id = local.vpc_id
199-
name = "${var.name}-${local.services_names[count.index]}-${terraform.workspace}-services-sg"
201+
name = "${var.name}-${local.services[count.index].name}-${terraform.workspace}-services-sg"
200202

201203
ingress {
202204
from_port = local.services[count.index].container_port
@@ -218,7 +220,7 @@ resource "aws_security_group" "services_dynamic" {
218220
count = local.services_count > 0 ? local.services_count : 0
219221

220222
vpc_id = local.vpc_id
221-
name = "${var.name}-${local.services_names[count.index]}-${terraform.workspace}-services-sg-dynamic"
223+
name = "${var.name}-${local.services[count.index].name}-${terraform.workspace}-services-sg-dynamic"
222224

223225
egress {
224226
from_port = 0
@@ -229,7 +231,7 @@ resource "aws_security_group" "services_dynamic" {
229231

230232
dynamic "ingress" {
231233
for_each = [for k, v in var.services : k
232-
if k != local.services_names[count.index] &&
234+
if k != local.services[count.index].name &&
233235
contains(lookup(local.services[count.index], "allow_connections_from", []), k)]
234236

235237
content {
@@ -257,7 +259,7 @@ resource "random_id" "target_group_sufix" {
257259
resource "aws_lb_target_group" "this" {
258260
count = local.services_count > 0 ? local.services_count : 0
259261

260-
name = "${var.name}-${local.services_names[count.index]}-${random_id.target_group_sufix[count.index].hex}"
262+
name = "${var.name}-${local.services[count.index].name}-${random_id.target_group_sufix[count.index].hex}"
261263
port = random_id.target_group_sufix[count.index].keepers.container_port
262264
protocol = "HTTP"
263265
vpc_id = local.vpc_id
@@ -279,7 +281,7 @@ resource "aws_lb_target_group" "this" {
279281
resource "aws_lb" "this" {
280282
count = local.services_count > 0 ? local.services_count : 0
281283

282-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-alb"
284+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-alb"
283285
subnets = slice(local.vpc_public_subnets_ids, 0, min(length(data.aws_availability_zones.this.names), length(local.vpc_public_subnets_ids)))
284286
security_groups = [aws_security_group.web.id]
285287
}
@@ -300,12 +302,46 @@ resource "aws_lb_listener" "this" {
300302
}
301303
}
302304

305+
# SERVICE DISCOVERY
306+
307+
resource "aws_service_discovery_private_dns_namespace" "this" {
308+
count = length([for s in local.services : s if lookup(s, "service_discovery_enabled", false)]) > 0 ? 1 : 0
309+
310+
name = "${var.name}.${terraform.workspace}.local"
311+
description = "${var.name} private dns namespace"
312+
vpc = local.vpc_id
313+
}
314+
315+
resource "aws_service_discovery_service" "this" {
316+
# ⚠️ replace when https://github.com/hashicorp/terraform/issues/22560 gets fixed
317+
# for_each = [for s in local.services : s if lookup(s, "service_discovery_enabled", false)]
318+
count = length(local.services_with_sd) > 0 ? length(local.services_with_sd) : 0
319+
320+
# name = each.value.name
321+
name = local.services_with_sd[count.index].name
322+
323+
dns_config {
324+
namespace_id = aws_service_discovery_private_dns_namespace.this[0].id
325+
326+
dns_records {
327+
ttl = 10
328+
type = "A"
329+
}
330+
331+
routing_policy = "MULTIVALUE"
332+
}
333+
334+
health_check_custom_config {
335+
failure_threshold = 1
336+
}
337+
}
338+
303339
# ECS SERVICES
304340

305341
resource "aws_ecs_service" "this" {
306342
count = local.services_count > 0 ? local.services_count : 0
307343

308-
name = local.services_names[count.index]
344+
name = local.services[count.index].name
309345
cluster = aws_ecs_cluster.this.name
310346
task_definition = "${aws_ecs_task_definition.this[count.index].family}:${max("${aws_ecs_task_definition.this[count.index].revision}", "${data.aws_ecs_task_definition.this[count.index].revision}")}"
311347
desired_count = local.services[count.index].replicas
@@ -326,10 +362,18 @@ resource "aws_ecs_service" "this" {
326362

327363
load_balancer {
328364
target_group_arn = aws_lb_target_group.this[count.index].arn
329-
container_name = local.services_names[count.index]
365+
container_name = local.services[count.index].name
330366
container_port = local.services[count.index].container_port
331367
}
332368

369+
dynamic "service_registries" {
370+
for_each = [for s in aws_service_discovery_service.this : s if s.name == local.services[count.index].name]
371+
372+
content {
373+
registry_arn = service_registries.value.arn
374+
}
375+
}
376+
333377
depends_on = ["aws_lb_target_group.this", "aws_lb_listener.this"]
334378

335379
lifecycle {
@@ -353,7 +397,7 @@ resource "aws_appautoscaling_target" "this" {
353397

354398
max_capacity = lookup(local.services[count.index], "auto_scaling_max_replicas", local.services[count.index].replicas)
355399
min_capacity = local.services[count.index].replicas
356-
resource_id = "service/${aws_ecs_cluster.this.name}/${local.services_names[count.index]}"
400+
resource_id = "service/${aws_ecs_cluster.this.name}/${local.services[count.index].name}"
357401
role_arn = aws_iam_role.autoscaling.arn
358402
scalable_dimension = "ecs:service:DesiredCount"
359403
service_namespace = "ecs"
@@ -364,7 +408,7 @@ resource "aws_appautoscaling_target" "this" {
364408
resource "aws_appautoscaling_policy" "this" {
365409
count = local.services_count > 0 ? local.services_count : 0
366410

367-
name = "${local.services_names[count.index]}-autoscaling-policy"
411+
name = "${local.services[count.index].name}-autoscaling-policy"
368412
policy_type = "TargetTrackingScaling"
369413
resource_id = aws_appautoscaling_target.this[count.index].resource_id
370414
scalable_dimension = aws_appautoscaling_target.this[count.index].scalable_dimension
@@ -417,14 +461,14 @@ data "template_file" "buildspec" {
417461
template = file("${path.module}/build/buildspec.yml")
418462

419463
vars = {
420-
container_name = local.services_names[count.index]
464+
container_name = local.services[count.index].name
421465
}
422466
}
423467

424468
resource "aws_codebuild_project" "this" {
425469
count = local.services_count > 0 ? local.services_count : 0
426470

427-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-builds"
471+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-builds"
428472
build_timeout = "10"
429473
service_role = aws_iam_role.codebuild.arn
430474

@@ -451,7 +495,7 @@ resource "aws_codebuild_project" "this" {
451495
resource "aws_iam_role" "codepipeline" {
452496
count = local.services_count > 0 ? local.services_count : 0
453497

454-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-codepipeline-role"
498+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-codepipeline-role"
455499

456500
assume_role_policy = file("${path.module}/policies/codepipeline-role.json")
457501
}
@@ -470,15 +514,15 @@ data "template_file" "codepipeline" {
470514
resource "aws_iam_role_policy" "codepipeline" {
471515
count = local.services_count > 0 ? local.services_count : 0
472516

473-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-codepipeline-role-policy"
517+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-codepipeline-role-policy"
474518
role = aws_iam_role.codepipeline[count.index].id
475519
policy = data.template_file.codepipeline[count.index].rendered
476520
}
477521

478522
resource "aws_codepipeline" "this" {
479523
count = local.services_count > 0 ? local.services_count : 0
480524

481-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-pipeline"
525+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-pipeline"
482526
role_arn = aws_iam_role.codepipeline[count.index].arn
483527

484528
artifact_store {
@@ -517,7 +561,7 @@ resource "aws_codepipeline" "this" {
517561
output_artifacts = ["imagedefinitions"]
518562

519563
configuration = {
520-
ProjectName = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-builds"
564+
ProjectName = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-builds"
521565
}
522566
}
523567
}
@@ -535,7 +579,7 @@ resource "aws_codepipeline" "this" {
535579

536580
configuration = {
537581
ClusterName = aws_ecs_cluster.this.name
538-
ServiceName = local.services_names[count.index]
582+
ServiceName = local.services[count.index].name
539583
FileName = "imagedefinitions.json"
540584
}
541585
}
@@ -609,14 +653,14 @@ data "template_file" "metric_dashboard" {
609653
region = var.region != "" ? var.region : data.aws_region.current.name
610654
alb_arn_suffix = aws_lb.this[count.index].arn_suffix
611655
cluster_name = aws_ecs_cluster.this.name
612-
service_name = local.services_names[count.index]
656+
service_name = local.services[count.index].name
613657
}
614658
}
615659

616660
resource "aws_cloudwatch_dashboard" "this" {
617661
count = local.services_count > 0 ? local.services_count : 0
618662

619-
dashboard_name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-metrics-dashboard"
663+
dashboard_name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-metrics-dashboard"
620664

621665
dashboard_body = data.template_file.metric_dashboard[count.index].rendered
622666
}
@@ -626,7 +670,7 @@ resource "aws_cloudwatch_dashboard" "this" {
626670
resource "aws_iam_role" "events" {
627671
count = local.services_count > 0 ? local.services_count : 0
628672

629-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-events-role"
673+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-events-role"
630674

631675
assume_role_policy = file("${path.module}/policies/events-role.json")
632676
}
@@ -644,7 +688,7 @@ data "template_file" "events" {
644688
resource "aws_iam_role_policy" "events" {
645689
count = local.services_count > 0 ? local.services_count : 0
646690

647-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-events-role-policy"
691+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-events-role-policy"
648692
role = aws_iam_role.events[count.index].id
649693
policy = data.template_file.events[count.index].rendered
650694
}
@@ -662,7 +706,7 @@ data "template_file" "ecr_event" {
662706
resource "aws_cloudwatch_event_rule" "events" {
663707
count = local.services_count > 0 ? local.services_count : 0
664708

665-
name = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-ecr-event"
709+
name = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-ecr-event"
666710
description = "Amazon CloudWatch Events rule to automatically start your pipeline when a change occurs in the Amazon ECR image tag."
667711

668712
event_pattern = data.template_file.ecr_event[count.index].rendered
@@ -674,7 +718,7 @@ resource "aws_cloudwatch_event_target" "events" {
674718
count = local.services_count > 0 ? local.services_count : 0
675719

676720
rule = aws_cloudwatch_event_rule.events[count.index].name
677-
target_id = "${var.name}-${terraform.workspace}-${local.services_names[count.index]}-codepipeline"
721+
target_id = "${var.name}-${terraform.workspace}-${local.services[count.index].name}-codepipeline"
678722
arn = aws_codepipeline.this[count.index].arn
679723
role_arn = aws_iam_role.events[count.index].arn
680724
}

0 commit comments

Comments
 (0)