Настройка Service Discovery для микросервисов
Service Discovery позволяет микросервисам находить друг друга динамически, без жёстко прописанных IP-адресов. Когда Order Service нужно вызвать Payment Service, он запрашивает реестр: «где сейчас живёт payment-service?» — и получает актуальный адрес.
Client-Side vs Server-Side Discovery
Client-Side: сервис сам запрашивает реестр и выбирает экземпляр (с балансировкой на клиенте). Пример — Eureka + Ribbon в Spring Cloud.
Server-Side: сервис обращается к load balancer, который сам консультируется с реестром. Пример — Kubernetes DNS + Service, AWS ELB.
Варианты инструментов
| Инструмент | Подход | Интеграция |
|---|---|---|
| Kubernetes DNS | Server-side | Нативная для K8s |
| Consul | Client/Server-side | Любой стек |
| Eureka (Netflix OSS) | Client-side | Spring Cloud |
| etcd | KV + watch | Kubernetes, CoreDNS |
В Kubernetes большинство задач решает встроенный DNS — он знает адрес каждого сервиса по имени service-name.namespace.svc.cluster.local.
Consul Service Discovery
# consul-agent.hcl
datacenter = "dc1"
data_dir = "/opt/consul"
log_level = "INFO"
server = false
retry_join = ["consul-server:8300"]
# Health check каждые 10 сек
check = {
id = "order-service-health"
name = "Order Service Health"
http = "http://localhost:3000/health"
interval = "10s"
timeout = "3s"
}
Регистрация сервиса через API:
import Consul from 'consul';
const consul = new Consul({ host: process.env.CONSUL_HOST });
async function registerService() {
await consul.agent.service.register({
name: 'order-service',
id: `order-service-${process.env.POD_NAME}`,
address: process.env.POD_IP,
port: 3000,
tags: ['v1', 'production'],
check: {
http: `http://${process.env.POD_IP}:3000/health`,
interval: '10s',
deregisterCriticalServiceAfter: '1m'
}
});
}
// Дерегистрация при остановке
process.on('SIGTERM', async () => {
await consul.agent.service.deregister(`order-service-${process.env.POD_NAME}`);
process.exit(0);
});
Поиск сервиса и вызов:
async function getPaymentServiceUrl(): Promise<string> {
const services = await consul.health.service({
service: 'payment-service',
passing: true // только здоровые экземпляры
});
if (services.length === 0) {
throw new ServiceUnavailableError('payment-service');
}
// Round-robin балансировка
const instance = services[Math.floor(Math.random() * services.length)];
return `http://${instance.Service.Address}:${instance.Service.Port}`;
}
Kubernetes DNS (рекомендованный подход для K8s)
Для Kubernetes отдельный discovery не нужен — каждый Service получает DNS-запись:
apiVersion: v1
kind: Service
metadata:
name: payment-service
namespace: production
spec:
selector:
app: payment-service
ports:
- port: 80
targetPort: 3000
Теперь из любого пода: http://payment-service.production.svc.cluster.local/charge или просто http://payment-service в том же namespace.
Headless Service для прямого доступа к подам (StatefulSet):
spec:
clusterIP: None # headless
selector:
app: kafka
DNS вернёт A-запись для каждого пода: kafka-0.kafka.production.svc.cluster.local.
Health Checks
Сервис должен отвечать на /health или /readiness:
app.get('/health', (req, res) => {
const checks = {
database: dbPool.totalCount > 0 ? 'ok' : 'error',
redis: redisClient.isReady ? 'ok' : 'error',
uptime: process.uptime()
};
const healthy = Object.values(checks).every(v => v === 'ok' || typeof v === 'number');
res.status(healthy ? 200 : 503).json({ status: healthy ? 'ok' : 'degraded', checks });
});
Сроки реализации
- Service Discovery через Consul + регистрация/дерегистрация — 3–5 дней
- Kubernetes-нативный подход с правильными Health Checks — 1–2 дня







