Skip to content
All projects

FinOps Kubernetes Operator

Kubernetes operator that scales non-production workloads to zero during declared sleep windows, with per-workload exclusions.

Source code
  • Kubernetes Operator
  • Python
  • Kopf
  • FinOps

The problem

Non-production clusters run all night and all weekend at full replica count, paying for capacity nobody is using — and the obvious fix, scaling things down, is exactly the kind of automation that causes an outage when it gets one workload wrong.

Constraints

Architecture

Trigger

Kopf timer — every 60s

Reconcile

Scaling engine
Read annotations
Compute active window

Act

Inside sleep windowpatch replicas → 0
Excludedbypass workload

Interactive Operator Simulator

Reconciliation & Schedule State

21:00 UTCSleep Window (19:00 - 08:00)
00:0008:00 (Wake)12:0019:00 (Sleep)23:00

web-frontend

Namespace: qa-env

Replicas0 (Scaled down)

cart-api

Namespace: qa-env

Replicas0 (Scaled down)

critical-worker

exclude: true

Annotation Bypass

Replicas1 (Protected)

Key decisions

What was chosen, what it was chosen over, and why.

Also decided

  • Per-workload exclusion annotationnotA central allow-list maintained by the platform team

    Opting a critical workload out has to be possible without a platform-team round trip. Cost automation only survives contact with users if the escape hatch is trivial.

  • Server-side field_selector when listing podsnotListing everything and filtering client-side

    The API server does the filtering, so the operator does not pull every pod in the cluster into memory to count a handful. It also skips terminating pods, which would otherwise read as rogue workloads.

  • Timer-based reconciliation every 60 secondsnotEvent-driven triggers

    The trigger is wall-clock time, not a cluster event. A periodic loop matches the shape of the problem and converges after any missed tick, restart or reschedule.

  • Kopf and PythonnotThe Go operator SDK and controller-runtime

    The reconciliation logic is small and schedule-shaped, so Kopf keeps it short and quick to iterate. The trade is a heavier runtime and a smaller ecosystem than controller-runtime.

Production resilience & failure modes

Failure mode this architecture has to account for

A workload under an active HPA or KEDA ScaledObject sits in a sleep-scheduled namespace — the operator scales it to zero, and the autoscaler, seeing load, scales it straight back up. The two controllers fight in a loop.

How it's handled

The operator doesn't try to out-reconcile another controller — that's a fight it can't win safely. Workloads under HPA/KEDA management opt out with a `finops-operator/exclude: "true"` annotation instead, so the two never touch the same replica count. Native coordination (pausing KEDA's `ScaledObject` for the sleep window instead of requiring opt-out) is on the roadmap, not yet built.

Source: README — KEDA & event-driven autoscaling