Scheduling Kubernetes jobs for when the grid is cleaner
The carbon intensity of electricity changes through the day. The same kilowatt-hour can come from a grid running mostly on wind and solar at one hour and mostly on gas a few hours later. For work that has to run now, that doesn’t help. But a lot of compute doesn’t have to run now: nightly reports, model training, data backfills, test suites, batch exports.
Green Computing Scheduler is a custom Kubernetes scheduler, written in Go, that uses that flexibility. It holds batch jobs until grid carbon intensity drops below a threshold, then lets them run.
In 2026, with data centers using more electricity every year and AI workloads making up a growing share of it, shifting flexible work in time is one of the few sustainability measures that costs almost nothing to adopt.
How it works
Kubernetes lets a pod choose its scheduler. Pods that set schedulerName: green-scheduler are handled by this scheduler. Everything else is untouched, so it can run next to the default scheduler without changing how the rest of the cluster behaves.
Each pod chooses a policy with one annotation:
metadata:
annotations:
green-scheduler.io/policy: 'prefer-low-carbon'
spec:
schedulerName: green-scheduler
Carbon intensity comes from a pluggable provider: WattTime for US grid regions, Electricity Maps for global coverage, and a mock provider so the whole thing can be tested on a laptop with Minikube and no API keys. Prometheus metrics report decisions and estimated savings.
Three policies, two failure modes
| Policy | Below threshold | Above threshold | Carbon data unavailable |
|---|---|---|---|
immediate |
Run | Run | Run |
prefer-low-carbon |
Run | Wait, up to a max delay (6 h) | Run |
carbon-only |
Run | Wait | Wait |
The interesting column is the last one.
With prefer-low-carbon, if the carbon API is down, the job runs immediately. It fails open. The job was going to run anyway, and saving carbon is a preference, not a requirement. Holding a report hostage to a third-party API outage would be a reliability bug wearing a sustainability badge.
With carbon-only, the same outage means the job keeps waiting. It fails closed. Someone chose this policy because they only want the work done on clean power, so running it on unknown power would break the promise.
Neither is right in general. What matters is that the choice is explicit, visible in the policy name, and matches what the person submitting the job expects.
The maximum delay is the reliability feature
prefer-low-carbon waits for a clean window, but never forever. Each pod can set its own maximum delay with an annotation, defaulting to six hours. When the delay is exceeded, the job runs regardless of carbon intensity.
That one setting is what makes carbon-aware scheduling acceptable to the people who own the jobs. “Your nightly export might start a few hours later” is a conversation a team can agree to. “Your nightly export runs when the wind blows” is not.
What it deliberately doesn’t do
It only shifts work in time, not in space. Moving a job to a cleaner region involves data location, latency and compliance, which is a much larger set of decisions than one scheduler should make on its own. And it only targets batch work. Services that answer users can’t wait for a cleaner hour.
The broader lesson applies well beyond carbon: whenever a scheduling decision depends on outside data, decide in advance what happens when that data is missing, and put that decision where users can see it.