Concepts
Lifecycle
Every ManagedWorkload moves through a defined set of phases. Napwake's one action is to pause. It never deletes a workload or its storage.
- Awake
Running
Active. Its activity clock is checked on each reconcile.
- No activity for
idleAfter, or a pause asked for - Awake
Idle
Napwake pauses it, or in dry-run reports the pause it would make.
- At once, unless held back
- Asleep
Pausing
What the pause will change is recorded. Then it's scaled to zero and its pods stopped.
- Asleep
Paused
At zero replicas.
- A request, activity, or
autoResume - Waking up
Resuming
Its previous replica count is being restored.
Running when its pods are Ready, or when Napwake stops waiting for those that aren't
The operator moves a workload on from its activity, its forecast, and pauses asked for with kubectl napwake pause.
- A new ManagedWorkload has no phase until its first reconcile sets
Running.kubectl napwake statusshowscreating. The API acceptsCreatingbut never sets it. - A workload a person, a pipeline or KEDA scaled to zero stays
Running, with theScaledToZerocondition. Napwake only pauses what’s running, and only wakes what it paused. See Workloads already at zero.
Transition triggers
Section titled “Transition triggers”Automatic
Section titled “Automatic”| Transition | When |
|---|---|
| Running → Idle | No activity for idleAfter. Or, with pauseEarly, for 20 minutes before hours a confident forecast expects quiet. And nothing may hold it up |
| Idle → Pausing | At once, unless it’s in dry-run, or a GitOps tool undid its last pause within the hour |
| Idle → Running | Activity resumes while dry-run or a GitOps tool’s undoing holds the pause off. Or it’s scaled to zero outside Napwake |
| Paused → Resuming | A request to its Service, or with wake.mode: Ask a person’s click on Wake it. A new napwake.dev/wake-requested value. A change to an activity annotation since the pause. A Prometheus activity query showing activity. autoResume ahead of forecast demand. Dry-run turned on |
| Pausing → Resuming → Running | Whatever would wake it once paused comes as it pauses, which calls the pause off. A PauseCancelled event says what came. It’s then scaled back up if its pods had begun to stop |
| Paused → Running | Something else scales it up (GitOps tools), or Napwake lets go of it. Also from Pausing, once it’s at zero (status.pause.pausedAt) while its pods stop. It isn’t scaled back down |
| Resuming → Running | Every replica it was restored to is Ready, or Napwake stops waiting (below). Scaled back to zero by something else after the resume scaled it up (status.pause.resumedAt), it’s left off with ScaledToZero |
The resume’s wait starts once its waitForReady dependencies serve. Napwake stops waiting 2 minutes after the first replica is Ready. With none Ready, it stops at the Deployment’s progressDeadlineSeconds after the scale-up. That’s 10 minutes by default, and for a StatefulSet. Stopping short sets ResumeIncomplete. Resume has what follows.
On request
Section titled “On request”| Transition | When |
|---|---|
| Running or Idle → Pausing → Paused | kubectl napwake pause, which sets a new napwake.dev/pause-requested value |
| Running → Idle | The same in dry-run, as a would-be pause |
| Paused → Resuming → Running | kubectl napwake wake, which sets a new napwake.dev/wake-requested value, whatever the wake setting. Or any automatic wake. A requested pause wakes like any other |
| Pausing → Resuming → Running | kubectl napwake wake as it pauses, which calls the pause off, with a PauseCancelled event |
| Idle → Running | The same in dry-run, ending a would-be pause. Awake, a wake request restarts the idle clock |
A request runs the idle clock out at once, and the rules of an idle pause apply. The hour after a GitOps conflict doesn’t hold it back. A forecast expecting demand within the hour declines it, saying when, unless the request overrides the forecast.
Idempotency
Section titled “Idempotency”Every lifecycle operation is idempotent. The operator can crash and restart at any point without leaving a workload inconsistent.
Before anything is scaled, the status write that enters Pausing records what the pause changes in status.pause. That’s previousReplicas, the KEDA ScaledObject, and its own paused-replicas value. So:
- Wherever a restart or a conflicting write interrupts a pause, it’s finished or undone from the record.
- Resuming restores the recorded replica count, not a hardcoded value.
- Pausing an already-paused workload does nothing.
Before scaling, the record is also copied to the target’s napwake.dev/paused-from annotation, until it’s handed back. If the ManagedWorkload loses its status while paused, by a restore without status or being created again:
| It finds | Then |
|---|---|
| The copy, and the workload still at zero | Back to Paused (PauseRecovered) |
| The copy, and the workload scaled up since | Released (PauseRecovered) |
Neither, while Pausing, Paused or Resuming |
Nothing was scaled. Running again (PauseRecordLost) |
spec.target can’t change once set, so a paused target is never left at zero. To manage another workload, create another ManagedWorkload.
Status conditions
Section titled “Status conditions”A ManagedWorkload’s conditions report what the operator found, such as what holds it awake or why it won’t pause. IdleVetoed and HeldByDependents hold a pause off. ScaledToZero leaves a workload off. ResumeIncomplete says a resume stopped waiting. Conditions and events lists every condition, with each reason.
Events
Section titled “Events”Each user-visible change emits one event, as it happens, shown by kubectl describe. The phases above show as IdleDetected, Paused and Resumed, and a wake by a request as WokenByRequest. Conditions and events lists every event, by what it’s about.