Napwake 0.2.0 is in release candidatesRead the changelog

30Get started
Menu

1 Pause on use

It pauses what’s idle.

No timetable decides it. Napwake keeps an activity clock for every workload it manages, the last time anything showed it was in use. When the clock is older than idleAfter, one hour by default, the workload is paused. Any single sign of activity resets it.

What counts as activity

SourceCounts whenNeeds
CPUAbove cpuThreshold of what the pods request, 10% by default. Set it per workload, per namespace or for the clustermetrics-server
A deployThe pod template changes: a new image, an environment variablenothing
A requestThe doorman held a request for it while it was pausednothing
napwake.dev/last-activityYour own tooling sets a newer time, say a developer portal when someone opens an environmentyour tooling
napwake.dev/active-untilA time in the future, to hold it awake, say for a demoyour tooling
A Prometheus queryAny query you give it, such as request rate, returns more than zeroPrometheus
  • Every source is checked and the most recent wins, so a worker with real work and no traffic isn’t paused.
  • Memory isn’t a source, since it stays allocated whether or not anyone uses the workload.
  • It never pauses a workload it can’t measure, and there’s no learning period. A new workload can pause after its first idle hour.

Opting in

A label on a workload or a whole namespace, with settings as annotations, in the manifests or Helm values you already keep in Git. Start in dry-run. It measures what it would pause and free, and never pauses anything. It can’t see requests, so a workload used with little CPU looks idler than it is. A Prometheus query on its request rate at the ingress fixes that.

# deployment.yaml
metadata:
  name: my-api
  namespace: dev
  labels:
    napwake.dev/managed: "true"
  annotations:
    napwake.dev/dry-run: "true"
    napwake.dev/idle-after: "1h"

Then kubectl napwake enable my-api -n dev ends dry-run, and it pauses after idleAfter with no sign of use. Idle detection in the docs has every rule.

devasleepstagingasleeppr-42awake
Nobody has used staging for idleAfter, so its lights go out. pr-42 is in use, so it stays awake.

2 How it wakes

The doorman wakes it on a request, or asks first.

A paused workload has no pods, so nothing would answer its Service. Its Services point at the doorman instead. Behind a VPN, single sign-on or an allowlist, the first request wakes it. On a public URL, set it to ask, and only a person’s click wakes it.

  1. The route. When a workload pauses, the operator adds an EndpointSlice to each of its Services, pointing at the doorman. The doorman is a small proxy, its own Deployment of two replicas beside the operator. The Service, its ClusterIP and its DNS name don’t change.
  2. The door. A connection reaches the doorman. Behind a VPN, once the caller has sent something, he holds it and wakes the workload. On a public URL set to ask, he waits for a person’s click instead. A wake takes the workload’s dependencies with it, such as the database it uses.
  3. The hand-over. As soon as a pod is Ready, by its readiness probe, a held connection goes through, with the bytes the caller already sent.
  4. Out of the way. Once the workload is running, the doorman’s EndpointSlices go and traffic reaches the pods directly again.

Waking on a request is the default, with nothing to configure. Asking first is napwake.dev/wake: ask on a workload or its namespace, or defaults.wake: ask in Helm for the whole cluster. Napwake can’t tell a public URL from a private one, so the setting is yours.

devasleepstagingwaking uppr-42awake
A request reached staging, behind the VPN. The doorman holds it while staging starts, and dev stays asleep.

Behind your VPN or SSO

The first request wakes it.

  • Once a caller sends something, the doorman holds the connection and wakes the workload. It works on TCP, so it holds HTTP, gRPC, Postgres and Redis alike.
  • A caller that connects and sends nothing for 3 seconds wakes it too, as for MySQL, where the server speaks first.
  • It holds a connection until a pod is Ready, for up to wake.maxWait. That’s 2 minutes by default, and at most an hour. After that, it closes the connection, and the wake goes on.
  • A browser gets the waking page at once. It reloads every 3 seconds, and the first reload after a pod is Ready reaches the app. It names no namespace, workload or Service.
  • Known probes, scrapers and uptime monitors, such as kube-probe, Prometheus, Datadog and Pingdom, get a 503. A TCP health check that connects and closes is closed. A request for a hidden file, such as /.env, gets a 404. None of them wake it.

On a public URL

With napwake.dev/wake: ask, a person wakes it.

  • On the internet, most requests are robots, and many pass for browsers. Once an environment has its own HTTPS certificate, its hostname is in public certificate logs within minutes. Left on the default, it would wake every few minutes.
  • So set it to ask, and the doorman asks first. A browser gets the asleep page, kept out of search engines. Loading it wakes nothing.
  • Wake it sends a token signed for that workload and that pause, good for 10 minutes. Only that wakes it, and the waking page carries on.
  • A click wakes it at most once a minute. A bot that clicks is rare. Each wake it causes keeps the workload up for at least idleAfter.
  • Anything else is turned away, and wakes nothing. Over HTTP it gets a 503 that says how to wake it. Known probes are answered as behind a VPN.

