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

В эпоху облачных решений и бессерверного майнинга Kubernetes стал почти эталоном развёртывания микросервисов. Но за этой гибкостью скрывается растущая проблема — энергопотребление дата-центров, которое уже не укладывается в привычные бюджеты и требования устойчивого развития. Обычная реакция — поднять лимиты CPU/RAM, автоматистически добавить ещё ноды — но это прямой путь к перерасходу электричества и разочарованию коллег из «зеленого офиса».

Инженеры, которые столкнулись с этим в крупноме сетевом сегменте, решили пойти дальше: собрать реальные данные об энергозатратах, понять корреляции с нагрузкой и автоматизировать подстройку кластера так, чтобы он «сам думал» о том, когда лучше днём держать больше подов, а ночью — сворачивать лишнее. Получился гибрид мониторинга, ML-модели и кастомного контроллера — и всё это без переписывания существующих сервисов.


Почему «просто авто-скейлинг» не спасает

Классический Horizontal Pod Autoscaler ориентируется на CPU и RAM, иногда — на пользовательские метрики. Но даже идеально настроенный HPA не отражает энергетического профиля:

  • Нелинейность энергопотребления. Удвоить CPU-нагрузку не значит удвоить энергозатраты. При низкой загрузке расход фиксируется на уровне «холостого хода».

  • Разнородность железа. Одни ноды более энергоэффективны, другие — давно доживают до апгрейда.

  • Реактивность вместо проактивности. HPA реагирует на текущие метрики, но не умеет «заглядывать» в тренды.

Поэтому команда разработала собственный pipeline:

  1. Сбор энергометрии. На уровне нод считываются данные IPMI и внешних ПДУ.

  2. Профилирование микросервисов. Какие сервисы сколько потребляют «в покое» и «под нагрузкой».

  3. Модель предсказания. Лёгкий регрессор, обученный на временных рядах энергопотребления и RPS.

  4. 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, которые ложили модель.


Итоги и неожиданные наблюдения

  1. Экономия 15–20 % электроэнергии на тестовом кластере из 10 нод по сравнению с HPA-only.

  2. Снижение просадок производительности при пиковых нагрузках: система предсказывает всплески и заранее поднимает реплики.

  3. Человеческий фактор. Самое ценное — историческое знание инженеров: они заметили зависимость между погодой и энергоэффективностью охлаждения.

В целом, даже вживую наблюдая за числами в Grafana, разработчики не раз шутили: «Наша система умнее меня — вчера она выключила два нода, когда я захотел поиграть ночью в шахматы через VPN».


Рекомендации к внедрению

  • Начать с простого мониторинга энергопотребления. Без данных нет автоматизации.

  • Пилотная модель для ключевого сервиса. Не пытаться охватить всё сразу.

  • Тщательно прорабатывать граничные условия. Прогнозы несовершенны — нужны «заградительные» пороги и ручной Override.

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


  1. slava_k
    06.07.2025 14:33

    Большое спасибо за статью!