Guides
Opting in
Install Napwake once per cluster, then label what it should manage. The label says which workloads, and annotations say how.
-
Label the workload, measuring first.
deployment.yaml apiVersion: apps/v1kind: Deploymentmetadata:name: checkout-apinamespace: preview-42labels:napwake.dev/managed: "true"annotations:napwake.dev/dry-run: "true" # measure first; nothing is pausednapwake.dev/idle-after: "2h"Napwake creates a ManagedWorkload for it, kept in step with the annotations. The label is usually one line in Helm values or manifests in Git.
-
See what pausing would free.
Terminal window kubectl napwake scanIt lists every workload in dry-run.
-
Start pausing.
Terminal window kubectl napwake enable checkout-api -n preview-42kubectl napwake enable --all -n preview-42 # every workload in the namespaceenablesets the workload’s ownnapwake.dev/dry-runto"false", which wins wherever the dry-run came from. Seeenablefor--all, GitOps and Helm.
Napwake pauses the workload once it has had no activity for idle-after, 2 hours here.
A whole namespace
Section titled “A whole namespace”Label a namespace to opt in every Deployment and StatefulSet in it, with its annotations as their defaults. It suits dev and preview namespaces, and third-party charts you can’t label.
apiVersion: v1kind: Namespacemetadata: name: preview-42 labels: napwake.dev/managed: "true" annotations: napwake.dev/dry-run: "true" napwake.dev/idle-after: "2h"An Application with CreateNamespace=true has no Namespace in Git. Put the label and annotations in managedNamespaceMetadata, which Argo CD sets on the namespace it creates.
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: preview-42 namespace: argocdspec: project: default source: repoURL: https://github.com/your-org/environments.git targetRevision: main path: previews/preview-42 destination: server: https://kubernetes.default.svc namespace: preview-42 syncPolicy: automated: {} managedNamespaceMetadata: labels: napwake.dev/managed: "true" annotations: napwake.dev/dry-run: "true" napwake.dev/idle-after: "2h" syncOptions: - CreateNamespace=trueArgo CD applies it only in a sync with CreateNamespace=true, so an existing namespace gets it at the next sync. Adding it starts that sync only with prune: true and selfHeal: true. Otherwise press Sync in the UI, which takes the sync options, or give them with kubectl:
kubectl patch application preview-42 -n argocd --type merge -p '{"operation":{"sync":{"syncOptions":["CreateNamespace=true"]}}}'To stop, remove the label from managedNamespaceMetadata and sync. Removing managedNamespaceMetadata leaves the label on, as Argo CD then leaves the namespace alone. Leave replicas to Napwake too, as in With GitOps.
Flux doesn’t create a Kustomization’s namespace, so it’s in Git. Label and annotate it there, as namespace.yaml beside the kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1kind: Kustomizationnamespace: preview-42resources: - namespace.yaml - ../../baseA HelmRelease’s install.createNamespace leaves its namespace unlabelled, so add the Namespace to the Kustomization that applies it. To stop, remove the label and keep the Namespace. With prune: true, Flux deletes a Namespace that leaves Git, and everything in it.
To leave one workload out, label it napwake.dev/ignore: "true" or napwake.dev/managed: "false".
With Kustomize
Section titled “With Kustomize”Use one of these. Each leaves the workloads’ selectors alone.
If the kustomization has the Namespace, label and annotate it there, as in A whole namespace. Nothing on the workloads changes.
labels with includeSelectors: false labels each resource’s own metadata only:
resources: - ../../baselabels: - pairs: napwake.dev/managed: "true" includeSelectors: falseLeave out includeTemplates. It labels the pods too, restarting them for nothing. It also labels a StatefulSet’s volumeClaimTemplates, which fails as the selector does. Services and ConfigMaps get the label, which Napwake ignores. So does a Namespace in the kustomization, opting in the whole namespace.
A patch changes only the workload’s own labels and annotations, settings included:
resources: - ../../basepatches: - target: kind: Deployment name: checkout-api patch: | apiVersion: apps/v1 kind: Deployment metadata: name: checkout-api labels: napwake.dev/managed: "true" annotations: napwake.dev/dry-run: "true" napwake.dev/idle-after: "2h"Keep the target. Without it, the build fails with no resource matches strategic merge patch if the manifest has a namespace the patch doesn’t give. For every Deployment and StatefulSet, target kind: Deployment|StatefulSet with no name, and keep the patch’s name, which is ignored.
commonAnnotations puts settings on the pod templates too. Adding or changing one, such as dry-run, then restarts every workload’s pods. Napwake counts each restart as a deploy, which is activity. Use the Namespace or a patch.
Settings
Section titled “Settings”Set them on the workload or its namespace. Settings annotations lists the values each accepts.
| Annotation | Default | What it does |
|---|---|---|
napwake.dev/dry-run |
"false" |
Measures, and records what pausing would free, without pausing |
napwake.dev/idle-after |
1h |
Time without activity before pausing, at least 1m |
napwake.dev/cpu-threshold |
10 |
CPU use, as a percentage of requests, above which it’s active |
napwake.dev/auto-resume |
"false" |
Wakes it ahead of the demand a confident forecast predicts |
napwake.dev/pause-early |
"false" |
Pauses before idle-after runs out when a confident forecast expects quiet. Never with wake: "ask" |
napwake.dev/forecast |
"balanced" |
How soon the forecast acts. "eager", "balanced" or "cautious" |
napwake.dev/depends-on |
none | Workloads this one needs |
napwake.dev/wake |
"request" |
What wakes it through the doorman. "request" is any request. "ask" is only a person’s click, for a public URL |
napwake.dev/wake-max-wait |
2m |
How long a request is held while it wakes, at most 1h |
napwake.dev/wake-page |
"true" |
Shows browsers a waking-up page |
Leave one out for its default. A value that can’t be read is skipped, with an InvalidSetting warning event, and the next place below decides. An unreadable dry-run turns dry-run on instead, and an unreadable wake asks.
Which setting wins
Section titled “Which setting wins”- 1The workload's annotation
- 2Its namespace's
- 3The Helm defaults
defaults.idleAfter,defaults.cpuThreshold,defaults.dryRun,defaults.pauseEarly,defaults.forecast,defaults.wake - 4The built-in default
So a namespace can measure everything with dry-run: "true" while one workload in it goes live with dry-run: "false".
Protected namespaces
Section titled “Protected namespaces”kubectl label namespace payments napwake.dev/protected=trueNapwake never manages anything in a protected namespace, such as production, whatever the labels. A warning event on each workload says why.
| In a protected namespace | What happens |
|---|---|
| A labelled workload | It isn’t opted in |
| A ManagedWorkload you wrote | It reports Protected=True and never pauses |
| A workload Napwake had paused | It’s woken, so protecting never leaves anything off |
| Always protected, with no label | So that |
|---|---|
kube-system and every other kube-* |
Labelling every namespace can’t pause CoreDNS or metrics-server |
| Napwake’s own namespace, and the doorman’s if it runs elsewhere | Napwake can’t pause itself |
Protect more by name, such as every prod-*, with the Helm value protectedNamespaces. Only napwake.dev/allow-protected: "true", set on purpose, lets Napwake manage a protected namespace. Napwake never sets either label. The operator’s and the doorman’s own Deployments are never managed, since nothing would be left to wake them. To keep Napwake out by Kubernetes RBAC, install it with watchNamespaces.
With GitOps
Section titled “With GitOps”GitOps tools leave the ManagedWorkload alone, but must leave the replicas to Napwake. The ManagedWorkload is owned by the workload, like a Deployment’s ReplicaSets, so Argo CD shows it under the workload. It never takes the workload’s labels, so a tool’s tracking label can’t make the tool claim and prune it.
A tool would scale a paused workload back up. GitOps tools shows how to stop that for Argo CD, Flux, Fleet, kapp and Kluctl. Under Flux, many charts let you leave replicas out, such as when their autoscaling value is on.
What Napwake does with the label
Section titled “What Napwake does with the label”| When you | Napwake |
|---|---|
| Label a workload | Creates a ManagedWorkload named after it, owned by it and marked napwake.dev/from-label: "true", with a Managed event on the workload. When the name is taken, as by a StatefulSet named like a Deployment, it adds the kind, such as api-statefulset |
| Change its annotations | Updates the ManagedWorkload within seconds |
Remove the label, or add napwake.dev/ignore |
Deletes it, first scaling a paused workload back to its replicas |
| Delete the workload | Deletes its ManagedWorkload too |
| Wrote a ManagedWorkload for it | Yours wins, and the label does nothing |
Set the label to anything but "true", such as "dry-run" |
Doesn’t opt it in, and says so in an event. Settings like dry-run are annotations |
Settings with more than one value, such as Prometheus activity queries, aren’t annotations. Set them on the ManagedWorkload the label created, which keeps them.