Skip to content

Service Discovery

In a microservices architecture, services need to locate each other dynamically. Instances start, stop, and scale constantly โ€” hardcoded URLs donโ€™t work. Service Discovery solves this.

# Static config (breaks when instances change)
OrderService โ†’ http://payment-service:8080 โ† what if it moves?
# With service discovery
OrderService โ†’ [ Service Registry ] โ†’ current address of PaymentService

The client queries the registry and load-balances itself:

Client โ”€โ”€โ–บ Service Registry โ”€โ”€โ–บ gets list of healthy instances
โ—„โ”€โ”€ [10.0.1.5:8080, 10.0.1.6:8080]
Client picks one and calls it directly

Tools: Netflix Eureka, Consul (with client-side library)

The client calls a load balancer; it queries the registry:

Client โ”€โ”€โ–บ Load Balancer โ”€โ”€โ–บ Service Registry
โ—„โ”€โ”€ picks instance
โ”€โ”€โ–บ forwards request to instance

Tools: AWS ALB, Kubernetes Services, NGINX with Consul

Register a service:

{
"service": {
"name": "payment-service",
"id": "payment-service-1",
"address": "10.0.1.5",
"port": 8080,
"check": {
"http": "http://10.0.1.5:8080/health",
"interval": "10s",
"timeout": "3s"
}
}
}

Query for healthy instances:

Terminal window
curl http://localhost:8500/v1/health/service/payment-service?passing=true

.NET integration with Consul:

Terminal window
dotnet add package Consul
builder.Services.AddSingleton<IConsulClient>(sp =>
new ConsulClient(cfg => cfg.Address = new Uri("http://consul:8500")));
// Register on startup
var registration = new AgentServiceRegistration
{
ID = $"payment-service-{Environment.MachineName}",
Name = "payment-service",
Address = "10.0.1.5",
Port = 8080,
Check = new AgentServiceCheck
{
HTTP = "http://10.0.1.5:8080/health",
Interval = TimeSpan.FromSeconds(10)
}
};
await consulClient.Agent.ServiceRegister(registration);

Kubernetes provides service discovery out of the box via DNS. Every Service gets a DNS name:

apiVersion: v1
kind: Service
metadata:
name: payment-service
namespace: default
spec:
selector:
app: payment
ports:
- port: 80
targetPort: 8080

Other services can now reach it at:

http://payment-service.default.svc.cluster.local
# or within the same namespace simply:
http://payment-service

In .NET configuration:

{
"Services": {
"PaymentService": "http://payment-service"
}
}

Services must report health so the registry stops routing to unhealthy instances:

builder.Services.AddHealthChecks()
.AddSqlServer(connectionString)
.AddRedis(redisConnectionString);
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
ApproachProsCons
Kubernetes DNSZero config, built-inOnly works in Kubernetes
ConsulWorks anywhere, rich health checksOperational overhead
EurekaGood .NET/Java supportLess active development
AWS Cloud MapNative AWS integrationAWS only