Всем привет! Продолжаю делиться наработками по Yandex Cloud, которые мы успешно реализовали в Спортсе”. Ранее я писал о Gitlab-раннерах и fleet-плагине, а также об утилите ycqouter. В этой статье расскажу о том, как значительно сократить расходы на manage k8s путем внедрения "прерываемых" нод без потери производительности и головной боли.

Как и большинство компаний мы активно используем kubernetes, а поскольку находимся в Облаке, то для удобства перешли на management k8s. После полного переезда именно куб стал основной статьей расхода в биллинге Яндекс Облако. Поэтому стало актуально найти способ снизить стоимость без потери в производительности.

Первый подход к решению проблемы

Самое очевидное решение лежало на поверхности – использовать "прерываемые" виртуальные машины, которые стоят до 70% дешевле. Но как мы знаем, их отличает один нюанс – они обязательно выключаются раз в сутки. Эта особенность казалась не критичной, ведь в managed k8s ноды запускаются в рамках instance group, а они умеют самостоятельно запускать "прерываемые" ВМ. Казалось бы, радость близко, но все разбилось о реальность.

После первых попыток мы выяснили, что если создать instance group из прерываемых машин, то в какой-то момент они всей толпой уходят в "отключку", а это влечет за собой сбои в работе кластера целиком. Получается, что в момент, когда большая часть нод уходит в офлайн, множество подов не могут разместиться из-за нехватки ресурсов. В дополнение к этому появляется большое количество подов в статусе Unknown и Pending. Это связано с тем, что "прерываемые" виртуальные машины выключаются без предупреждения и k8s не успевает корректно пересоздать поды на других нодах.

Переход к своему оператору

Мы попробовали несколько подходов с использованием стандартных средств Облака, но ничего не давало нужной стабильности. В итоге мы решили взять на себя остановку и пересоздание "прерываемых" нод. Результатом работы стал k8s-оператор, который и позволил нам перейти на "прерываемые" виртуальные машины и не нести потери в производительности.

Общий принцип работы достаточно прост, но при этом максимально надежен.

  1. Оператор отслеживает все ноды и собирает те, которые имеют определенные лейблы, в нашем случае yandex.cloud/preemptible: "true" и составляет список.

  2. Следующим шагом выбирает ноду, которую нужно вывести из нагрузки, основываясь на трех параметрах:
    Максимальное время жизни (21 час)
    Минимальное время жизни (12 часов)
    Окно обслуживания (с 00:00 до 07:00)

    В самом простом сценарии мы выводим ноду, которая живет больше 21 часа. Однако это может вылиться в то, что в рабочее время большое количество нод будут перезапускаться. Поэтому было добавлено "окно обслуживания" – с его помощью ночью мы пересоздаем большую часть кластера в относительно спокойное время.

  3. После того как мы определились с нодой, которая должна следующей уйти из кластера, мы навешиваем на нее cordon. Таким образом на нее больше не будут планироваться новые поды. Далее оператор начинает выселять(evicted) все поды по очереди и ждать, когда нода станет совсем пустой (за исключением служебной нагрузки).

  4. Все остальное мы отдаем на откуп Cluster autoscaling – он видит, что нода больше не используется и удаляет ее, а при необходимости создает новую, свежую ноду.

Кстати, побочным и очень приятным эффектом является уплотнение кластера. В процессе evicted подов cluster scheduler распределяет поды по нодам и только после этого создает новые.

Все это оператор делает непрерывно и понемногу обновляет все "прерываемые" ноды. Именно благодаря этому мы в один момент теряем не более одной ноды, а плавный вывод подов почти не влияет на производительность приложений в кластере.

Ограничения и рекомендации

