Продакшен Kubernetes: паттерны отказоустойчивости и масштабирования

Kubernetes проявляет себя не в моменты идеальной работы, а когда инфраструктура начинает сыпаться: нода уходит в NotReady, на сервис обрушивается всплеск трафика, внешний API зависает на полсекунды, у одного из подов заканчивается память. В проде не нужны «магические настройки» — нужны чёткие, проверенные паттерны, которые снижают риск простоя и позволяют системе расти без ручного вмешательства. Я собрал практический разбор того, как строить отказоустойчивый и масштабируемый кластер в реальных условиях: что включать обязательно, что проверять в первую очередь и какие ошибки чаще всего приводят к ночным инцидентам.

Что означает «отказоустойчивость» в Kubernetes

Отказоустойчивость — это способность приложения продолжать обслуживать запросы при частичных сбоях инфраструктуры. В Kubernetes это не одна функция, а совокупность механизмов, которые нужно сознательно включить и настроить:

  • несколько реплик приложения вместо одной;
  • автоматическое пересоздание упавших подов;
  • перераспределение нагрузки между экземплярами;
  • размещение подов по разным нодам и зонам доступности;
  • корректная реакция на недоступность зависимостей;
  • безопасное и предсказуемое обновление без простоя.

Важно понимать: Kubernetes не делает сервис устойчивым автоматически. Он только даёт инструменты. Если приложение хранит сессию в памяти, пишет данные локально на диск и падает при каждом коротком сетевом сбое, кластер не спасёт. На практике это означает, что ваш сервис должен пережить потерю одной ноды без деградации, а обновление версии — без единого 5xx ответа. Я не раз видел, как команды удивлялись, что после добавления реплик сессии пользователей всё равно терялись — потому что sticky sessions были завязаны на IP пода, а не на общее хранилище.

Базовые паттерны отказоустойчивости

1. Несколько реплик приложения

Минимум для продакшена — не одна реплика, а несколько. Это база для высокой доступности.

Что даёт:

  • сервис продолжает отвечать, если один под умер;
  • обновление можно проводить без остановки;
  • нагрузка распределяется между экземплярами.

Практика:

  • для stateless-сервисов обычно начинают с 2–3 реплик;
  • для критичных API — больше, если хватает ресурсов и есть смысл по трафику;
  • реплики должны быть действительно независимыми.

Типовая ошибка: увеличить replicas, но оставить сессии, кэш или временные файлы только в памяти одного экземпляра. В этом случае отказоустойчивость иллюзорна. Я сталкивался с ситуацией, когда сервис аутентификации держал токены в локальном кэше, и при падении одного пода пользователи вылетали из системы, хотя остальные два работали.

2. Readiness и liveness probes

Пробы — один из самых полезных инструментов в Kubernetes.

  • livenessProbe отвечает на вопрос: «контейнер жив или завис и его надо перезапустить».
  • readinessProbe отвечает на вопрос: «можно ли уже слать трафик в этот под».

Почему это важно:

  • без readiness под может получить трафик до полной инициализации;
  • без liveness зависший процесс может оставаться в списке «живых», хотя фактически не обслуживает запросы.

Хорошая практика:

  • readiness проверяет готовность зависимостей: прогрев кэша, подключение к БД, миграции;
  • liveness должен быть простым и быстрым;
  • не делайте слишком жёсткие таймауты, иначе получите ложные рестарты.

На своём опыте: когда мы выставили initialDelaySeconds: 5 для Java-сервиса с долгим стартом, readiness постоянно фейлилась, и поды бесконечно перезапускались. Увеличили до 30 секунд — проблема ушла. Для liveness обычно хватает проверки HTTP-эндпоинта /healthz с таймаутом 1–2 секунды.

3. PodDisruptionBudget

PDB ограничивает количество подов, которое можно вывести из работы одновременно при добровольных событиях: drain ноды, обновление узла, операции кластера.

