Всем привет! Продолжаю делиться наработками по 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). Если у вас есть похожие наработки, то поделитесь ими в комментариях, интересно сравнить подходы.

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