Распределённые системы
Service Discovery
Цели урока
- Объяснить почему hardcode IP-адресов не работает в облачных архитектурах
- Сравнить DNS-based discovery и service registry по возможностям и ограничениям
- Различать self-registration и third-party registration паттерны
- Выбирать между client-side и server-side discovery для конкретных требований
Предварительные знания
- Понимание микросервисной архитектуры (сервисы как отдельные процессы)
- Базовое знание DNS (A-записи, TTL)
- Знакомство с Kubernetes на уровне Pod и Service концептов
Netflix в 2012 перешёл на AWS: 800 микросервисов, каждый с динамическими IP. Один деплой менял адреса десятков инстансов за секунды. Так появился Eureka - open-source service registry который теперь работает в тысячах компаний.
- **Kubernetes** - каждый из 5+ млн k8s-кластеров использует CoreDNS + Endpoints для service discovery из коробки
- **Consul** от HashiCorp - 100 000+ production deployments, включая Stripe, Cloudflare, Adobe
- **AWS ELB + Route 53** - server-side discovery для сотен тысяч приложений в AWS
- **Istio service mesh** - расширяет discovery до circuit breaking, canary deployments, mTLS между сервисами
- **etcd** - каждый Kubernetes API server хранит состояние кластера в etcd, включая Endpoints для discovery
Eureka и рождение cloud-native discovery
Netflix открыл исходный код Eureka в 2012 году после внутренней разработки для миграции на AWS. До Eureka использовался статический конфиг и проприетарные решения. Eureka ввёл ключевые концепции которые стали стандартом: self-registration, heartbeat, client-side caching. Сегодня Eureka - часть Spring Cloud и используется в тысячах Java-приложений, хотя сам Netflix перешёл на Consul для новых проектов.
Проблема: IP-адреса не постоянны
**Netflix, 2012. Переезд на AWS. 800+ микросервисов, каждый масштабируется горизонтально. EC2-инстанс запустился - получил новый IP. Перезапустился - снова новый IP. Hardcode адресов в конфигах стал невозможен.** Так Netflix создал Eureka - первый крупный open-source service registry. Сегодня каждый Kubernetes-кластер решает эту же проблему встроенными средствами.
**Суть проблемы:** в статической архитектуре сервис A знает адрес сервиса B заранее. В динамической - инстансы B появляются и исчезают, масштабируются от 1 до 100 и обратно. Нужен механизм, который отвечает на вопрос: "Какие инстансы сервиса X живы прямо сейчас и готовы принимать трафик?"
Три причины динамических адресов
| Причина | Что происходит | Частота |
|---|---|---|
| Autoscaling | Новые инстансы поднимаются под нагрузку, старые гасятся | Минуты-часы |
| Rolling deploy | Новая версия заменяет старую поочерёдно - каждый pod получает новый IP | При каждом деплое |
| Crash + restart | Kubernetes перезапускает упавший pod на другом узле - снова новый IP | Непредсказуемо |
Service discovery нужен только в микросервисах с сотнями сервисов
Достаточно двух сервисов с autoscaling чтобы потребовался discovery механизм
Как только один сервис имеет более одного инстанса или перезапускается автоматически - IP-адрес становится нестабильным. Kubernetes решает это для каждого приложения через ClusterIP и CoreDNS.
Почему hardcode IP-адресов не работает в современных облачных архитектурах?
DNS как service discovery
**Kubernetes использует DNS для service discovery из коробки с первого релиза в 2014. Каждый Service получает DNS-имя вида `payment-service.default.svc.cluster.local`. Pod не знает ни одного IP - только имена.** DNS - самый старый и распространённый механизм discovery, но с жёсткими ограничениями.
Ограничения DNS-based discovery
| Плюс | Минус | Последствие |
|---|---|---|
| Работает везде без доп. инфраструктуры | DNS TTL кеширует записи | Клиент продолжает слать запросы на мёртвый IP пока не истечёт TTL |
| Стандартный протокол - любой язык поддерживает | Нет встроенных health checks | Registry не знает что инстанс нездоров - только что он существует |
| Kubernetes CoreDNS обновляет записи быстро | Нет метаданных (версия, регион, вес) | Нельзя делать canary deploy через DNS без доп. инструментов |
**DNS TTL ловушка:** JVM и многие HTTP-клиенты агрессивно кешируют DNS-ответы. Даже при TTL=10s приложение может держать устаревший IP минутами. Решение - использовать Kubernetes Service (ClusterIP), который сам управляет routing через iptables/ipvs без DNS-кеш проблем.
Клиент получил IP сервиса через DNS. Сервис упал. DNS TTL = 30 секунд. Что произойдёт?
Service Registry: Consul, etcd, ZooKeeper
**Consul запущен HashiCorp в 2014. За первые 2 года - 10 000 production deployments. Причина популярности:** health checks из коробки, watch API для мгновенных обновлений, и KV store для конфигов в одном инструменте. etcd выбрал Kubernetes в 2014 как хранилище состояния кластера - с тех пор etcd работает в каждом k8s-кластере планеты.
Self-registration vs Third-party registration
| Паттерн | Кто регистрирует | Плюс | Минус |
|---|---|---|---|
| Self-registration | Сам сервис при старте | Сервис контролирует свои метаданные | Каждый сервис требует registry SDK |
| Third-party | Внешний registrar (Kubernetes, Registrator) | Сервисы не знают о registry | Registrar - доп. компонент, SPOF |
**Kubernetes использует third-party:** Endpoints controller следит за pods через API server и автоматически обновляет Endpoints объект при изменениях. Сервис не содержит ни строки кода для регистрации.
Популярные реестры
| Система | Консенсус | Health checks | Применение |
|---|---|---|---|
| Consul | Raft | HTTP / TCP / gRPC / script | Service mesh, multi-datacenter |
| etcd | Raft | Через TTL-ключи или watch | Kubernetes state store, конфиги |
| ZooKeeper | ZAB | Ephemeral nodes (исчезают при отключении) | Legacy: Kafka, Hadoop |
| Kubernetes DNS + Endpoints | Встроен в k8s | Readiness probe | В k8s-кластере из коробки |
Heartbeat = health check: если процесс шлёт heartbeat, значит сервис здоров
Heartbeat проверяет живость процесса, health check проверяет работоспособность сервиса
Процесс может быть жив (шлёт heartbeat), но зависнуть в deadlock, исчерпать connection pool или потерять соединение с БД. Health check endpoint /health должен проверять зависимости (БД, кеш, очереди) и возвращать 503 при проблемах.
Инстанс сервиса завис (процесс жив, но не отвечает на запросы). Какой механизм registry это обнаружит?
Client-side vs Server-side discovery
**Netflix OSS (2012-2015) стандартизировал client-side discovery через Ribbon + Eureka. AWS ELB и Kubernetes реализуют server-side. Оба паттерна живут в production прямо сейчас** - выбор зависит от требований к сложности клиента и допустимости дополнительного hop.
Client-Side Discovery
Клиент сам запрашивает registry, получает список живых инстансов и выбирает один по алгоритму балансировки.
Server-Side Discovery
Клиент отправляет запрос на фиксированный адрес load balancer. LB сам консультируется с registry и выбирает инстанс.
| Подход | Плюсы | Минусы | Пример |
|---|---|---|---|
| Client-Side | Гибкость в балансировке, нет SPOF | Каждый клиент нужно обновлять при смене логики балансировки | Netflix Ribbon + Eureka |
| Server-Side | Клиент прост - один фиксированный адрес | Дополнительный сетевой hop, LB - потенциальный SPOF | AWS ELB, Kubernetes Service |
В **Kubernetes** server-side discovery встроен: обращение по имени сервиса (`http://payment-service`) через CoreDNS резолвится в ClusterIP, а kube-proxy направляет трафик на один из живых Endpoints. Инфраструктура полностью прозрачна для приложения.
Команда хочет делать canary deployments: 10% трафика на v2, 90% на v1. Какой подход discovery подходит лучше?
Вопросы для размышления
- В Kubernetes Service discovery работает прозрачно - приложение не знает ни о каком registry. Что происходит внутри кластера когда pod падает и поднимается новый? Какие компоненты обновляют информацию о доступных инстансах?
Связанные уроки
- ds-09-gossip-protocols — Gossip (SWIM) is used for health propagation in discovery
- dist-09-raft — Consul uses Raft for consistent service registry reads
- ds-11-distributed-locks — Registry and lock service are both linearisable KV stores
- dist-12-consistency — Discovery reads need monotonic consistency guarantees
- net-18-dns-basics