December 22

Как мы сократили расходы на Kubernetes примерно на 78% без единой секунды простоя

Это перевод оригинальной статьи How We Cut Kubernetes Costs by ~78% Without a Second of Downtime.

Подписывайтесь на телеграм-канал usr_bin, где я публикую много полезного по Linux, в том числе ссылки на статьи в этом блоге.

Краткая информация: Мы обнаружили значительный избыток ресурсов в нашем боевом кластере Kubernetes — 41 узел, в среднем потребляющий около 3% ресурсов CPU и около 4% памяти, — и перешли на меньшие, более плотные рабочие пулы. За две недели мы сократили количество узлов с 41 до 17, снизили ежемесячные расходы на инфраструктуру с 61,5 тыс. долларов США до 13,5 тыс. долларов США и обеспечили годовую экономию в размере 576 тыс. долларов США — и всё это без простоев.

Почему это важно (проблема на 500 тысяч долларов)

Казалось, всё в порядке: сервисы работали стабильно, пользователи ничего не подозревали. Но плановая проверка ёмкости показала неприятную правду:

  • 41 рабочий узел с загрузкой CPU 2–7%
  • Сервера на 32 ядра / 128 ГБ, способные запускать сотни контейнеров, в основном простаивали
  • Ежемесячный счет за инфраструктуру: ~61 500 долларов США
  • Годовой перерасход: ~576 000 долларов США (с учётом показателей после миграции)

Это не системная проблема — это финансовая проблема, замаскированная под инженерную инертность.

Ключевая мысль: метки, а не имена пулов, управляют планированием

Мы проверили, как распределяются рабочие нагрузки, и выяснили, что ни одно Deployment-развертывание не использует nodeSelector по имени пула облачных узлов. Вместо этого они использовали метки узлов кластера, например:

nodeSelector:
  workload-type: compute-intensive

Поскольку Kubernetes осуществляет планирование по меткам узлов, мы могли создавать новые пулы с идентичными метками, очищать старые узлы, и поды автоматически перенаправлялись на новые узлы нужного размера. Это открытие открыло безопасный и обратимый путь миграции.

Варианты миграции, которые мы рассмотрели

  1. Обновления рабочих процессов на месте — подходит для обновлений ОС/версий, но не позволяет менять размер ноды → отклонено.
  2. Blue-Green миграция пулов (выбрано) — создать новые меньшие пулы, постепенно зачистить старые ноды, проверять работу и удалять старый пул. Безопасный откат, небольшой временный рост стоимости.
  3. Пересборка кластера — чисто, но рискованно и требует простоя → отклонено.

Мы выбрали blue-green как оптимальное сочетание скорости и безопасности.

Реализация: 7 пулов за 2 дня (без простоев)

Высокоуровневый процесс для каждого пула:

  1. Создайте новый пул с меньшим размером (например, 32×128 → 16×64 ) и теми же метками рабочей нагрузки.
  2. Проверьте, что DaemonSet’ы и системные поды корректно размещаются на новых узлах.
  3. Выводите из работы (cordon) и зачищайте старые узлы по одному(kubectl drain … --ignore-daemonsets --grace-period=300).
  4. Убедитесь, что нет Pending/Failed подов и что PVC корректно переподключаются(повторное подключение хранилища обрабатывается облачным блочным хранилищем).
  5. Удалите старый пул сразу после проверки.

Результаты по репрезентативным этапам:

  • Этап 1 (с большим количеством DaemonSet): 12 → 3 узла → экономия 15 000 долларов в месяц
  • Этап 2 (смешанный): 9 → 3 узла → экономия 10 000 долларов в месяц
  • Этапы с тем же количеством узлов, но вдвое меньшими по размеру: дополнительная экономия в размере 1–3 тыс. долларов в месяц на каждом этапе.

На всех этапах:

  • Общее количество узлов: 41 → 17 (сокращение на 59%)
  • Ежемесячные расходы: 61 500 долларов → 13 500 долларов (снижение на 78%)
  • Ежегодная экономия: 576 000 долларов США
  • Средняя загрузка CPU: 3% → 15% (улучшение в 4 раза)
  • Средний объем памяти: 4% → 18% (улучшение в 4 раза)
  • ROI: 576 тыс. долларов США ежегодной экономии ÷ 22 человеко-часа ≈ 26 тыс. долларов США экономии на каждый вложенный час.