Это особенно полезно, когда:

  • реплик мало;
  • сервис чувствителен к снижению мощности;
  • есть риск одновременно потерять слишком много экземпляров.

Пример логики:

  • если у вас 3 реплики, PDB может разрешать минимум 2 доступных пода;
  • если кластерное обслуживание выведет одну ноду, сервис не останется без работающих экземпляров.

Типовая ошибка — не учитывать PDB при планировании maintenance window. Тогда обновление нод может «упереться» в ограничения и зависнуть. Я помню случай, когда ночной drain ноды застрял на два часа, потому что PDB требовал минимум 2 пода, а на ноде сидело два из трёх. Пришлось вручную снимать ограничение.

4. Распределение по нодам и зонам

Если все реплики одного сервиса сидят на одной ноде, это не отказоустойчивость, а имитация.

Используйте:

  • podAntiAffinity для разнесения одинаковых подов по разным нодам;
  • topologySpreadConstraints для равномерного распределения по зонам или узлам;
  • nodeAffinity, если определённые нагрузки нужно держать на конкретных типах нод.

Зачем это нужно:

  • сбой одной ноды не кладёт весь сервис;
  • сбой одной зоны не убивает все реплики;
  • нагрузка распределяется более предсказуемо.

В облачных кластерах я обычно задаю topologySpreadConstraints с topologyKey: topology.kubernetes.io/zone и maxSkew: 1, чтобы поды равномерно размазывались по зонам. Без этого после сбоя одной зоны можно остаться без половины сервиса.

Таблица: ключевые механизмы и зачем они нужны

Механизм Что решает Когда использовать
Несколько реплик Переживание падения пода Почти всегда для продакшена
Readiness probe Не пускать трафик в неготовый под Для любых сервисов с инициализацией
Liveness probe Перезапуск зависшего контейнера Если приложение может зависать без выхода
PodDisruptionBudget Защита от одновременного вывода слишком многих подов Для критичных сервисов
Pod anti-affinity Разнесение подов по нодам Для повышения доступности
Topology spread Распределение по узлам и зонам Для больших и геораспределённых кластеров
HPA Автомасштабирование по нагрузке Для переменного трафика
Cluster Autoscaler Добавление/удаление нод Когда ресурсов кластера может не хватать

Масштабирование: как делать это без боли

Масштабирование в Kubernetes делится на два уровня:

  • горизонтальное масштабирование подов;
  • масштабирование инфраструктуры кластера.

Горизонтальное масштабирование подов

Самый частый сценарий — увеличение числа реплик при росте нагрузки.

Для этого используют:

  • HorizontalPodAutoscaler для автоматического изменения числа подов;
  • метрики CPU/памяти;
  • кастомные метрики: RPS, latency, длина очереди, количество активных задач.

Что важно:

  • CPU не всегда отражает реальную нагрузку;
  • для API, упирающегося в БД, лучше ориентироваться на latency и очередь;
  • для воркеров полезнее считать глубину очереди, чем средний CPU.

Типовая ошибка: настраивать HPA только по CPU и удивляться, что при росте запросов сервис начинает тормозить, хотя CPU ещё не «красный». Узкое место может быть в БД, I/O или внешнем API. Я как-то разбирал инцидент, где HPA по CPU держал 4 реплики, а latency выросла до 5 секунд из-за медленного ответа внешнего сервиса. Перешли на метрику http_request_duration_seconds — и автоскейлинг заработал адекватно.

Вертикальное масштабирование

Это увеличение ресурсов на под: CPU и памяти.

Когда подходит:

  • нагрузка плохо параллелится;
  • сервису нужна большая память;
  • приложение не готово к большому числу реплик;
  • есть ограничения лицензирования или архитектуры.

Ограничение простое: вертикальное масштабирование быстро упирается в потолок ноды. Кроме того, при отказе одной крупной реплики потери заметнее, чем при нескольких небольших. Я предпочитаю горизонтальное масштабирование везде, где это возможно, а вертикальное оставляю для монолитов, которые пока не распилены.

