---
name: service-discovery
description: Service Discovery và Service Registry patterns. Use for dynamic service location, health checking, và load balancing trong microservices.
compatibility: ".NET 10+, Consul, Kubernetes, DNS"
metadata:
author: Velik Ho
version: "1.0"
---
# Service Discovery / Service Discovery Pattern
Patterns cho service discovery và dynamic service location trong microservices.
## When to Use This Skill / Khi Nào Sử Dụng
Use this skill when:
- Services need to find each other dynamically / Services cần tìm nhau động
- Deploying to Kubernetes / Triển khai trên Kubernetes
- Implementing client-side load balancing / Triển khai load balancing phía client
- Health checking service instances / Kiểm tra health của service instances
## Core Concepts / Khái Niệm Cốt Lõi
### Service Discovery Patterns
```
┌─────────────────────────────────────────────────────────────┐
│ SERVICE DISCOVERY PATTERNS │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CLIENT-SIDE DISCOVERY │ │
│ │ │ │
│ │ Client ──Query──▶ Registry ──▶ Service List │ │
│ │ │ │ │
│ │ └──Direct Call──▶ Service Instance │ │
│ │ │ │
│ │ Pros: Simpler, fewer hops │ │
│ │ Cons: Client needs discovery logic │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SERVER-SIDE DISCOVERY │ │
│ │ │ │
│ │ Client ──▶ Load Balancer ──Query──▶ Registry │ │
│ │ │ │ │
│ │ └──▶ Service Instance │ │
│ │ │ │
│ │ Pros: Client stays simple │ │
│ │ Cons: Extra network hop │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Service Registry / Registry dịch vụ
```
┌─────────────────────────────────────────────────────────────┐
│ SERVICE REGISTRY │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Service: user-service │ │
│ │ Instances: │ │
│ │ - host: 10.0.1.10, port: 5001, health: ✅ │ │
│ │ - host: 10.0.1.11, port: 5001, health: ✅ │ │
│ │ - host: 10.0.1.12, port: 5001, health: ❌ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Service: order-service │ │
│ │ Instances: │ │
│ │ - host: 10.0.2.10, port: 5002, health: ✅ │ │
│ │ - host: 10.0.2.11, port: 5002, health: ✅ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Discovery Options Comparison
| Option | Type | Complexity | Best For |
|--------|------|------------|----------|
| **Kubernetes DNS** | Server-side | Low | K8s deployments |
| **Consul** | Client-side | Medium | Multi-platform |
| **Eureka** | Client-side | Medium | Spring ecosystem |
| **DNS + Load Balancer** | Server-side | Low | Simple setups |
## Key Patterns / Mẫu Chính
### Kubernetes Service Discovery
```yaml
# EN: Kubernetes Service for internal discovery
# VI: Kubernetes Service cho discovery nội bộ
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: goodgo
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 5001
type: ClusterIP
---
# EN: Deployment with health probes
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: goodgo
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: goodgo/user-service:latest
ports:
- containerPort: 5001
livenessProbe:
httpGet:
path: /health/live
port: 5001
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 5001
initialDelaySeconds: 5
periodSeconds: 5
```
### Using Kubernetes DNS in .NET
```csharp
///
/// EN: HTTP client using Kubernetes DNS.
/// VI: HTTP client sử dụng Kubernetes DNS.
///
// Program.cs
builder.Services.AddHttpClient(client =>
{
// EN: Use Kubernetes service DNS name
// VI: Sử dụng Kubernetes service DNS name
client.BaseAddress = new Uri("http://user-service.goodgo.svc.cluster.local");
})
.AddStandardResilienceHandler();
// appsettings.json for different environments
{
"Services": {
"UserService": {
"BaseUrl": "http://user-service.goodgo.svc.cluster.local" // K8s
// "BaseUrl": "http://localhost:5001" // Local dev
}
}
}
```
### Consul Service Registration
```csharp
///
/// EN: Register service with Consul.
/// VI: Đăng ký service với Consul.
///
public static class ConsulServiceExtensions
{
public static IServiceCollection AddConsulServiceDiscovery(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddSingleton(sp =>
{
var consulAddress = configuration["Consul:Address"];
return new ConsulClient(cfg =>
{
cfg.Address = new Uri(consulAddress!);
});
});
services.AddHostedService();
return services;
}
}
///
/// EN: Background service for Consul registration.
/// VI: Background service cho Consul registration.
///
public class ConsulRegistrationService : IHostedService
{
private readonly IConsulClient _consulClient;
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private string? _registrationId;
public ConsulRegistrationService(
IConsulClient consulClient,
IConfiguration configuration,
ILogger logger)
{
_consulClient = consulClient;
_configuration = configuration;
_logger = logger;
}
public async Task StartAsync(CancellationToken ct)
{
var serviceName = _configuration["Service:Name"]!;
var serviceHost = _configuration["Service:Host"]!;
var servicePort = int.Parse(_configuration["Service:Port"]!);
_registrationId = $"{serviceName}-{Guid.NewGuid():N}";
var registration = new AgentServiceRegistration
{
ID = _registrationId,
Name = serviceName,
Address = serviceHost,
Port = servicePort,
Tags = new[] { "api", "v1" },
Check = new AgentServiceCheck
{
HTTP = $"http://{serviceHost}:{servicePort}/health",
Interval = TimeSpan.FromSeconds(10),
Timeout = TimeSpan.FromSeconds(5),
DeregisterCriticalServiceAfter = TimeSpan.FromMinutes(1)
}
};
await _consulClient.Agent.ServiceRegister(registration, ct);
_logger.LogInformation(
"Registered service {ServiceName} with Consul as {RegistrationId}",
serviceName, _registrationId);
}
public async Task StopAsync(CancellationToken ct)
{
if (_registrationId != null)
{
await _consulClient.Agent.ServiceDeregister(_registrationId, ct);
_logger.LogInformation("Deregistered service {RegistrationId}", _registrationId);
}
}
}
```
### Client-Side Load Balancing with Consul
```csharp
///
/// EN: HTTP client with Consul-based service discovery.
/// VI: HTTP client với service discovery dựa trên Consul.
///
public class ConsulServiceDiscoveryClient
{
private readonly IConsulClient _consulClient;
private readonly ILogger _logger;
public ConsulServiceDiscoveryClient(
IConsulClient consulClient,
ILogger logger)
{
_consulClient = consulClient;
_logger = logger;
}
public async Task GetServiceUriAsync(
string serviceName,
CancellationToken ct = default)
{
var services = await _consulClient.Health.Service(
serviceName,
tag: null,
passingOnly: true,
ct);
if (services.Response.Length == 0)
{
_logger.LogWarning("No healthy instances found for {ServiceName}", serviceName);
return null;
}
// EN: Simple round-robin load balancing
// VI: Load balancing round-robin đơn giản
var service = services.Response[Random.Shared.Next(services.Response.Length)];
var uri = new Uri($"http://{service.Service.Address}:{service.Service.Port}");
_logger.LogDebug(
"Resolved {ServiceName} to {Uri}",
serviceName, uri);
return uri;
}
}
///
/// EN: HTTP message handler with service discovery.
/// VI: HTTP message handler với service discovery.
///
public class ServiceDiscoveryHandler : DelegatingHandler
{
private readonly ConsulServiceDiscoveryClient _discovery;
private readonly string _serviceName;
public ServiceDiscoveryHandler(
ConsulServiceDiscoveryClient discovery,
string serviceName)
{
_discovery = discovery;
_serviceName = serviceName;
}
protected override async Task SendAsync(
HttpRequestMessage request,
CancellationToken ct)
{
var serviceUri = await _discovery.GetServiceUriAsync(_serviceName, ct);
if (serviceUri == null)
throw new ServiceNotFoundException(_serviceName);
// EN: Replace the request URI with discovered service
// VI: Thay thế URI request với service được discover
var builder = new UriBuilder(request.RequestUri!)
{
Scheme = serviceUri.Scheme,
Host = serviceUri.Host,
Port = serviceUri.Port
};
request.RequestUri = builder.Uri;
return await base.SendAsync(request, ct);
}
}
```
### Health Check Endpoints
```csharp
///
/// EN: Health check endpoints for service discovery.
/// VI: Health check endpoints cho service discovery.
///
// Program.cs
builder.Services.AddHealthChecks()
.AddDbContextCheck("database")
.AddRedis(builder.Configuration.GetConnectionString("Redis")!, "redis")
.AddRabbitMQ(builder.Configuration.GetConnectionString("RabbitMQ")!, "rabbitmq");
var app = builder.Build();
// EN: Liveness probe - is the service running?
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false, // No checks, just confirm the process is running
ResponseWriter = WriteMinimalResponse
});
// EN: Readiness probe - can the service handle requests?
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = WriteDetailedResponse
});
// EN: Full health check for monitoring
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = WriteDetailedResponse
});
static Task WriteMinimalResponse(HttpContext context, HealthReport report)
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync(report.Status.ToString());
}
static Task WriteDetailedResponse(HttpContext context, HealthReport report)
{
context.Response.ContentType = "application/json";
var response = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description,
duration = e.Value.Duration.TotalMilliseconds
}),
totalDuration = report.TotalDuration.TotalMilliseconds
};
return context.Response.WriteAsJsonAsync(response);
}
```
## Common Mistakes / Lỗi Thường Gặp
### 1. Hardcoded Service URLs
```csharp
// ❌ BAD: Hardcoded URLs
var client = new HttpClient
{
BaseAddress = new Uri("http://10.0.1.50:5001") // What if IP changes?
};
// ✅ GOOD: Use service discovery
var client = new HttpClient
{
BaseAddress = new Uri("http://user-service.goodgo.svc.cluster.local")
};
// Or with Consul
var serviceUri = await _discovery.GetServiceUriAsync("user-service");
```
### 2. No Health Checks
```yaml
# ❌ BAD: No health probes
spec:
containers:
- name: api
image: my-api:latest
# ✅ GOOD: With health probes
spec:
containers:
- name: api
image: my-api:latest
livenessProbe:
httpGet:
path: /health/live
port: 80
readinessProbe:
httpGet:
path: /health/ready
port: 80
```
### 3. Missing Retry on Discovery Failure
```csharp
// ❌ BAD: Single attempt
var service = await _discovery.GetServiceAsync("order-service");
await _client.GetAsync(service.Uri + "/api/orders");
// ✅ GOOD: With retry and fallback
var services = await _discovery.GetHealthyServicesAsync("order-service");
foreach (var service in services)
{
try
{
return await _client.GetAsync(service.Uri + "/api/orders");
}
catch (HttpRequestException)
{
continue; // Try next instance
}
}
throw new ServiceUnavailableException("order-service");
```
## Quick Reference / Tham Chiếu Nhanh
### Kubernetes Service DNS Format
| Type | Format | Example |
|------|--------|---------|
| Same namespace | `` | `user-service` |
| Different namespace | `.` | `user-service.goodgo` |
| Full FQDN | `..svc.cluster.local` | `user-service.goodgo.svc.cluster.local` |
### Consul Health Check Types
| Type | Use For |
|------|---------|
| HTTP | REST APIs |
| TCP | Databases, caches |
| gRPC | gRPC services |
| Script | Custom checks |
### Health Probe Configuration
| Probe | Purpose | Failure Action |
|-------|---------|----------------|
| Liveness | Is process alive? | Restart container |
| Readiness | Can handle traffic? | Remove from LB |
| Startup | Is starting up? | Wait before checking |
## Resources / Tài Nguyên
- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [Deployment Kubernetes](../deployment-kubernetes/SKILL.md) - K8s patterns
- [Docker Traefik](../docker-traefik/SKILL.md) - Container networking
- [Inter-service Communication](../inter-service-communication/SKILL.md) - HTTP clients
- [Error Handling](../error-handling-patterns/SKILL.md) - Resilience patterns