Состояние, хранилище и безопасность: на что обращать внимание

  • StatefulSets и PVC: современное облачное блочное хранилище автоматически переподключает тома. Используйте увеличенное время остановки (--grace-period=300), чтобы обеспечить корректное завершение работы и отключение. Мы перенесли более 20 PVC без сбоев.
  • DaemonSets: они запускаются один раз на каждом узле — чем меньше узлов, тем меньше меньше daemon-подов и тем больше экономия средств. Пример: 17 DaemonSets × 12 узлов = 204 пода против 51 пода на 3 узлах; значительное снижение накладных расходов.
  • Стратегия зачистки: зачищайте по одному узлу за раз, делайте паузу между операциями (мы использовали 120 секунд) и проверяйте работоспособность кластера после каждого отключения. Одновременная зачистка нескольких узлов перегружает планировщик.
  • Соответствие меток: новые узлы обязаны иметь те же метки, на которые нацелены поды, иначе поды не окажутся там, где вы ожидаете.

Практическая автоматизация (скрипты, которые мы использовали)

Мы автоматизировали анализ и выполнение, благодаря чему решения принимались на основе данных и были воспроизводимыми.

Анализ пулов (в общих чертах):

# identify pools and per-node utilization
kubectl top nodes -l pool=<pool-name>
# find which workloads are DaemonSets / StatefulSets / Deployments
kubectl get pods --all-namespaces -o json | jq ...

Безопасный скрипт зачистки (концепция):

kubectl drain <node> --ignore-daemonsets --delete-emptydir-data --grace-period=300 --timeout=600s
# validate: no Pending/Failed pods; PVCs bound; new nodes healthy

Автоматизация этапа анализа позволила сократить 4 часа ручной работы до 5 минут и уменьшить количество человеческих ошибок.

10 кратких уроков (чтобы вы не повторяли наших ошибок)

  1. Имена пулов не имеют значения — важны метки. Метки применяются ко всему кластеру.
  2. Начните с простого. Сначала перенесите пулы, состоящие только из DaemonSet.
  3. Используйте увеличенные grace-периоды (300 секунд) для состояния.
  4. Анализируйте загрузку каждого узла, а не средние показатели по пулу. «Горячие» узлы могут скрываться в низких средних показателях пула.
  5. DaemonSets увеличивают потери на многих крупных узлах — консолидация помогает.
  6. Автоматизировать оценку рисков (PVC, taints, nodeAffinity, стоимость).
  7. Очищайте один узел за раз с небольшими перерывами между ними, чтобы избежать перегрузки планировщика.
  8. Перед миграцией PVC проверьте параметры класса хранилища (динамическое выделение ресурсов, режим привязки).
  9. Удаляйте старые пулы сразу после проверки — неработающие узлы стоят денег.
  10. Поддерживайте ежемесячный контроль использования и автоматические оповещения, чтобы избежать регресса.

Цифры — еще раз (потому что они имеют значение)

22 человеко-часа. Экономия 576 тыс. долларов в год. 0 простоев.

🧩 Как начать

  1. Запустите kubectl top nodes по всем кластерам.
  2. Определите пулы с загрузкой CPU <20%.
  3. Убедитесь, что рабочие нагрузки используют метки, а не имена пулов.
  4. Создайте меньший пул с соответствующими метками.
  5. Поочередно зачищайте узлы и проверяйте состояние.
  6. Удалите старые пулы после миграции.
  7. Настройте ежемесячные проверки использования.

🧠 Заключительная мысль

Инфраструктурные потери часто прячутся из виду. Решение — не новые инструменты, а измерение, планирование и правильное масштабирование.

Если ваш кластер использует меньше 20% CPU, вы почти наверняка переплачиваете.
Мы сэкономили 576 тысяч долларов в год, просто спросив:

  1. «Какова средняя загрузка CPU?» → 3%
  2. «Когда эти узлы последний раз правильно подбирали по размеру?» → Никогда
  3. «Платим ли мы за неиспользуемые ресурсы?» → Определённо.

На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.