Komunikacja mikroserwisów w AWS ECS bez Load Balancera dzięki DNS Service Discovery

Pewnego dnia zamarzyłem o prostej architekturze na AWS. Kilka microserwisów z FastAPI, prosta baza SQLLite, bo RDS był dla mnie za drogi, na etapie nauki AWS... Nie wspominając o kosztach ALB (Application Load Balancer).

Chciałem tym razem poeksperymentować z AWS ECS, zależało mi szybkiej komunikacji pomiędzy serwisami, niezawodnej i skalowalnej. Standardowe rozwiązanie to Application Load Balancer pomiędzy mikroserwisami.

ALE Chciałem to zbudować bez użycia Load Balancera, wykorzystując AWS Cloud Map i **DNS-based Service Discovery"


1. Problem: Komunikacja bez Load Balancera

Jak to wygląda zazwyczaj?

W klasycznych projektach na AWS ECS, komunikacja między mikroserwisami odbywa się przez Load Balancer. Przykładowo, jeśli mamy user-service, który potrzebuje komunikować się z auth-service, zwykle wygląda to tak:

  1. Tworzymy Application Load Balancer (ALB).
  2. Rejestrujemy auth-service jako target group.
  3. Używamy adresu ALB do komunikacji, np.:
http://auth-alb.mycompany.internal/login

Problemy z Load Balancerem:

  • Koszt – Każdy ALB to dodatkowy, stały koszt. Przy większej liczbie usług koszty rosną znacząco. Kosztuje gdy ruch jest niewielki, a dla samego POC, był poprostu zbędny.Poczułem strach w oczach i chciałem się poddać, malutki projekt, ale nie poddałem się i nie polecialem po Siano do Bociana.
  • Złożoność – Trzeba zarządzać routingiem, target groups, portami i health checkami.
  • Opóźnienia – Zapytania są kierowane przez warstwę ALB, co niepotrzebnie wydłuża ścieżkę, zwłaszcza w obrębie tej samej sieci VPC.

A gdyby tak… bez Load Balancera?

W prostych i średnich architekturach mikroserwisowych można całkowicie zrezygnować z Load Balancera. AWS ECS umożliwia użycie DNS Service Discovery, co pozwala usługom komunikować się bezpośrednio przez nazwę DNS — prosto, tanio i szybko.


2. Rozwiązanie: DNS-based Service Discovery z AWS Cloud Map

Co to jest Cloud Map?

AWS Cloud Map to usługa umożliwiająca tworzenie nazw usług (service names), które dynamicznie wskazują na działające instancje ECS (lub inne). ECS automatycznie rejestruje uruchomione kontenery w Cloud Map.

Dzięki temu, inny mikroserwis może po prostu zapytać:

stock-service.trading.platform

…i Cloud Map przekaże prawidłowy adres IP kontenera.


3. Konfiguracja w Terraform

Poniższy kod tworzy wpis w Cloud Map z obsługą rekordów A i SRV oraz strategią MULTIVALUE.

resource "aws_service_discovery_service" "service_discovery" {
  name = var.service_name

  dns_config {
    namespace_id = var.namespace_id

    dns_records {
      type = "A"
      ttl  = 60
    }

    dns_records {
      type = "SRV"
      ttl  = 60
    }

    routing_policy = "MULTIVALUE"
  }
}

To był najtrudniejszy dla mnie fragment wpisów w DNS records, a w zasadzie routingu.

  • A - klastyczny record DNS z adresem IP kontenera
  • SRV - zawiera IP, port, wage i protokół
  • MULTIVALUE - DNS może zwrócić wiele IP

Co robi MULTIVALUE? Gdy inne mikroserwisy zapytają DNS o stock-service.trading-platform.local, to:

Cloud Map (z routing policy = MULTIVALUE) zwróci wszystkie aktywne IP (i porty jeśli używasz SRV) zarejestrowane przez ECS dla tego serwisu. To znaczy, że klient dostaje 3 rekordy IP, z których może wybrać dowolny — lub po kolei próbować wszystkich, jeśli jeden nie działa.

Zwycięstwo!

