Spot Management — preemptible-ноды без ложных гарантий

Oopps.ai помогает перенести подходящие stateless workloads на более дешёвые preemptible-ноды. Эта функция снижает стоимость, но сама по себе не гарантирует непрерывную доступность приложения.

Тёплый резерв проверен для миграции, но не заменяет ваш SLO-тест

По умолчанию Oopps сохраняет одну фактически Ready on-demand ноду. Для явно отмеченного stateless Deployment можно дополнительно держать одну service-visible warm replica. В строгом isolated live-E2E тесте продуктовая Spot-конверсия прошла без ошибок и таймаутов в двух независимых probes. Это не гарантирует бесшовность любого приложения: свой SLO подтверждайте external и internal probes в собственном fault-test.

Обзор

Preemptible (spot) ноды в Yandex Cloud обычно дешевле on-demand, но провайдер может остановить их в любой момент. Spot Management анализирует workloads на каждой on-demand node group, показывает причины несовместимости и по подтверждению создаёт preemptible-клон группы.

Как spot участвует в масштабировании

Это не разовая замена VM и не список доступных инстансов. После подтверждения безопасной node group Oopps создаёт её preemptible-клон и копирует в него действующие правила autoscaling. После переноса исходная on-demand группа по умолчанию сохраняется с минимальным или фиксированным размером 1, а product apply ждёт физическую Ready ноду. Размер резерва можно изменить в настройках; значение 0 означает явный холодный резерв.

Как устроен резерв в одном кластере

Один Kubernetes-кластер
├── workers-spot       1…N preemptible-нод, обслуживают основную нагрузку
└── workers            1 on-demand нода по умолчанию
    └── api-oopps-fallback   1 opt-in service-visible replica

Почему «+1 on-demand нода» не равна полному резерву

Monitor при длительном отсутствии всех Ready Spot-нод пытается один раз увеличить размер парной on-demand группы на заданный шаг (по умолчанию на одну ноду сверх тёплого минимума). Если одновременно потеряно несколько Spot-нод, ёмкости может не хватить по CPU, памяти, зоне или ограничениям scheduler. Monitor также не обещает автоматическое возвращение на Spot после восстановления.

Анализ кандидатов

Для каждой on-demand node group платформа проверяет все workloads на нодах:

Критерии spot-safe workload

  • Только Deployments — StatefulSet и другие контроллеры блокируют рекомендацию; DaemonSet-поды при проверке игнорируются.
  • replicas > 1 — Deployment должен иметь избыточность. Это фильтр кандидатов, а не доказательство, что реплики фактически размещены на разных нодах или зонах.
  • Не system namespaces — kube-system, kube-public, kube-node-lease исключены.
  • Нет local PV — hostPath и local Persistent Volumes привязаны к конкретной ноде, данные будут потеряны.
Всё или ничего

Если все проверяемые workloads в node group spot-safe — группа получает рекомендацию. Если хотя бы один workload unsafe — вся группа пропускается. Непосредственно перед cloud mutation live-preflight повторяет проверку и останавливает устаревшую или опасную рекомендацию.

Unsafe Reasons

Каждая причина, по которой workload не подходит для spot, отображается в UI:

Причина Описание
StatefulSet Workload с состоянием, привязан к конкретным PV
Single replica Только 1 реплика, нет избыточности
Local PV Использует hostPath или local Persistent Volume
System namespace Workload в kube-system или другом системном namespace

Что происходит после «Включить Spot»

Шаг 1 — Fetch

Получение текущей конфигурации on-demand node group из Yandex MKS API.

Шаг 2 — Live-preflight

Повторная проверка фактических source nodes, stateless workloads и ограничений warm fallback. На этом шаге облачные ресурсы ещё не меняются.

Шаг 3 — Тёплый резерв до миграции

Для opt-in workload Oopps сначала создаёт отдельную on-demand replica и ждёт, пока она станет Ready и ready endpoint в EndpointSlice каждого подходящего Service. Реплика входит в тот же PDB selector, поэтому Deployment с двумя replicas и minAvailable: 2 получает безопасный budget для одной миграции. Без этого подтверждения drain не начинается.

Шаг 4 — Create

Создание preemptible клона node group с schedulingPolicy.preemptible = true. Имя новой группы: {old_name}-spot. Все остальные параметры (тип VM, scalePolicy, labels, taints) копируются из оригинала. Поэтому autoscaling продолжает работать по тем же правилам, но на spot-нодах.

Шаг 5 — Wait

Ожидание статуса RUNNING у preemptible группы. Таймаут — 600 секунд.

Шаг 6 — Drain

