No description
  • Python 72.8%
  • Dockerfile 24.1%
  • Shell 3.1%
Find a file
2026-03-17 19:52:16 +00:00
microcontroller-wol-agent initial commit 2026-03-13 17:13:17 +01:00
node-agent initial commit 2026-03-13 17:13:17 +01:00
operator initial commit 2026-03-13 17:13:17 +01:00
wol-agent initial commit 2026-03-13 17:13:17 +01:00
README.md Update README.md 2026-03-17 19:52:16 +00:00

k8s-power-manager

A Kubernetes operator for managing power on bare-metal nodes that have no BMC. Nodes are shut down and woken via Wake-on-LAN when needed. The idea is that it can be used in conjuction with something like kube-downscaler to help keep our electricity bills down

Current Status: not working! Right now the overall architecture is in place, but I will get it working as and when family/home life allows

Overview

┌─────────────────────────────────────────────┐
│               Kubernetes Cluster            │
│                                             │
│  ┌─────────────┐        ┌────────────────┐  │
│  │  operator   │───────▶│  node-agent    │  │
│  │  (kopf)     │        │  (DaemonSet)   │  │
│  └──────┬──────┘        └────────────────┘  │
│         │                                   │
└─────────┼───────────────────────────────────┘
          │ HTTP /wake
          ▼
   ┌─────────────┐       ─ ─ ─ ─ ─ ─ ─
   │  wol-agent  │──WOL──▶  bare-metal │
   │  (Pi Zero)  │       ─ ─ ─ ─ ─ ─ ─
   └─────────────┘

The operator:

  1. Cordons and drains the managed node on a cron schedule
  2. Calls the node-agent to trigger shutdown -h now
  3. At wake time, calls the wol-agent to send a WOL magic packet
  4. Waits for the node to become Ready, then scales workloads back up

Components

wol-agent

A small Flask HTTP server that sends Wake-on-LAN magic packets. Runs on a device physically attached to the same L2 network as the managed nodes (required for broadcast).

Supported platforms:

Platform Runtime
Raspberry Pi Zero W / Zero 2 W Docker (armv6l, armv7l, amd64)
ESP32 MicroPython
Raspberry Pi Pico W MicroPython

Endpoint: POST /wake?mac=AA:BB:CC:DD:EE:FF

node-agent

A Flask HTTP server that triggers host shutdown. Runs as a DaemonSet, only on nodes labelled power-manager/managed=true.

Platforms: arm64, amd64

Security:

  • Shared secret via X-Agent-Secret header
  • Distroless-style image (shells removed) to prevent kubectl exec abuse
  • NetworkPolicy restricting access to the operator pod only

Endpoint: POST /shutdown

operator

A kopf-based Python operator that reconciles PowerManagedNode CRDs.

CRD

apiVersion: power.homelab/v1alpha1
kind: PowerManagedNode
metadata:
  name: heavy-node-1
spec:
  nodeName: heavy-node-1
  macAddress: "aa:bb:cc:dd:ee:ff"
  wolAgent: "http://pi-zero.local:8080/wake"
  sleepSchedule: "0 23 * * *"   # shut down at 23:00
  wakeSchedule:  "0 7 * * *"    # wake at 07:00
  managedWorkloads:
    - kind: Deployment
      name: ollama
      namespace: ai

Node phases: RunningDrainingOffWakingWaitingForReadyRunning

Getting Started

Deploy the wol-agent

cd wol-agent
cp .env.example .env   # set WOL_BROADCAST to your subnet e.g. 192.168.1.255
docker compose up -d

For MicroPython devices, edit WIFI_SSID and WIFI_PASSWORD in wol-agent-micropython/main.py, then:

mpremote mip install microdot
mpremote cp wol-agent-micropython/main.py :main.py

Build multi-arch images

docker buildx create --use --name multiplatform

# wol-agent (includes armv6l for Pi Zero W)
docker buildx build \
  --platform linux/arm/v6,linux/arm/v7,linux/amd64 \
  --tag yourrepo/wol-agent:latest --push \
  ./wol-agent

# node-agent
docker buildx build \
  --platform linux/arm64,linux/amd64 \
  --tag yourrepo/node-agent:latest --push \
  ./node-agent
#operator
docker buildx build \
  --platform linux/arm64,linux/amd64 \
  --tag yourrepo/node-agent:latest --push \
  ./operator

Deploy the operator

kubectl apply -f operator/crd/powermanagednode.yaml
kubectl apply -f operator/manifests/

Repository Structure

.
├── wol-agent/               # Pi Zero / SBC Docker agent
├── wol-agent-micropython/   # ESP32 / Pico W MicroPython agent
├── node-agent/              # In-cluster shutdown DaemonSet
└── operator/                # kopf operator