Как ты обеспечиваешь отказоустойчивость в приложениях на уровне инфраструктуры и конфигурации?

Ответ

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

1. Репликация и распределение подов:

  • Всегда задаю replicas: >1 для Deployment'ов критичных сервисов.
  • Использую topologySpreadConstraints для равномерного распределения подов по зонам и нодам, что защищает от падения одной зоны или ноды.
    spec:
    topologySpreadConstraints:
    - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: backend

2. Проверки жизнеспособности (Probes):

  • Liveness Probe: Определяет, когда контейнер нужно перезапустить. Например, если HTTP-эндпоинт /health не отвечает 200 OK.
  • Readiness Probe: Определяет, когда под готов принимать трафик. Поды, не прошедшие проверку, удаляются из балансировщика нагрузки Service.
  • Startup Probe: Для "медленно" стартующих приложений (Java), даёт им время на запуск перед началом liveness/readiness проверок.
    livenessProbe:
    httpGet:
    path: /healthz
    port: 8080
    initialDelaySeconds: 30
    periodSeconds: 10
    readinessProbe:
    httpGet:
    path: /ready
    port: 8080
    initialDelaySeconds: 5
    periodSeconds: 5

3. Автомасштабирование:

  • Настраиваю Horizontal Pod Autoscaler (HPA) на основе CPU, памяти или кастомных метрик из Prometheus (например, RPS).
  • Использую Cluster Autoscaler, который автоматически добавляет ноды в кластер, если поды не могут быть размещены из-за нехватки ресурсов.

4. Стратегия деплоя:

  • Использую стратегию rolling update с maxSurge и maxUnavailable для бесшовного обновления без простоя.
  • Для критичных приложений настраиваю blue-green или canary деплой через инструменты вроде Flagger и Istio, что позволяет быстро откатиться при проблемах.

5. Зависимости и graceful shutdown:

  • Настраиваю terminationGracePeriodSeconds (обычно 30+ секунд) и реализую в приложении обработку сигнала SIGTERM для корректного завершения работы: завершения соединений с БД, обработки текущих запросов, снятия с балансировки.
  • Для stateful-приложений (БД) использую StatefulSet с привязкой к устойчивым томам (Persistent Volumes) и настраиваю репликацию на уровне самого приложения (например, Patroni для PostgreSQL).

Ответ 18+ 🔞

А, ну ты про то, как чтобы сервис не ложился как подстреленный, когда что-то пошло не так? Ёпта, это целая наука, конечно. Ну, слушай, я тебе на пальцах, как я это обычно делаю, а то некоторые думают, что просто три реплики запихнуть — и всё, пизда рулю.

1. Реплики и где их тыкать. Первое и очевидное — это чтобы одного инстанса не было. replicas: 1 — это верный путь в ад, чувак. Один под упал — и сервис накрылся медным тазом. Ставлю минимум 2, а лучше 3. Но это пол-дела. Главное — раскидать их так, чтобы если целая зона в облаке наебнулась, у тебя хоть кто-то выжил. Для этого есть topologySpreadConstraints. Смотри, как я это настраиваю:

spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: backend

Короче, эта штука говорит: «Распределяй поды равномерно по зонам, не скучковай всё в одном месте». И если равномерно не получается — лучше вообще не создавай новый под (DoNotSchedule). Иначе получится, что все твои реплики висят на одной хлипкой ноде, которая потом чихнет — и тебе волнение ебать, а сервиса нет.

