Налаштування Service Discovery для мікросервісів
Service Discovery дозволяє мікросервісам знаходити один одного динамічно, без жорстко прописаних IP-адрес. Коли Order Service потрібно викликати Payment Service, він запитує реєстр: «де зараз живе payment-service?» — і отримує актуальну адресу.
Client-Side проти 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 дні







