k8s-cleaner - Kubernetes Controller that identifies, removes, or updates stale/orphaned or unhealthy resources
Introduction to Rollback
rollback is an optional field on the Cleaner spec that captures the state of every resource right before a Delete or Transform action is applied to it, so the most recent execution can be reverted. Only the last execution can be rolled back: each run overwrites the previous one's captured data, the same way it overwrites the Report itself.
storage currently only supports Report, which stores the pre-action resource inline on the Report instance's resourceInfo[].fullResource field. No extra infrastructure (no PersistentVolume) is required.
Requirements
Rollback data is only useful if it is durably persisted, and today the only place it is persisted is the Report instance. Because of that, a Cleaner with rollback set must also configure a CleanerReport notification. If it doesn't, the Cleaner refuses to run: no resource is scanned, matched, deleted, or transformed, and the reason is surfaced in status.failureMessage.
$ kubectl get cleaner unused-configmaps -o yaml
...
status:
failureMessage: 'rollback is enabled but no CleanerReport notification is configured: rollback data would never be persisted'
Ordering guarantee
Cleaner never deletes or transforms a resource before its pre-action state has been safely written to the Report. If persisting that snapshot fails for any reason, the run aborts before touching any resource, rather than risking a resource being changed with no way to revert it.
What gets captured
rollbackhas no effect whenactionis set toScan: nothing is ever deleted or transformed, so there is nothing to revert.- A single resource's captured state is capped at 256KB. Larger resources are skipped (rollback won't be available for them specifically), and a note is added to that resource's entry in the Report so it's clear why. This keeps the Report's total size bounded, together with
blastRadiusLimit, which bounds how many resources a single run can affect. - Captured resource bodies are never included in outgoing Slack/Webex/Discord/Teams/Telegram/SMTP notifications. They only ever live on the
Reportinstance.
Example - Rollback a Delete Action
Cleaner Definition with Rollback and a CleanerReport Notification
---
apiVersion: apps.projectsveltos.io/v1alpha1
kind: Cleaner
metadata:
name: unused-configmaps
spec:
schedule: "0 * * * *"
action: Delete
rollback:
storage: Report
resourcePolicySet:
resourceSelectors:
- namespace: test
kind: ConfigMap
group: ""
version: v1
evaluate: |
function evaluate()
hs = {}
hs.matching = true
return hs
end
notifications:
- name: report
type: CleanerReport
Example - Rollback a Transform Action
Cleaner Definition with Rollback and a CleanerReport Notification
---
apiVersion: apps.projectsveltos.io/v1alpha1
kind: Cleaner
metadata:
name: scale-down-deployments
spec:
schedule: "0 * * * *"
action: Transform
rollback:
storage: Report
transform: |
function transform()
hs = {}
obj.spec.replicas = 0
hs.resource = obj
return hs
end
resourcePolicySet:
resourceSelectors:
- namespace: test
kind: Deployment
group: "apps"
version: v1
notifications:
- name: report
type: CleanerReport
Report Output
After either Cleaner runs, the Report instance carries the captured resource alongside the usual fields:
apiVersion: apps.projectsveltos.io/v1alpha1
kind: Report
metadata:
name: unused-configmaps
spec:
action: Delete
resourceInfo:
- resource:
apiVersion: v1
kind: ConfigMap
name: my-configmap
namespace: test
message: '. time: 2026-01-15 10:00:00 +0000 UTC'
fullResource: <base64-encoded ConfigMap as it existed right before deletion>
Triggering a Rollback
The easiest way is from the web dashboard: open the Reports page, expand a report whose action was Delete or Transform, and click Rollback. A confirmation dialog explains what will happen before anything is reverted. Like every other mutating action, the button is hidden when the dashboard is running in read-only mode.
The button calls the same API endpoint you can reach directly, by name of the Cleaner (the Report shares its name with the Cleaner that produced it):
$ kubectl port-forward -n projectsveltos svc/k8s-cleaner-web 9080:9080
$ curl -X POST http://localhost:9080/api/v1/reports/unused-configmaps/rollback
[
{
"kind": "ConfigMap",
"namespace": "test",
"name": "my-configmap",
"success": true
}
]
Each entry in the response reflects one captured resource: success: true means it was recreated (Delete action) or restored to its previous state (Transform action); success: false comes with a message explaining why, for instance when no rollback data was captured for that resource because it exceeded the size cap.