Отказоустойчивость системы: подводные камни архитектуры
Три реплики сервиса не делают систему отказоустойчивой. Разбираем, почему приложение продолжает отдавать 502 после падения ноды, как база данных превращается в единую точку отказа и что измерять до выхода в прод.

Сервис из трёх реплик в Kubernetes может продолжить работу после падения одной ноды, но всё равно начать отдавать 502, терять задания и дублировать платежи. Причина обычно не в количестве контейнеров, а в цепочке зависимостей: балансировщик, readiness-проверки, база данных, очередь, DNS и повторные запросы должны переживать отказ согласованно. Ниже — разбор для типичного HTTP-сервиса на Kubernetes 1.29+ с PostgreSQL 16: где отказоустойчивость ломается первой, как это проверять и какой ценой.
Реплики приложения не равны отказоустойчивости
Реплика защищает только от отказа конкретного экземпляра приложения. Она не спасает, если все экземпляры подключены к одному PostgreSQL, размещены на одной ноде или используют общее хранилище, которое стало недоступно.
В Kubernetes важны как минимум четыре независимых слоя:
- размещение — реплики должны переживать отказ ноды или зоны доступности;
- маршрутизация — трафик нельзя отправлять в контейнер, который ещё запускается или уже не может обслуживать запросы;
- состояние — сессии, файлы, блокировки и транзакции не должны жить только внутри одного pod;
- зависимости — база, очередь, платёжный шлюз и внешние API должны иметь собственный сценарий отказа.
Если эти слои не согласованы, масштабирование увеличивает не надёжность, а число мест, где может возникнуть рассинхронизация.
Отказоустойчивость — это не обещание «ничего не упадёт». Это заранее проверенный сценарий: что именно отключится, сколько данных можно потерять и как система вернётся в рабочее состояние.
Главные подводные камни
Все реплики оказываются на одной ноде
Планировщик может разместить несколько pod рядом, если вы не задали правила распределения. Отказ ноды тогда выглядит как отказ всего приложения. Для критичного сервиса проверьте topology spread constraints или pod anti-affinity и убедитесь, что реплики действительно разнесены по нодам и зонам.
Проверка должна быть практической: получите список pod с указанием нод, затем смоделируйте недоступность одной ноды на тестовом кластере. Если после этого сервис не принимает запросы, количество реплик само по себе не обеспечивает доступность.
Readiness-проверка превращена в «процесс жив»
Проверка, которая возвращает успешный ответ только потому, что процесс запущен, не говорит о готовности приложения. Pod может принимать HTTP-запросы, но не иметь соединения с базой, миграциями или обязательной конфигурацией.
Слишком глубокая проверка тоже опасна. Если readiness каждый раз обращается к медленной базе и временно получает ошибку, Kubernetes уберёт здоровый pod из балансировки, а оставшиеся получат дополнительную нагрузку. Разделяйте:
- liveness — процесс завис и его нужно перезапустить;
- readiness — экземпляр временно не должен получать новый трафик;
- startup — приложению нужно время на первый запуск.
Смысл проверок — не подтвердить, что контейнер существует, а корректно управлять его участием в обслуживании трафика.
Обновление отключает больше экземпляров, чем выдерживает система
Rolling update и добровольное перемещение pod могут совпасть по времени. Без ограничения на недоступные реплики обновление способно остановить несколько экземпляров сразу. Pod Disruption Budget ограничивает добровольные disruptions*, но не заменяет распределение по нодам и не спасает от аварийного отказа оборудования.
Проверьте также время graceful shutdown. Приложение должно перестать принимать новый трафик, дождаться завершения допустимых запросов и только потом закрыть соединения. Если остановка длится дольше, чем период ожидания, запросы будут оборваны независимо от числа реплик.
Повторы превращают короткий отказ в перегрузку
Клиент, ingress, сервисная библиотека и внешний шлюз могут каждый повторить один и тот же запрос. При отказе зависимого сервиса десять исходных запросов быстро превращаются в десятки или сотни попыток. Это называют retry storm — штормом повторных запросов.
Повторы допустимы не для всех операций. Безопаснее повторять идемпотентный GET или запрос, оснащённый ключом идемпотентности. Для создания заказа одного сетевого таймаута недостаточно, чтобы решить, был ли заказ записан: сервер мог завершить транзакцию, а ответ потерялся по дороге.
У каждого повтора должны быть ограничение количества, backoff с jitter и общий deadline. Иначе система будет тратить последние ресурсы на попытки достучаться до уже недоступной зависимости.
PostgreSQL остаётся единой точкой отказа
Три экземпляра приложения не помогут, если единственный PostgreSQL недоступен. Репликация базы добавляет варианты восстановления, но не делает переключение мгновенным и не гарантирует нулевую потерю данных.
Для PostgreSQL 16 отдельно проектируют:
- синхронную или асинхронную репликацию и допустимый RPO;
- механизм обнаружения отказа и выбор нового primary;
- перенаправление клиентов после переключения;
- поведение активных транзакций и соединений в пуле;
- резервные копии и проверку восстановления.
Если старый primary вернётся в сеть без корректной процедуры возврата, две стороны могут считать себя главным узлом. Это сценарий split-brain**, который опаснее обычного простоя: система начинает принимать противоречивые записи.
Что именно даёт каждый подход
Отказоустойчивость состоит из нескольких механизмов, и у каждого своя цена. Нельзя считать резервную копию заменой репликации, а репликацию — заменой резервной копии.
| Механизм | Защищает от | Не защищает от | Цена |
|---|---|---|---|
| Несколько реплик приложения | Падения отдельного pod | Отказа базы, ноды с несколькими pod, неверного релиза | Дополнительные ресурсы и сложнее диагностика |
| Распределение по нодам и зонам | Отказа ноды или зоны | Ошибок в коде, общей базы, неверной маршрутизации | Меньше плотность размещения, выше стоимость кластера |
| Репликация PostgreSQL | Потери primary при настроенном failover | Ошибочных удалений, повреждения данных, неверных записей | Сложное переключение, контроль RPO и резервные копии |
| Очередь с повторной доставкой | Временной недоступности обработчика | Неидемпотентного обработчика и бесконечного роста очереди | Задержка обработки, дедупликация и мониторинг backlog |
Что измерять вместо абстрактного «сервис надёжный»
До аварийного теста зафиксируйте базовые значения. Нужны не только средние задержки, но и p95 или p99, доля ошибок, время восстановления и число потерянных операций.
- Падение pod. Удалите один экземпляр и измерьте, сколько запросов получили ошибку, как быстро новый pod прошёл startup и readiness.
- Падение ноды. Проверьте, остаются ли реплики в доступных зонах и не упирается ли новый pod в нехватку ресурсов.
- Разрыв соединения с базой. Зафиксируйте ошибки в приложении, длительность восстановления пула и судьбу открытых транзакций.
- Переключение PostgreSQL. Измерьте RTO — время до восстановления сервиса — и RPO — объём данных, который допустимо потерять.
- Недоступность внешнего API. Проверьте deadline, количество повторов, circuit breaker и попадание неуспешных сообщений в очередь повторной обработки.
Если тест не создаёт наблюдаемого сигнала, это не значит, что аварии нет. В логах и метриках должны быть видны причина удаления pod, смена роли базы, рост очереди, отказ подключения и завершение запроса по deadline.
Где архитектура ломается первой
Общее хранилище и локальные файлы
Файлы, загруженные в локальную файловую систему контейнера, могут исчезнуть вместе с pod. Общий диск устраняет одну проблему, но добавляет зависимость от доступности и производительности хранилища. Для пользовательских файлов заранее определите источник истины и процедуру восстановления, а не просто подключайте volume ко всем репликам.
Сессии в памяти
Если сессия пользователя хранится в памяти конкретного экземпляра, следующий запрос после балансировки может попасть в другой pod и потребовать повторной авторизации. Sticky sessions маскируют проблему, но привязывают пользователя к экземпляру и усложняют отказ. Для критичных сессий выбирайте внешнее хранилище с понятными сроками жизни и сценарием его отказа.
Очередь без контроля backlog
Очередь переживает кратковременный отказ обработчика, но не бесконечную деградацию. Следите за возрастом самого старого сообщения, скоростью поступления и скоростью обработки. Если обработчик неидемпотентен, повторная доставка может создать дубликаты: статус операции и бизнес-результат должны защищаться отдельно.
Резервная копия, которую никто не восстанавливал
Успешное создание backup не доказывает, что из него можно поднять рабочую систему. Регулярно выполняйте пробное восстановление в изолированной среде и проверяйте не только наличие таблиц, но и права, версии схемы, фоновые задания и конфигурацию подключения.
Сноска. RTO — целевое время восстановления сервиса. RPO — объём данных по времени, который допустимо потерять между последней согласованной копией и моментом отказа. Pod Disruption Budget — правило Kubernetes, ограничивающее число pod, которые можно добровольно вывести из работы одновременно.
Что делать дальше
Начните не с установки ещё одной копии приложения, а с карты зависимостей. Для каждого компонента запишите владельца, точку отказа, RTO, RPO, способ обнаружения проблемы и процедуру возврата.
Затем проведите три небольших теста: удаление одного pod, отказ ноды и переключение базы в тестовой среде. Сравните фактические значения с тем, что обещано пользователям. После этого настройте алерты на p99, долю 5xx, возраст очереди, ошибки пула соединений и задержку восстановления — именно эти показатели покажут, выдерживает ли система отказ, а не просто выглядит ли она многорепличной.
Субтитры и таймкоды: Расшифровка речи и субтитры по правилам читаемости: 42 знака в строке, две строки, минимум секунда на реплику.
Спросить за 40 ₽Источники: Kubernetes Documentation: Pod Disruptions, PostgreSQL 16 Documentation: Warm Standby and Streaming Replication
Частые вопросы
Сколько реплик нужно для отказоустойчивого сервиса?
Поможет ли Kubernetes пережить отказ базы данных?
Можно ли повторять любой запрос после таймаута?
Что будет, если readiness-проверка временно не работает?
Как понять, что резервные копии действительно пригодны?
Что произойдёт при сетевом разделении между узлами базы?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.