Масштабирование самого кластера

Если подам некуда садиться, HPA уже не поможет. Здесь нужен Cluster Autoscaler или ручное добавление нод.

Это особенно важно, когда:

  • нагрузка растёт волнами;
  • в пиковые периоды кластер должен расширяться автоматически;
  • ресурсы кластера используются близко к пределу.

Проверяйте заранее:

  • хватает ли IP-адресов, если сеть это ограничивает;
  • сколько времени занимает поднятие новой ноды;
  • как быстро реально стартуют поды после масштабирования;
  • есть ли корректные requests/limits, иначе автоскейлеру будет не на чем принять решение.

В одном проекте мы столкнулись с тем, что Cluster Autoscaler добавлял ноды, но поды не могли на них запуститься, потому что в подсети кончились свободные IP. С тех пор всегда проверяю лимиты VPC перед настройкой автоскейлинга.

Requests и limits: без этого масштабирование ломается

Если у подов не заданы requests, планировщик не понимает, сколько ресурсов им реально нужно. Если слишком агрессивно задать limits, контейнеры начинают троттлиться или падать по OOM.

Практический подход

  • requests задавайте как минимально честную потребность пода;
  • limits ставьте после измерений, а не «на глаз»;
  • для memory часто лучше быть осторожнее: OOMKill в проде обычно дороже, чем умеренный запас;
  • для CPU важно помнить, что троттлинг может резко увеличить latency.

Я обычно начинаю с requests, равных среднему потреблению за неделю под нагрузкой, а limits выставляю с запасом 20–30% для памяти и без жёсткого лимита на CPU, если сервис не склонен к утечкам.

Частые ошибки

  • requests слишком низкие — поды планируются, но затем деградируют под реальной нагрузкой;
  • limits слишком жёсткие — сервис начинает сам себя душить;
  • отсутствие наблюдений за фактическим потреблением — настройки остаются случайными.

Однажды я видел, как разработчики поставили memory limit 256Mi для Python-сервиса, который на самом деле потреблял 400Mi. OOMKill происходил каждые несколько минут, и они грешили на утечку памяти, хотя проблема была в неправильном лимите.

Как сделать обновления безопасными

Продакшен Kubernetes оценивают не по тому, «поднялся ли сервис», а по тому, можно ли его обновлять без простоя.

Rolling Update

Стандартный путь — поэтапная замена старых подов новыми.

Полезные настройки:

  • maxUnavailable — сколько подов можно потерять во время обновления;
  • maxSurge — сколько новых подов можно добавить сверх текущего числа.

Для критичных сервисов важно:

  • не допускать слишком большого числа недоступных реплик;
  • убедиться, что readiness реально отсекает неготовые экземпляры;
  • проверить, что приложение корректно завершается по SIGTERM.

Я обычно ставлю maxUnavailable: 0 для сервисов, где потеря даже одного пода критична, и maxSurge: 1, чтобы новые поды запускались до удаления старых. Это гарантирует, что количество работающих экземпляров никогда не падает ниже желаемого.

Graceful shutdown

Контейнер должен завершаться аккуратно:

  • перестать принимать новые запросы;
  • завершить текущие операции;
  • закрыть соединения;
  • освободить ресурсы.

Если это не настроить, во время деплоя можно получить обрывы запросов, битые транзакции и ошибки на стороне клиентов. Практика показывает, что terminationGracePeriodSeconds стоит выставить с запасом — например, 30 секунд для быстрых API и до 60 секунд для сервисов с длинными запросами. И обязательно обрабатывать SIGTERM в коде приложения, а не просто убивать процесс.

Что делать с состоянием и данными

Kubernetes отлично подходит для stateless-нагрузки. Со stateful-сервисами нужен отдельный подход.

Если сервис хранит данные