Albo i nie?

A więc tworzymy DNS

module "dns_namespace" {
  source        = "./modules/core/dns_namespace"
  namespace_name = "trading-platform.local"
  vpc_id       = var.vpc_id  # Set this variable in root variables
}

Tworzymy service discovery:

module "stock_data_service_discovery" {
  source        = "./modules/core/service_discovery"
  service_name  = "stock-service"
  namespace_id  = module.dns_namespace.namespace_id
}

module "signal_processing_service_discovery" {
  source        = "./modules/core/service_discovery"
  service_name  = "signal-processing-service"
  namespace_id  = module.dns_namespace.namespace_id
}

Utworzenie nowej usługi w ECS:

resource "aws_ecs_service" "signal_processing_service" {
  name            = "signal-processing-service"
  cluster         = var.ecs_cluster_id
  task_definition = aws_ecs_task_definition.task_definition.arn
  desired_count   = 1
  launch_type     = "FARGATE"
  service_registries {
    registry_arn = aws_service_discovery_service.signal_processing_service_discovery.arn
    port         = "80"

  }
  network_configuration {
    subnets          = var.subnets
    security_groups  = var.security_groups
  }
  depends_on = [aws_service_discovery_service.signal_processing_service_discovery]
}

Tutaj koniecznie trzeba było podpiąć nasz Sevice Discovery

service_registries {
  registry_arn = aws_service_discovery_service.signal_processing_service_discovery.arn
  port         = "80"
}

czyli rejestrujemy naszą usługe w AWS Cloud Map - czyli mamy nasz DNS-based service discovery!

i wszystko razem jako serwis:

module "stock_data_service_ecs" {
  source                      = "./modules/services/stock_data_service"
  subnets                     = var.subnets
  security_groups             = var.security_groups
  ecs_cluster_id              = aws_ecs_cluster.ecs_cluster.id
  efs_file_system_id          = module.efs.file_system_id
  ecs_task_execution_role_arn = module.iam.ecs_task_execution_role_arn
  ecr_repository_url          = module.stock_data_ecr.repository_url
  namespace_id                = module.dns_namespace.id
}

module "signal_processing_service_ecs" {
  source                      = "./modules/services/signal_processing_service"
  subnets                     = var.subnets
  security_groups             = var.security_groups
  ecs_cluster_id              = aws_ecs_cluster.ecs_cluster.id
  efs_file_system_id          = module.efs.file_system_id
  ecs_task_execution_role_arn = module.iam.ecs_task_execution_role_arn
  ecr_repository_url          = module.signal_processing_ecr.repository_url
  namespace_id                = module.dns_namespace.id
}

4. Praktyka: Jak mikroserwisy odnajdują się nawzajem?

Przykład serwis: stock-service.trading.platform' dostępny zsignal-service. Czyli mamy serwis który odpowiada za CRUD operacje związanie z notowaniami -stock-service', a drugi serwis signal-service chce pobrać dane i dokonaćp pewnej operacji.

curl http://stock-service.trading.platform/api/v1/data/TICKER_1

Gdy mamy 3 repliki tasków na ECS to otrzymujemy takie odpowiedzi z DNS'a:

$ dig stock-service.trading-platform.local

;; ANSWER SECTION:
stock-service.trading.local.  60  IN  A  10.0.1.101
stock-service.trading.local.  60  IN  A  10.0.1.102
stock-service.trading.local.  60  IN  A  10.0.1.103

5. Wnioski i czego się nauczyłem

Czasami dobrze mieć mały budżet na swoją naukę :) Przy pomocy AWS Cloud Map i DNS Service Discovery udało się zbudować lekką, elastyczną i tanią komunikację mimikroserwisów bez użycia klasyczenego Load Balancera. Idealne dla środowisk testowych, ,architektur, gdzie ważna jest prostota i niskie koszty!

6. O mnie

Jestem początkującym pasjonatem chmury, architektury systemów rozproszonych i prostych, eleganckich (?) rozwiązań. Buduję własną platformę opartą o mikroserwisy i dzielę się tym, czego uczę się po drodze!!

Dzięki!