How services register, discover each other, and handle failures via Consul
Service Discovery отвечает на простой, но критичный вопрос: где сейчас живет нужный сервис. В монолите адреса почти статичны: один процесс, одна база, один набор endpoints. В микросервисах и Kubernetes это перестает работать. Инстансы появляются после autoscaling, исчезают после rollout, переезжают между нодами, падают из-за OOM, временно не готовы после старта и возвращаются после recovery. Hardcoded IP превращается в технический долг уже на первом инциденте.
Без discovery клиент либо ходит в мертвые адреса, либо требует ручной переконфигурации, либо начинает использовать DNS как случайный список хостов без понятия о health. В нормальной системе discovery связывает три вещи: регистрацию адреса, проверку здоровья и выбор живого instance для запроса. Это основа для load balancing, service mesh, blue/green deploy, canary rollout и graceful failover.
Главная инженерная мысль: discovery не делает сервис надежным сам по себе. Он только поддерживает актуальную карту мира. Если карта устарела, health check слишком поверхностный, registry один и без quorum, а клиенты кэшируют адреса бесконечно, то failure все равно дойдет до пользователя. Поэтому discovery надо проектировать как control plane с собственными SLO, а не как второстепенный справочник.
::concept{slug="load-balancing"} ::concept{slug="dns"} ::concept{slug="service-mesh"}
Представь регистратуру в большом здании. Каждый сервис при входе сообщает: я жив, принимаю клиентов по такому адресу, умею такие операции. Клиент не бегает по всем этажам и не угадывает кабинет, а спрашивает регистратуру. Регистратура не просто хранит список, а периодически проверяет, что кабинет открыт и сотрудник реально принимает посетителей.
В distributed system эта регистратура называется service registry. Сервис при старте делает register, при остановке делает deregister, а между ними либо отвечает на active health checks, либо отправляет heartbeat. Клиент или gateway запрашивает только healthy instances и выбирает один из них. Если проверка здоровья провалилась, instance убирают из rotation; если он восстановился, возвращают обратно.
ADR-style framing: решение не в том, нужен ли discovery, а где находится ответственность за него. В client-side discovery каждый consumer знает registry API и сам балансирует между instances. В server-side discovery consumer видит стабильный endpoint, а gateway/load balancer берет registry и routing на себя. DNS-based discovery добавляет универсальность, но платит TTL, stale cache и ограниченными алгоритмами выбора.
Диаграмма показывает API Gateway, HA-группу Consul, два сервиса с несколькими instances и Postgres за сервисом заказов. Ребра отражают реальные зависимости: сервисы регистрируются в Consul и отвечают на health checks, gateway спрашивает Consul через DNS/HTTP API, затем маршрутизирует пользовательский трафик к живому backend. Consul nodes связаны между собой Raft/Gossip репликацией, чтобы registry не был single point of failure.
Первый важный акцент: gateway не ходит напрямую в случайный список IP. Он сначала спрашивает registry: дай passing instances для service-a. Это разделяет data plane и control plane. Data plane обрабатывает пользовательский запрос, control plane поддерживает актуальную информацию о topology.
Второй акцент: service discovery включает не только регистрацию. Если service-a-2 продолжает числиться в registry после падения, discovery становится вредным: он уверенно направляет трафик в мертвую точку. Поэтому в диаграмме health check failure приводит к статусу critical, репликации этого статуса на followers и исключению instance из ответа gateway.
Третий акцент: восстановление не должно требовать ручного вмешательства. После restart service-a-2 снова регистрируется, проходит проверку /health, получает status passing, и gateway начинает распределять трафик на оба instances.
registration показывает старт сервиса. service-a-1 отправляет Consul запрос регистрации, Consul подтверждает endpoint и health check, затем реплицирует каталог на followers. Это учит, что registry является состоянием control plane, и его надо защищать так же серьезно, как любую критичную базу данных: quorum, backup, мониторинг leader election, лимиты на churn.
discovery показывает normal path. Gateway получает клиентский запрос, спрашивает Consul только о passing instances, выбирает service-a-1 и forward-ит HTTP traffic. Это учит отделять выбор instance от бизнес-логики. Клиенту не надо знать, сколько replicas у каталога и где они сейчас запущены.
failure показывает провал health check. Consul видит timeout, переводит service-a-2 в critical, синхронизирует статус и больше не возвращает его в списке healthy endpoints. Пользовательский запрос продолжает проходить через service-a-1. Урок: failure скрывается не магией, а быстрым удалением плохого endpoint из rotation.
recovery показывает обратный путь. После restart instance не должен сразу получать traffic только потому, что процесс поднялся. Он проходит registration и readiness check. Это принципиально для приложений с warmup: соединения к базе, migration lock, загрузка модели, заполнение локального cache.
ADR-001: client-side discovery против server-side discovery. Client-side дает гибкость: consumer может использовать latency-aware routing, zone preference, custom retry policy и circuit breaker на уровне SDK. Цена: каждый язык и сервис должны иметь discovery client, retry semantics и observability. В polyglot-организации это быстро превращается в набор несовместимых библиотек. Server-side discovery делает clients проще: они ходят в gateway или Kubernetes Service. Цена: дополнительный hop, bottleneck на proxy layer и необходимость проектировать сам gateway как критичный компонент.
ADR-002: active health checks против passive health. Active checks предсказуемы и быстро исключают dead process, но часто проверяют слишком мало. TCP connect или 200 OK на /health не доказывает, что сервис может обработать заказ: база может быть недоступна, очередь переполнена, thread pool исчерпан. Passive health видит реальные 5xx, timeouts и reset от production traffic, но реагирует после пользовательских ошибок. Практический выбор: lightweight liveness для restart, readiness для rotation и passive outlier detection в proxy.
ADR-003: DNS discovery против registry API. DNS работает почти везде и не требует SDK. Но DNS TTL, client-side caching и разные resolver semantics делают failover менее точным. Registry API дает richer metadata: tags, zones, weights, health status, service subsets. Цена: клиенты или proxy должны понимать этот API. Поэтому Consul DNS удобен для простых consumers, а Envoy xDS или Consul HTTP API лучше для сложного routing.
ADR-004: strict consistency registry против availability. Если registry недоступен, что делать gateway: отказывать всем или использовать last known good endpoints. Для user-facing traffic обычно лучше bounded staleness: держать локальный cache endpoints с TTL, но не держать stale entries бесконечно. Для admin или payment flows можно выбрать fail-closed, чтобы не отправлять операции в неизвестное состояние.
Kubernetes решает discovery через Service, EndpointSlice и CoreDNS. Pod readiness влияет на попадание в endpoints, а kube-proxy или CNI реализует data plane. Это server-side/DNS hybrid: приложение часто видит стабильное DNS имя, а платформа меняет backing endpoints.
Consul дает service registry, health checks, KV и DNS/HTTP API. Он полезен в mixed environment, где есть VMs, bare metal, Kubernetes и несколько datacenters. Consul Connect добавляет service mesh и intentions, то есть discovery становится частью security model.
Envoy и Istio используют xDS: control plane сообщает proxy, какие clusters, endpoints и routes существуют. Это делает discovery динамическим и тонким: можно менять weights, subsets, retries и circuit breakers без redeploy приложения.
AWS ECS и Cloud Map дают managed registry для containers. AWS ALB target groups решают похожую задачу с другой стороны: service instance становится target, health check управляет rotation, а DNS имени ALB остается стабильным.
Registry как single point of failure. Один Consul server или один etcd node без quorum хуже hardcoded config: теперь при падении registry ломается весь fleet.
Health check проверяет только порт. Процесс может слушать socket, но не иметь соединения к DB, быть в deadlock, стоять на migration или отдавать 500 на реальных requests.
Слишком агрессивный TTL или polling. Если все clients каждую секунду спрашивают registry, control plane сам становится bottleneck. Если TTL слишком длинный, failover растягивается на минуты.
Нет deregistration на graceful shutdown. Instance получает SIGTERM, уже перестал принимать работу, но остается в registry и продолжает ловить traffic до timeout.
Путают liveness и readiness. Liveness отвечает на вопрос, надо ли перезапустить процесс. Readiness отвечает на вопрос, можно ли давать ему пользовательский traffic. Это разные проверки.
Retries без учета discovery. Если client получил список из трех endpoints и все retries делает в один и тот же мертвый instance, discovery не помогает. Retry должен выбирать другой healthy endpoint и уважать timeout budget.
Не надо поднимать отдельный Consul или Eureka для маленького монолита с одним backend и одним database. Статический config, DNS record или managed load balancer будут проще и надежнее.
Не надо внедрять client-side discovery в каждую команду, если уже есть Kubernetes Services, service mesh или managed gateway, который покрывает требования. Лишний SDK увеличит поверхность отказов.
Не надо использовать discovery как замену versioned API contracts. То, что сервис найден, не означает, что он совместим по schema, auth или behavior.
Не надо решать discovery direct SQL или вручную редактируемыми списками IP. Если runtime topology меняется автоматически, registry должен обновляться автоматически и проходить validation.
Начни с load-balancing, потому что discovery почти всегда заканчивается выбором backend. Затем прочитай dns, чтобы понимать TTL и caching. После этого переходи к service-mesh: mesh превращает discovery в полноценный control plane для retries, timeouts, mTLS и traffic shaping.
Полезные внешние источники: Chris Richardson про Service Discovery patterns, Consul documentation по health checks и DNS interface, Kubernetes docs про Services и EndpointSlices, Envoy xDS overview.
Связанные CloudArch материалы: ::concept{slug="load-balancing"} ::concept{slug="dns"} ::concept{slug="timeout-deadline"} ::concept{slug="sidecar"}