Используйте:

  • PersistentVolumeClaim для постоянного хранилища;
  • отдельные механизмы репликации на уровне базы данных;
  • backup и restore как обязательную часть эксплуатации.

Что важно помнить:

  • PVC не делает приложение отказоустойчивым сам по себе;
  • если одна реплика зависит от локального тома, переносимость ограничена;
  • для баз данных лучше использовать специализированные операционные практики, а не надеяться только на Kubernetes.

Я обычно выношу базы данных в управляемые облачные сервисы или использую операторы вроде Zalando Postgres Operator, которые умеют управлять репликацией и бэкапами. StatefulSet с PVC — это только базовый кирпичик, но не готовая отказоустойчивая БД.

Где Kubernetes особенно силён

  • stateless API;
  • микросервисы;
  • фоновые воркеры;
  • batch-задачи;
  • ingress-слой;
  • системы, где можно быстро пересоздать экземпляр без потери данных.

Наблюдаемость: без неё отказоустойчивость не работает

Если метрик и логов нет, кластер кажется стабильным ровно до первого серьёзного инцидента.

Что нужно собирать минимум

  • метрики подов, нод и кластера;
  • количество рестартов;
  • latency запросов;
  • ошибки readiness/liveness;
  • события планировщика;
  • состояние HPA и PDB;
  • нагрузку на сеть, CPU, память и диск.

Я предпочитаю связку Prometheus + Grafana для метрик и Loki для логов. Обязательно настраиваю алерты на резкий рост рестартов и переход подов в Pending — это первые признаки надвигающейся беды.

Что удобно проверять в первую очередь

  • растёт ли число рестартов;
  • не сидят ли поды в состоянии Pending;
  • не перегружены ли конкретные ноды;
  • нет ли постоянного flapping у readiness;
  • не упёрся ли HPA в maxReplicas.

Эти пять пунктов — мой утренний чек-лист при дежурстве. Если все они в норме, можно спокойно пить кофе.

Пошаговый чек-лист для продакшена

Перед запуском сервиса

  • определить, stateful сервис или stateless;
  • задать replicas не меньше 2 для критичных сервисов;
  • прописать requests и limits;
  • настроить readiness и liveness;
  • добавить graceful shutdown;
  • проверить поведение при SIGTERM;
  • включить логи и метрики;
  • определить SLO: доступность, latency, ошибки.

Для устойчивости к сбоям

  • разнести поды по разным нодам;
  • использовать PDB;
  • проверить поведение при drain одной ноды;
  • симулировать падение одного пода;
  • проверить, что сервис не теряет трафик при rolling update;
  • протестировать зависимость от БД, Redis, очередей и внешних API.

Для масштабирования

  • выбрать метрику для HPA;
  • задать разумные пороги;
  • проверить, что кластер может добавить ноды;
  • убедиться, что requests отражают реальность;
  • провести нагрузочное тестирование до пикового трафика;
  • посмотреть, как ведёт себя система при резком росте RPS.

Типовые ошибки в продакшен Kubernetes

  • Одна реплика у критичного сервиса.
  • Отсутствие readiness probe.
  • HPA по CPU там, где узкое место совсем в другом.
  • Поды сидят на одной ноде.
  • Нет PDB, и обслуживание кластера выбивает сервис.
  • Слишком низкие memory limits.
  • Нет graceful shutdown, поэтому деплой рвёт активные запросы.
  • Метрики собираются, но на них никто не смотрит.
  • Масштабируется приложение, но не база данных.
  • Не проверяется реальное время восстановления после сбоя.

Этот список я собрал на основе собственных ночных инцидентов. Каждый пункт — реальная боль, которую можно предотвратить заранее.

Когда Kubernetes не спасает

Kubernetes не заменяет архитектуру приложения. Если:

  • одна БД без репликации является единой точкой отказа;
  • приложение не умеет переживать повторный старт;
  • критическое состояние хранится только в памяти;
  • зависимости внешние и нестабильные;
  • нет стратегии резервного копирования;