2. Пробы — чтобы знать, кто живой, а кто уже нет. Это, блядь, основа основ. Без проб — ты слепой.

  • Liveness Probe: Это чтоб понять, когда контейнер совсем помер и его надо пристрелить и переродить. Например, если эндпоинт /health пять раз подряд ответил хуём, а не 200 OK — значит, пора перезапускать. Сам от себя охуеешь, как часто это спасает.
  • Readiness Probe: А это чтоб понять, когда под уже вроде запустился, но ещё не готов работать. Скажем, подключение к базе установил? Конфиги прочитал? Если нет — то его выкидывают из-за балансировщика, и трафик на него не идёт. Крайне важно, чтобы один кривой под не положил весь сервис.
  • Startup Probe: Это для тех, кто стартует долго, как та же Java. Даёшь ей время на раскачку, прежде чем начинать тыкать liveness-пробами. А то она ещё JVM разогревает, а ты её уже убиваешь за неотвечающий /health — и так по кругу, ебать колотить.

Вот как это выглядит в деле:

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30 # Даём 30 секунд на старт, не трогаем
  periodSeconds: 10 # А потом тыкаем раз в 10 секунд
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5 # readiness проверяем почти сразу
  periodSeconds: 5 # и почаще

3. Автомасштабирование — чтобы не орать «Всё упало!» в пятницу вечером. Тут два слона:

  • HPA (Horizontal Pod Autoscaler): Он смотрит на метрики — загрузку CPU, память, или, что круче, на кастомные метрики из Prometheus (например, запросов в секунду). Видит, что нагрузка растёт — создаёт новые поды. Упала — убивает лишние. Главное, лимиты на ресурсы выставить адекватные, а то он начнёт плодить их овердохуища.
  • Cluster Autoscaler: Это магия поинтереснее. Бывает, HPA хочет создать новые поды, а свободных ресурсов в кластере — нихуя. Вот тут Cluster Autoscaler смотрит на это безобразие и говорит облачному провайдеру: «Э, сабака сука, дай-ка нам ещё одну виртуалку в кластер!». И нода добавляется. Красота.

4. Как выкатывать новую версию, чтобы не обосраться. kubectl apply -f и молиться — это не стратегия, это пиздопроебибна.

  • По умолчанию все используют rolling update. Тут важно поиграть с maxSurge и maxUnavailable. maxSurge: 25% — значит, можно временно создать на четверть больше подов сверх желаемого количества, чтобы было куда новую версию ставить. maxUnavailable: 0 — значит, вообще ни один под не должен стать недоступным во время обновления. Идеально для критичных сервисов, но дольше.
  • Для совсем уж параноиков и сложных систем — blue-green или canary деплой. Заводишь себе Istio с Flagger'ом. Выкатываешь новую версию параллельно со старой, направляешь на неё 1% трафика, смотришь метрики (ошибки, latency). Если всё ок — увеличиваешь процент. Если пошла херня — одним движением возвращаешь весь трафик на старую версию. Откат за секунды. Доверия к процессу — ебать ноль, но зато надёжно.

5. Грамотное завершение работы и stateful-штуки. Самое обидное — когда поду говорят «умирай», а он берёт и обрывает все соединения к базе нахуй, не доделав запросы. Клиенты в шоке.

  • Поэтому настраиваю terminationGracePeriodSeconds (ставишь 30-45 секунд, не жадничай). И в самом приложении обязательно ловлю сигнал SIGTERM от Kubernetes. Уведомляю балансировщик, что я больше не готов, заканчиваю обрабатывать текущие запросы, аккуратно закрываю соединения с БД и кэшами, и только потом разрешаю себе умереть. Это называется graceful shutdown.
  • Ну и для stateful-вещей вроде баз данных — тут вообще отдельная песня. Используешь StatefulSet, который гарантирует порядок и устойчивые имена подов. Диски (Persistent Volumes) цепляешь, чтобы данные не потерялись при переезде пода. А репликацию и фейловер настраиваешь уже на уровне самого приложения (тот же Patroni для PostgreSQL). Kubernetes тут может помочь с оркестрацией, но логику «кто мастер, а кто реплика» он за тебя не сделает.

Вот, короче, такой подход. Не сказать, что на 100% от всего защищает, но от большинства стандартных косяков — спасает, да. Главное — не быть распиздяем и думать головой, э бошка думай.