Как мы сократили расходы на 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 осуществляет планирование по меткам узлов, мы могли создавать новые пулы с идентичными метками, очищать старые узлы, и поды автоматически перенаправлялись на новые узлы нужного размера. Это открытие открыло безопасный и обратимый путь миграции.
Варианты миграции, которые мы рассмотрели
- Обновления рабочих процессов на месте — подходит для обновлений ОС/версий, но не позволяет менять размер ноды → отклонено.
- Blue-Green миграция пулов (выбрано) — создать новые меньшие пулы, постепенно зачистить старые ноды, проверять работу и удалять старый пул. Безопасный откат, небольшой временный рост стоимости.
- Пересборка кластера — чисто, но рискованно и требует простоя → отклонено.
Мы выбрали blue-green как оптимальное сочетание скорости и безопасности.
Реализация: 7 пулов за 2 дня (без простоев)
Высокоуровневый процесс для каждого пула:
- Создайте новый пул с меньшим размером (например, 32×128 → 16×64 ) и теми же метками рабочей нагрузки.
- Проверьте, что DaemonSet’ы и системные поды корректно размещаются на новых узлах.
- Выводите из работы (cordon) и зачищайте старые узлы по одному(
kubectl drain … --ignore-daemonsets --grace-period=300). - Убедитесь, что нет Pending/Failed подов и что PVC корректно переподключаются(повторное подключение хранилища обрабатывается облачным блочным хранилищем).
- Удалите старый пул сразу после проверки.
Результаты по репрезентативным этапам:
- Этап 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 кратких уроков (чтобы вы не повторяли наших ошибок)
- Имена пулов не имеют значения — важны метки. Метки применяются ко всему кластеру.
- Начните с простого. Сначала перенесите пулы, состоящие только из DaemonSet.
- Используйте увеличенные grace-периоды (300 секунд) для состояния.
- Анализируйте загрузку каждого узла, а не средние показатели по пулу. «Горячие» узлы могут скрываться в низких средних показателях пула.
- DaemonSets увеличивают потери на многих крупных узлах — консолидация помогает.
- Автоматизировать оценку рисков (PVC, taints, nodeAffinity, стоимость).
- Очищайте один узел за раз с небольшими перерывами между ними, чтобы избежать перегрузки планировщика.
- Перед миграцией PVC проверьте параметры класса хранилища (динамическое выделение ресурсов, режим привязки).
- Удаляйте старые пулы сразу после проверки — неработающие узлы стоят денег.
- Поддерживайте ежемесячный контроль использования и автоматические оповещения, чтобы избежать регресса.
Цифры — еще раз (потому что они имеют значение)
22 человеко-часа. Экономия 576 тыс. долларов в год. 0 простоев.
🧩 Как начать
- Запустите
kubectl top nodesпо всем кластерам. - Определите пулы с загрузкой CPU <20%.
- Убедитесь, что рабочие нагрузки используют метки, а не имена пулов.
- Создайте меньший пул с соответствующими метками.
- Поочередно зачищайте узлы и проверяйте состояние.
- Удалите старые пулы после миграции.
- Настройте ежемесячные проверки использования.
🧠 Заключительная мысль
Инфраструктурные потери часто прячутся из виду. Решение — не новые инструменты, а измерение, планирование и правильное масштабирование.
Если ваш кластер использует меньше 20% CPU, вы почти наверняка переплачиваете.
Мы сэкономили 576 тысяч долларов в год, просто спросив:
- «Какова средняя загрузка CPU?» → 3%
- «Когда эти узлы последний раз правильно подбирали по размеру?» → Никогда
- «Платим ли мы за неиспользуемые ресурсы?» → Определённо.
На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.