Ноды on-demand группы обрабатываются строго по одной, а внутри ноды Pod выселяются по одной: cordon, evict через Kubernetes Eviction API с уважением PodDisruptionBudget, ожидание ухода старой Pod и готовности replacement. Тёплая replica исключается из drain. Только затем платформа переходит к следующей Pod.

Шаг 7 — Сохранение резерва

Исходная on-demand node group не удаляется: её минимальный (или fixed) размер по умолчанию устанавливается в 1. Oopps ждёт физического схождения до одной Ready ноды, подтверждает её соответствие переносимому nodeSelector workload и делает её schedulable. Затем Oopps повторно проверяет, что уже созданная тёплая replica всё ещё Ready и присутствует в EndpointSlice каждого подходящего Service; одного статуса Available недостаточно.

Что сейчас делает Health Monitor

Backend периодически проверяет состояние preemptible-групп. Это наблюдение и реактивная попытка восстановления, а не механизм высокой доступности.

Проверка каждые 60 секунд

Для каждой preemptible node group платформа подсчитывает количество нод в статусе NotReady.

Нормальный preemption

Если часть нод NotReady, платформа логирует состояние и ждёт. Fallback рассматривается только когда ожидаемая ёмкость Spot больше нуля, а Ready-нод в группе нет вообще.

Реализованная попытка fallback

Если ожидаемая preemptible-ёмкость больше нуля и Ready-нод нет (все NotReady или ноды отсутствуют) непрерывно более 5 минут, monitor пытается увеличить парную on-demand группу на один настроенный шаг.

Пятиминутный интервал отсчитывается по сохранённому состоянию мониторинга, поэтому перезапуск сервиса не сбрасывает защиту. Для одной непрерывной аварии fallback выполняется не более одного раза и записывается как action_kind = spot_fallback. Наличие записи говорит о выполненной операции scale up, но не подтверждает, что все поды уже Ready.

Не превращайте настройку в неподтверждённое обещание

Ready-нода и Available replica подтверждают резерв ёмкости, но не ноль ошибок конкретного Service: на результат влияют endpoint propagation, readiness, connection draining и клиентские таймауты. Для критичной нагрузки выполните fault-test с непрерывными probes без retry и зафиксируйте собственный SLO.

Как подготовить workloads

  1. Оставьте системные компоненты, stateful-сервисы и критичные одиночные экземпляры на on-demand нодах. Автоматическая конвертация группы не создаёт такой смешанный baseline за вас.
  2. Для stateless Deployment задайте минимум две реплики и проверьте topologySpreadConstraints или pod anti-affinity. Одного replicas: 2 недостаточно, если обе реплики попали на одну ноду.
  3. Настройте корректные CPU/memory requests, readiness probe и время старта. Иначе scheduler не сможет предсказуемо разместить поды после прерывания.
  4. Добавьте PodDisruptionBudget, но помните: PDB помогает при добровольном drain и не может запретить облаку внезапно остановить VM.
  5. Проведите собственный fault-test: остановите тестовую Spot-ёмкость, измерьте ошибки и время восстановления, проверьте Pending-поды и события. Не переносите критичную нагрузку только по отметке «Можно включить».

Экономика и выбор режима

СхемаСтоимостьЧто ожидать
Spot + warm capacity (по умолчанию) Одна обычная VM оплачивается постоянно Первая on-demand ёмкость уже Ready; opt-in replica может сразу участвовать в Service
Spot + cold reserve (fallback_min_size: 0) Максимальная экономия в штатном режиме Холодное восстановление; warm replica недоступна
Только on-demand Выше стоимость Подходит, если прерывания и холодный старт неприемлемы
Как включить opt-in warm replica

Включите spot_management.warm_fallback.enabled в настройках и добавьте исходному Deployment annotation oopps.ai/spot-warm-fallback: enabled. Нужны минимум две replicas, matching PDB и отсутствие PVC/hostPath, init containers, host namespaces, жёсткого nodeName, affinity или привязки к конкретной node group. Переносимый nodeSelector допускается, только если ему соответствует retained on-demand capacity. Нарушение блокирует apply до изменений.

Подтверждённый strict SLO в тестовой среде

В isolated YC live-E2E на stateless Deployment с PDB minAvailable: 2 Oopps выполнил product-controlled Spot-конверсию: сохранил тёплую on-demand replica в Service, перенёс primary Pod'ы и оставил ready on-demand capacity. В marker-bounded SLO-окне external probe дал 220/220, internal Service-DNS — 145/145; retries, ошибки и таймауты отсутствовали. Это evidence для данной миграции, а не публичная гарантия на полную физическую потерю Spot capacity или произвольную конфигурацию workload.

Что дальше?