Есть несколько важных моментов, которые стоит учитывать:

  • Лучше всего переносить на прерываемые ноды deployment'ы с несколькими репликами и сервисы без состояния.

  • Нежелательно размещать на них базы данных, очереди, stateful-сервисы – им нужна выделенная группа обычных ВМ.

  • Размер instance group нужно увеличить. Поскольку между cordon и удалением ноды сluster autoscaling может пройти порядка 30 минут, нам нужен запас. К примеру, если раньше у нод-группы был максимальный размер в 15 виртуальных машин, то его придется увеличить до 17-18.

Результат

И конечно всем интересно, а сколько же можно сэкономить с таким подходом. Приведем небольшую табличку до и после.

До использования оператора:

Характеристики

Прерываемая

Количество

Цена

NodeGroup 1

vCPU 8, RAM 16

No

15

169 125

NodeGroup 2

vCPU 24, RAM 48

No

1

33 794

202 919

После использования оператора:

Характеристики

Прерываемая

Количество

Цена

NodeGroup 1

vCPU 8, RAM 16

No

3

33 825

NodeGroup 2

vCPU 8, RAM 16

Yes

15

43 155

NodeGroup 2

vCPU 24, RAM 48

No

1

33 794

110 774

Как видно, мы сократили расходы на кластер почти на 50%, что очень хорошо с учетом того, что у нас далеко не один такой кластер. Но и на этом мы останавливаться не собираемся. В планах есть улучшения, например, автоматическая перебалансировка и подбор нод (аля-karpenter). Если у вас есть похожие наработки, то поделитесь ими в комментариях, интересно сравнить подходы.

Комментарии (7)


  1. zVlad909
    17.12.2025 18:18

    Меня умиляет стремление иметь много виртуальных машин и много узлов (node - узел, утолщение) и стручков (pod - стручок, кокон, подвеска). Слово cordon можно писать и кирилицей - кордон, есть такое слово в русском языке.

    Хотелось бы услышать объянение этого стремления от практикующего специалиста. .


    1. 0x00FA7A55
      17.12.2025 18:18

      Казалось бы, что лучше один стручок, но как у коня, но всё-таки когда их N+1, как-то поспокойнее. А то вдруг не поднимется, или чего хуже, упадёт в самый неподходящий момент.

      мимо практикующий специалист


      1. zVlad909
        17.12.2025 18:18

        А что Вы думаете об издержках управления всей этой многоузловой и многостручковой конфигурацией?

        Подскажу,: это не бесплатно.

        А в целом ответ Ваш принимается. Да на таких ненадежных платформах как х86 и облако единственный выход это создавать софтверную избыточность и пытаться ее поддерживать через неизбежные издержки. Когда выигрываешь в одном обязательно проигрываешь в чем то другом.

        В гарантированных прибылях при этом остаются только поставщики базового обеспечения (Кубернетес) и облачных сервисов.


  1. HWTH
    17.12.2025 18:18

    Спасибо за статью. После прочтения появилось несколько вопросов:
    1. Яндекс выключает прерываемую ноду не только раз в сутки, но и по своему усмотрению, когда потребуются ресурсы. Выключает он посылая сигнал shutdown на ноду, без предварительного уведомления. Вы как-то пытались победить?
    2. Что вы будете делать, если в зоне закончатся ресурсы для прерываемых нод?


    1. GoooodBoy Автор
      17.12.2025 18:18

      1. Да, прерываемая ВМ может быть выключена в любое время, по этому пока используем в тестовых кластерах. Но за несколько месяцев использования еще не было ситуации чтобы это повлияло на работу кластера. И да, сигнал посылается и его даже можно отследить, то он отправляется за 30 секунд до отключения, так что толком ничего успеть не получится.

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


  1. freelook27
    17.12.2025 18:18

    спасибо за статью! хорошая идея. мы настроили бота, который выключает все наши тестовые managed сервисы на ночь и на выходные, с интеграцией в mattermost, чтобы в случае чего, если тестировщику надо поработать, чтобы он отменил остановку или запустил все сервисы одной командой.


    1. GoooodBoy Автор
      17.12.2025 18:18

      Да, мы тоже такой подход используем для ВМ разработчиков и части инфровых ВМ.