Schedule icon
Get icon
Script icon
If icon
Log icon
SlackIncomingWebhook icon
Pause icon
Switch icon
Loop icon
Patch icon
Delete icon

Kubernetes PersistentVolume Released State and Orphan Reclamation Guard

Audits Kubernetes PersistentVolumes in Released state, verifies deleted PVCs, and safely recycles or prunes orphaned storage to stop cloud disk capacity leaks.

Categories
Infrastructure

Production Kubernetes clusters hosting stateful workloads (databases, message brokers, streaming state stores) routinely configure StorageClasses with reclaimPolicy: Retain to protect against catastrophic accidental data loss. However, when a Helm release, stateful namespace, or PersistentVolumeClaim (PVC) is deleted, the underlying PersistentVolume (PV) transitions into Released status. In this state, the volume is completely stranded: Kubernetes prevents any new PVC from binding to it because it retains the old claimRef UID, while the underlying cloud block volume (AWS EBS, GCP Persistent Disk, Azure Managed Disk, Ceph RBD) continues to accrue monthly storage charges and exhaust cluster capacity quotas.

This blueprint implements a production-grade Day 2 storage governance gate. It audits all PersistentVolumes across the cluster, cross-references active PersistentVolumeClaims to verify claim deletion, enforces a configurable retention grace window, and provides two distinct automated remediation strategies: safely recycling reusable volumes back to Available by clearing spec.claimRef, or pruning expired volumes past the retention SLA to stop storage waste.

How it works

  1. periodic_released_pv_audit (io.kestra.plugin.core.trigger.Schedule) initiates a daily storage governance audit at 06:00 UTC (shipped disabled for safe initial evaluation).
  2. fetch_persistent_volumes (io.kestra.plugin.kubernetes.kubectl.Get) connects to the Kubernetes cluster API with fetchType: STORE to pull the complete inventory of all cluster-wide PersistentVolume resources, writing raw JSON specifications to Kestra internal storage.
  3. fetch_persistent_volume_claims (io.kestra.plugin.kubernetes.kubectl.Get) pulls all active PersistentVolumeClaim resources across all namespaces with fetchType: STORE, establishing the baseline of legitimate active claims.
  4. analyze_released_volumes (io.kestra.plugin.scripts.python.Script) cross-references each Released PV against active PVCs. Volumes whose PVC still exists in terminating or finalizer-locked state are safely skipped. Volumes whose PVC is confirmed deleted are evaluated against retention_grace_hours and storage_class_filter, calculating total leaked capacity in GiB.
  5. evaluate_orphaned_volumes (io.kestra.plugin.core.flow.If) evaluates whether orphaned volumes exist:
    • When orphaned volumes are present: dispatches a detailed incident summary to Slack (alert_released_pvs) including capacity in GiB and target volume identifiers.
    • When remediation is enabled (remediation_strategy != 'ALERT_ONLY' and dry_run == 'false'): optionally pauses for human SRE sign-off via approve_remediation (io.kestra.plugin.core.flow.Pause), then routes via route_remediation_action (io.kestra.plugin.core.flow.Switch).
    • Under RECYCLE_TO_AVAILABLE: loops over targeted volumes using patch_clear_claimref (io.kestra.plugin.kubernetes.kubectl.Patch with JSON_MERGE) to set spec.claimRef: null, enabling the Kubernetes volume controller to mark the volume Available.
    • Under PRUNE_EXPIRED: loops over expired volumes using delete_pv (io.kestra.plugin.kubernetes.kubectl.Delete) to delete the PV and trigger storage provider deprovisioning.
    • verify_post_remediation (io.kestra.plugin.kubernetes.kubectl.Get) re-reads cluster storage state to confirm successful transition or removal.
  6. log_audit_complete (io.kestra.plugin.core.log.Log) writes a final structured summary of cluster storage inventory across Bound, Available, and Released phases.
  7. An errors block alerts Slack if API server connectivity drops or RBAC permissions are revoked.

What you get

  • Automated detection of stranded Released PersistentVolumes leaking cloud storage budget.
  • True zero-downtime volume recycling: clearing claimRef returns retained volumes to the cluster storage pool without manual CLI intervention.
  • Double-safety safeguard: requires both dry_run: false and optional human approval pause before any cluster state mutation.
  • Protection for terminating workloads: cross-references PVC inventory to ensure in-progress pod migrations are never disrupted.
  • Multi-unit storage capacity calculation (TiB, GiB, MiB) tracking exact reclaimable storage metrics.

Who it's for

  • Platform Engineers and Kubernetes Administrators managing multi-tenant stateful clusters.
  • Cloud FinOps practitioners auditing unattached cloud disks and idle persistent volume costs.
  • Site Reliability Engineers maintaining cluster storage capacity and preventing storage pool exhaustion.

Why orchestrate this with Kestra

  • Declarative storage lifecycle management without writing, maintaining, and deploying custom Kubernetes operator controllers.
  • Native Pause task support allowing human SREs to review audit calculations before executing irreversible storage pruning.
  • Complete audit trail with execution history, before-and-after volume verification reads, and automated Slack reporting.
  • Standardized secret management protecting cluster service account credentials and webhook endpoints.

Prerequisites

  • A running Kubernetes cluster (v1.23+) with RBAC authorization enabled.
  • A Kubernetes ServiceAccount with permissions to get and list persistentvolumeclaims, and get, list, patch, and delete persistentvolumes.
  • A Slack Incoming Webhook URL for incident and audit reporting.

Secrets

  • K8S_MASTER_URL: HTTPS endpoint of the Kubernetes API server (e.g., https://k8s.internal.company.com:6443).
  • K8S_TOKEN: Bearer token for the storage auditor ServiceAccount.
  • SLACK_WEBHOOK_URL: Slack Incoming Webhook URL for storage alerts and audit reports.

Quick start

  1. Configure the K8S_MASTER_URL, K8S_TOKEN, and SLACK_WEBHOOK_URL secrets in your Kestra namespace.
  2. Run the flow with default inputs (dry_run: "true", remediation_strategy: "ALERT_ONLY").
  3. Review the execution logs and Slack notification to inspect any detected Released volumes and total leaked GiB.
  4. To test automated volume recycling, set remediation_strategy: "RECYCLE_TO_AVAILABLE" and dry_run: "false". Approve the pause task to verify that spec.claimRef is cleared and the volume transitions to Available.
  5. Enable periodic_released_pv_audit to schedule continuous daily storage governance.

How to extend

  • Add Prometheus push gateway metrics via io.kestra.plugin.prometheus.Push to graph orphaned volume counts and wasted GiB over time in Grafana.
  • Filter by specific storage providers (e.g. storage_class_filter: "ebs-sc|ceph-rbd") to establish tiered retention policies per storage class.
  • Chain a Jira issue creation task via io.kestra.plugin.jira.issues.Create when total leaked storage exceeds 1 TiB to trigger formal capacity review.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.