то кластер лишь быстрее и удобнее автоматизирует проблемы. Я видел, как команда мигрировала монолит в Kubernetes, но оставила файловое хранилище на локальном диске ноды — при первом же рестарте данные потерялись. Оркестратор не решает фундаментальных архитектурных просчётов.

Практическая формула надёжного продакшена

Хороший production Kubernetes обычно строится так:

  • приложение stateless или максимально близко к этому;
  • несколько реплик;
  • корректные probes;
  • разнесение по нодам и зонам;
  • PDB для критичных сервисов;
  • HPA на подходящей метрике;
  • autoscaling кластера;
  • observability с метриками и алертами;
  • проверенные сценарии обновления и отката.

Это не сложный «секретный» набор. Это дисциплина, где каждый элемент закрывает конкретный класс отказов. Если выстроить эти практики, кластер перестаёт быть источником хаоса и становится предсказуемой платформой для роста.

Вывод

Продакшен Kubernetes — это не про то, чтобы «всё работало само». Это про то, чтобы система продолжала работать, когда что-то идёт не так, и масштабировалась без ручного героизма. Отказоустойчивость строится через реплики, probes, PDB и распределение по инфраструктуре. Масштабирование — через HPA, правильно заданные requests/limits и возможность расширить сам кластер.

Если начать с базовых паттернов и проверить их нагрузкой, Kubernetes становится не источником хаоса, а предсказуемой платформой для роста. Все инциденты, которые я разбирал, сводились к игнорированию одного из этих принципов. Не повторяйте чужих ошибок — внедряйте паттерны сразу.

FAQ

Сколько реплик нужно для production?
Для критичного stateless-сервиса обычно начинают с 2–3 реплик. Точное число зависит от трафика, SLO и того, выдержит ли сервис потерю одной реплики без деградации. Я всегда проверяю: если одна реплика упадёт, успевает ли оставшаяся обработать пиковый RPS без нарушения SLO по latency.

Чем readiness отличается от liveness?
Readiness показывает, готов ли под принимать трафик. Liveness отвечает, жив ли процесс и нужен ли ему перезапуск. Проще говоря: readiness управляет попаданием в Service, liveness — жизненным циклом контейнера. Если readiness фейлится, трафик перестаёт идти, но под не убивают. Если liveness фейлится, kubelet перезапускает контейнер.

Почему HPA по CPU часто не хватает?
Потому что реальное узкое место может быть в БД, очереди, памяти или внешнем API. CPU не всегда отражает пользовательскую деградацию. Например, сервис может простаивать в ожидании ответа от медленной базы, и CPU будет низким, а latency — высокой. Поэтому для HPA лучше использовать метрики, приближенные к пользовательскому опыту: latency, количество запросов в очереди, ошибки.

Нужен ли PodDisruptionBudget всем сервисам?
Нет, но для критичных сервисов PDB очень полезен. Он защищает от одновременного вывода слишком большого числа подов при обслуживании кластера. Если сервис может безболезненно пережить временное снижение числа реплик, PDB не обязателен. Но для всего, что напрямую влияет на пользователей, я ставлю PDB с minAvailable не менее 50% от replicas.

Можно ли считать Kubernetes отказоустойчивым без репликации приложения?
Нет. Если работает только один экземпляр, Kubernetes сможет его перезапускать, но не обеспечит настоящую доступность при сбое. Рестарт занимает время, и в этот период сервис недоступен. Отказоустойчивость начинается с нескольких реплик, разнесённых по разным нодам.

Что тестировать в первую очередь перед продакшеном?
Падение одного пода, drain одной ноды, rolling update, резкий рост трафика и поведение сервиса при потере ключевой зависимости. Я обычно провожу game days: имитирую отказ ноды, обрыв сети до БД, скачок RPS в 3 раза выше ожидаемого. Это вскрывает слабые места до того, как они ударят по пользователям.