Microservice communication in AWS ECS without a Load Balancer, using DNS Service Discovery¶
One day I dreamed about a simple AWS architecture. A few FastAPI microservices, a simple SQLite database (RDS was too expensive for me at this learning stage), and no ALB (Application Load Balancer) cost either.
This time I wanted to experiment with AWS ECS. I needed fast communication between services — reliable and scalable. The standard solution is an Application Load Balancer between microservices.
BUT. I wanted to build this without a Load Balancer, using AWS Cloud Map and DNS-based Service Discovery.
1. The problem: communication without a Load Balancer¶
How does it usually look?¶
In a typical AWS ECS project, microservices talk to each other through a Load Balancer. For example, if user-service needs to talk to auth-service, it usually looks like this:
- Create an Application Load Balancer (ALB).
- Register
auth-serviceas a target group. - Use the ALB address to communicate, for example:
http://auth-alb.mycompany.internal/login
Problems with a Load Balancer¶
- Cost — every ALB is an extra, fixed cost. With more services, the cost grows fast. It costs money even when traffic is small, and for a simple POC it was just unnecessary. I almost gave up on this small project, but I didn't — I pushed through instead.
- Complexity — you have to manage routing, target groups, ports, and health checks.
- Latency — requests go through the ALB layer, which adds an unnecessary hop, especially inside the same VPC.
What if... no Load Balancer at all?¶
In simple and medium microservice architectures, you can skip the Load Balancer completely. AWS ECS supports DNS Service Discovery, which lets services talk to each other directly by DNS name — simple, cheap, and fast.
2. The solution: DNS-based Service Discovery with AWS Cloud Map¶
What is Cloud Map?¶
AWS Cloud Map is a service that lets you create service names that point dynamically to running ECS instances (or other resources). ECS automatically registers running containers in Cloud Map.
Thanks to this, another microservice can simply ask:
stock-service.trading.platform
...and Cloud Map returns the correct IP address of the container.
3. Configuration in Terraform¶
The code below creates an entry in Cloud Map with A and SRV records and a MULTIVALUE routing policy.
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"
}
}
This DNS record and routing part was the hardest for me to understand.
- A — a classic DNS record with the container's IP address
- SRV — includes IP, port, weight, and protocol
- MULTIVALUE — DNS can return more than one IP
What does MULTIVALUE do?
When another microservice asks DNS for stock-service.trading-platform.local, Cloud Map (with routing policy = MULTIVALUE) returns all active IPs (and ports, if you use SRV) that ECS registered for that service. This means the client gets, say, 3 IP records, and it can pick any one of them — or try them one by one if the first one fails.
Success!
Or is it?
So first we create the DNS namespace:
module "dns_namespace" {
source = "./modules/core/dns_namespace"
namespace_name = "trading-platform.local"
vpc_id = var.vpc_id # Set this variable in root variables
}
Then we create service discovery entries:
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
}
Create a new ECS service:
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]
}
Here it was important to connect our Service Discovery:
service_registries {
registry_arn = aws_service_discovery_service.signal_processing_service_discovery.arn
port = "80"
}
This registers our service in AWS Cloud Map — and that's our DNS-based service discovery!
And everything together as a service:
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. In practice: how do microservices find each other?¶
Example service: stock-service.trading.platform, reachable from signal-service.
So we have one service that handles CRUD operations for stock quotes — stock-service — and a second service, signal-service, that wants to fetch that data and run some computation on it.
curl http://stock-service.trading.platform/api/v1/data/TICKER_1
When we have 3 task replicas running on ECS, DNS answers like this:
$ 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. Conclusions and what I learned¶
Sometimes it's good to have a small budget for learning :) With AWS Cloud Map and DNS Service Discovery, I managed to build light, flexible, and cheap microservice communication without a classic Load Balancer. Great for test environments and architectures where simplicity and low cost matter!
6. About me¶
I'm a beginner enthusiast of cloud computing, distributed system architecture, and simple, elegant (?) solutions. I build my own microservice-based platform and share what I learn along the way!!
Thanks!