Who wakes it, at each door

Who wakes a paused workload, behind your VPN or SSO and on a public URL set to ask
The caller, while it’s asleepBehind your VPN or SSOOn a public URL, set to ask
A person, in a browserWakes it by opening a page, and gets the waking page.Gets the asleep page. Only a click on Wake it wakes it.
Scripts, API clients, gRPC and database driversHeld until a pod is Ready, for up to wake.maxWait. After that, it’s closed, and the wake goes on.Turned away. Over HTTP, a 503 that says how to wake it. Nothing wakes.
Crawlers and scannersWake it, unless they ask for a hidden file.A 503. Nothing wakes.
Known probes, scrapers and uptime monitorsA 503 at both doors. Nothing wakes.
Tooling, such as a pull request bot, a portal or a chat command, by an annotation or kubectl napwake wakeWakes it, at both doors. A condition on the workload tells the tool the wake was taken up.
CI, through an activity annotation, or a dependent wakingWakes it, at both doors.
The forecast, if you turn it onWakes it 15 minutes before an hour it expects busy, at both doors. Where it asks first, the forecast learns those hours from people’s clicks and tooling’s wakes.

Wake on request in the docs covers readiness probes, timeouts behind an Ingress, and the Services it can’t route.

3 The forecast

It wakes the team’s rooms 15 minutes early.

Napwake learns each workload’s daily and weekly pattern with a Holt-Winters double seasonal model, fed every hour from its CPU and from the requests that wake it. With autoResume on, it wakes a paused workload 15 minutes before an hour it expects to be busy, so the first person in doesn’t wait.

08:45devasleepstagingasleepqaasleep
The morning list, before the team arrives. Staging is expected busy at 09:00, so Napwake wakes it at 08:45.
  1. 08:45 autoResume wakes it, 15 minutes before an hour the forecast expects to be busy. Nobody waits.
  2. 09:10 The first person opens it, and finds it awake.
  3. 18:30 An hour after the last use at 17:30, idleAfter runs out and it’s paused.
An illustration, with no measured data. A weekday of a workload people use from 09:10 to 17:30, the pattern the forecast’s simulator tests, with autoResume on and the default idleAfter of one hour.
  • It learns before it acts. It learns its first week without scoring it. On office hours it acts from the Tuesday of the second week. With a few weeks of history in Prometheus, it acts from the first day.
  • Weekends stay asleep, once the first weekend teaches it they’re quiet.
  • The activity clock pauses workloads, and the forecast never does. A confident forecast holds off a pause before an hour it expects busy. It brings one forward only if you turn on pauseEarly.
  • Check it first. kubectl napwake forecast replays it over your workloads’ own history before you trust autoResume with them.
$ kubectl napwake forecast -n preview-42
staging: the forecast replayed over 28 days of CPU history from monitoring/prometheus-operated, times in UTC

NAMESPACE   NAME              FORECAST             READY  COLD  WOKEN
preview-42  api               acts from day one    9/10   1     08:45, 25m ahead
preview-42  worker (managed)  acts from Mon 2 Nov  3/10   7     09:45, 20m ahead
preview-42  reports           not within 4 weeks   0/6    6     -

Example output from the plugin’s docs, columns trimmed. Forecasting in the docs has the model, the confidence it acts at, and the simulator that tests it.

4 What a pause changes

It never deletes anything.

Its one action is to pause, which scales a workload to zero replicas. The Deployment or StatefulSet stays, and so do its PVCs and their data. Only the pods are removed.

Stays

  • The Deployment or StatefulSet
  • Its Services, ClusterIP and DNS name
  • Its PVCs and their data
  • Its replica count, recorded to restore

Changes while it’s paused

  • Replicas: zero
  • Its Services route to the doorman
  • A KEDA ScaledObject is held at zero
  • The pods are gone
  • It writes down what a pause will change before it scales anything, in its status and on the workload’s napwake.dev/paused-from annotation. So a pause cut short by a restart is finished or undone, and a resume goes back to the replicas it had.
  • Some things it won’t pause: a StatefulSet whose PVCs would be deleted on scale-down, Kubernetes’ own namespaces, and Napwake itself.
  • It doesn’t fight you. Scaled up by anyone else, a paused workload counts as woken. Scaled to zero by hand, it’s left alone. If a GitOps tool undoes a pause, Napwake reports how to fix it.

Pause and resume in the docs has each step of a pause.

devasleepstagingasleepqaasleep
Every room asleep, and everything still in its room. Only the pods are gone.

See it on your own cluster.

kubectl napwake scan shows what’s idle and what pausing would free, with nothing installed.