В статье рассматривается подход к снижению энергозатрат Kubernetes-кластера путём динамической подстройки ресурсов под реальный профиль нагрузки. Описан опыт внедрения системы сбора показателей энергопотребления, построения модели потребления сервисов и разработки «умного» контроллера на Go. Приведены примеры кода для Python и Go, а также разбор неожиданных подводных камней, с которыми столкнулся инженер.

В эпоху облачных решений и бессерверного майнинга Kubernetes стал почти эталоном развёртывания микросервисов. Но за этой гибкостью скрывается растущая проблема — энергопотребление дата-центров, которое уже не укладывается в привычные бюджеты и требования устойчивого развития. Обычная реакция — поднять лимиты CPU/RAM, автоматистически добавить ещё ноды — но это прямой путь к перерасходу электричества и разочарованию коллег из «зеленого офиса».
Инженеры, которые столкнулись с этим в крупноме сетевом сегменте, решили пойти дальше: собрать реальные данные об энергозатратах, понять корреляции с нагрузкой и автоматизировать подстройку кластера так, чтобы он «сам думал» о том, когда лучше днём держать больше подов, а ночью — сворачивать лишнее. Получился гибрид мониторинга, ML-модели и кастомного контроллера — и всё это без переписывания существующих сервисов.
Почему «просто авто-скейлинг» не спасает
Классический Horizontal Pod Autoscaler ориентируется на CPU и RAM, иногда — на пользовательские метрики. Но даже идеально настроенный HPA не отражает энергетического профиля:
Нелинейность энергопотребления. Удвоить CPU-нагрузку не значит удвоить энергозатраты. При низкой загрузке расход фиксируется на уровне «холостого хода».
Разнородность железа. Одни ноды более энергоэффективны, другие — давно доживают до апгрейда.
Реактивность вместо проактивности. HPA реагирует на текущие метрики, но не умеет «заглядывать» в тренды.
Поэтому команда разработала собственный pipeline:
Сбор энергометрии. На уровне нод считываются данные IPMI и внешних ПДУ.
Профилирование микросервисов. Какие сервисы сколько потребляют «в покое» и «под нагрузкой».
Модель предсказания. Лёгкий регрессор, обученный на временных рядах энергопотребления и RPS.
Kubernetes-контроллер на Go. Читает прогноз нагрузки, выдаёт рекомендации по масштабированию через Custom Resource Definition.
Сбор и нормализация энергозатрат (Python)
Первый этап — инфраструктура сбора метрик. Инженеры использовали Python-скрипт для опроса IPMI и ПДУ:
#!/usr/bin/env python3
# language: python
import time
import requests
from threading import Thread
NODES = ["10.0.0.1", "10.0.0.2"] # IPMI-адреса
PDU_API = "http://pdu.local/api/v1/power"
def fetch_ipmi(node):
# Здесь условная библиотека pyipmi
# Возвращает мощность в ваттах
return pyipmi.get_power(node)
def fetch_pdu():
resp = requests.get(PDU_API)
data = resp.json()
return data.get("total_power")
def push_to_prometheus(node, watt):
# Отправка в Pushgateway или напрямую в TSDB
requests.post(f"http://prometheus/push/{node}", json={"power": watt})
def monitor_node(node):
while True:
watt = fetch_ipmi(node)
push_to_prometheus(node, watt)
time.sleep(15)
def monitor_pdu():
while True:
watt = fetch_pdu()
push_to_prometheus("rack_total", watt)
time.sleep(30)
if __name__ == "__main__":
for n in NODES:
Thread(target=monitor_node, args=(n,), daemon=True).start()
Thread(target=monitor_pdu, daemon=True).start()
while True:
time.sleep(3600)
После пары ночей «настраивания правописания» скрипт стабильно пишем метрики в Prometheus.
Профилирование сервисов и построение модели
Наивная идея «подогнать одну линейную модель для всех» разбилась о реальную картину: каждый микросервис имел свой отклик на рост RPS. Было решено для каждой службы собирать метрики CPU, RAM и сравнивать их с энергопотреблением нод, на которых они крутятся.
Использовались готовые экспортеры и Grafana. Затем — экспорт исторических данных и подготовка обучающей выборки для простейшего градиентного бустинга (LightGBM).
Результат: на «тренировочных» данных модель предсказывала суммарное энергопотребление с MAE ~5 Вт, что оказалось достаточным для принятия решений о масштабировании.
Разработка автономного контроллера на Go
Самая весёлая часть — написать Kubernetes-оператор, который живёт в кластере и встраивается в цикл reconcile.
// language: go
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrl "sigs.k8s.io/controller-runtime"
)
type Forecast struct {
Timestamp time.Time `json:"ts"`
Watt float64 `json:"watt"`
}
type EnergyScaler struct {
client client.Client
}
func (r *EnergyScaler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var svc corev1.Service
if err := r.client.Get(ctx, req.NamespacedName, &svc); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Получаем прогноз по HTTP от внешней ML-службы
resp, err := http.Get(fmt.Sprintf("http://predictor.local/forecast/%s", svc.Name))
if err != nil {
return ctrl.Result{RequeueAfter: time.Minute}, err
}
defer resp.Body.Close()
var f Forecast
if err := json.NewDecoder(resp.Body).Decode(&f); err != nil {
return ctrl.Result{}, err
}
// Простейшее правило: если прогноз > X Вт — масштабируем вверх
if f.Watt > 200 {
patch := client.MergeFrom(&svc)
replicas := int32( svc.Spec.Selector["env"] == "prod" && f.Watt > 300 ? 5 : 3 )
svc.Spec.Selector["replicas"] = fmt.Sprintf("%d", replicas)
if err := r.client.Patch(ctx, &svc, patch); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}
func main() {
mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{})
_ = ctrl.NewControllerManagedBy(mgr).
For(&corev1.Service{}).
Complete(&EnergyScaler{client: mgr.GetClient()})
mgr.Start(ctrl.SetupSignalHandler())
}
Несмотря на кажущуюся простоту, реализация столкнулась с нюансами:
Тайминги reconcile. Слишком частые вызовы «убивают» API-сервер.
Границы безопасности. Неточный прогноз мог привести к агрессивному скейлу.
Вертикальные пиковые нагрузки. Бороться пришлось с «всплесками» RPS, которые ложили модель.
Итоги и неожиданные наблюдения
Экономия 15–20 % электроэнергии на тестовом кластере из 10 нод по сравнению с HPA-only.
Снижение просадок производительности при пиковых нагрузках: система предсказывает всплески и заранее поднимает реплики.
Человеческий фактор. Самое ценное — историческое знание инженеров: они заметили зависимость между погодой и энергоэффективностью охлаждения.
В целом, даже вживую наблюдая за числами в Grafana, разработчики не раз шутили: «Наша система умнее меня — вчера она выключила два нода, когда я захотел поиграть ночью в шахматы через VPN».
Рекомендации к внедрению
Начать с простого мониторинга энергопотребления. Без данных нет автоматизации.
Пилотная модель для ключевого сервиса. Не пытаться охватить всё сразу.
Тщательно прорабатывать граничные условия. Прогнозы несовершенны — нужны «заградительные» пороги и ручной Override.
slava_k
Большое спасибо за статью!