How do you prune released persistent volumes

Posted June 29, 2023 by Thomas Kooi ‐ 5 min read

Learn how to cleanup released persistent volumes in Kubernetes at scale

As a DevOps engineer, effectively managing Kubernetes clusters requires being mindful of avoiding unnecessary cloud resource usage. One particular area to address is the accumulation of unused Kubernetes Persistent Volumes (PVs). This situation commonly occurs when undeploying stateful sets, testing new applications, or in non-production clusters.

Cleaning up these unused PVs manually can be a tedious and error-prone task. It involves considering the Persistent Volume’s Retain value, and if there are many PVs, it can take a significant amount of time. Moreover, it’s a monotonous and repetively task.

To simplify and speed up the process, our CLI tool, acloud-toolkit, provides a convenient storage prune subcommand. This functionality allows you to quickly remove any unused persistent volume claims (PVCs). By utilizing the acloud-toolkit, you can automate the cleanup, ensuring efficient use of cloud resources and reducing unnecessary waste associated with unused PVs.

Real world example

Consider a real-world scenario where we have a Lab cluster dedicated to one of our team members who has been actively working on Helm charts and deployments for Dependency Track. Over time, we noticed that the number of persistent volumes being utilized in the cluster has been steadily increasing. While the storage occupied may not be significant, it is important to address the issue of accumulating unused resources, as it is wasteful to retain them unnecessarily.

Here are the persistent volumes within the cluster:

❯ kubectl get persistentvolume
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                                                 STORAGECLASS      REASON   AGE
pg-backup-pv                               5Gi        RWO            Retain           Available                                                         gp2                        61d
postgresdb-persistent-volume               8Gi        RWX            Retain           Released    dependency-track/db-persistent-volume-claim           manual                     20h
pvc-084c6751-ef42-4f7b-8f94-e30dc7d5497c   8Gi        RWO            Retain           Bound       dependency-track/dependency-track-apiserver           gp2                        19h
pvc-085c94b0-939a-4150-b79f-c5857ba72f06   10Gi       RWO            Retain           Released    minio/minio                                           gp2                        62d
pvc-0f19c704-ba50-4332-8ebf-eff54a7f56f4   8Gi        RWO            Retain           Bound       default/data-test-postgresql-0                        gp2                        216d
pvc-1784aa38-1137-4986-a76a-33cfd1048729   8Gi        RWO            Retain           Bound       dependency-track/data-dependency-track-postgresql-0   gp2                        20h
pvc-2529c053-181b-4264-91e8-f50b179a6610   1Gi        RWO            Retain           Bound       minio/cluster-example-with-backup-2                   standard                   33d
pvc-36a7efd4-e8c0-4d79-9c09-c81677748b1d   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        212d
pvc-45bc639d-f6b8-4921-b7aa-87d6f1a8bb21   1Gi        RWO            Retain           Released    minio/cluster-example-with-backup-1                   my-storageclass            61d
pvc-473997f2-90d9-45e4-a289-bf9c987e3301   1Gi        RWO            Retain           Bound       minio/cluster-example-with-backup-1                   standard                   33d
pvc-533652d0-42a6-42f1-aab6-262fc8d1106c   1Gi        RWO            Retain           Released    minio/cluster-example-with-backup-3                   standard                   61d
pvc-5a407924-5268-40cf-9893-4f1b6e7b8449   1Gi        RWO            Retain           Bound       minio/cluster-example-with-backup-3                   standard                   33d
pvc-6456dc25-41b9-4f9b-888b-6eabe082bcc5   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        210d
pvc-6a2b5a29-a44d-4ca7-b89e-38c79ed0f38f   1Gi        RWO            Retain           Released    minio/cluster-example-with-backup-1                   my-storageclass            61d
pvc-8259c5df-ee09-4b2e-adba-59d9fdd48c93   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        215d
pvc-9182dd06-251b-4fee-b9e9-47225dae935a   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        20h
pvc-abed312e-f913-492f-a1d8-806e77e99914   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        20h
pvc-b04a4ccd-4295-4582-8436-111c52d9b0c6   1Gi        RWO            Retain           Released    minio/cluster-example-with-backup-1                   standard                   61d
pvc-bb8bc5ab-0499-40b3-a9cb-ac9ea649df52   10Gi       RWO            Retain           Bound       dependency-track/pg-data-dependency-track-db-0        gp2                        18h
pvc-bcfc6bce-d100-47f3-8637-e0116e651982   8Gi        RWO            Retain           Released    dependency-track/dependency-track-apiserver           gp2                        211d
pvc-cf0dd4f9-e8a6-4410-9763-fe7a62f5b1ac   8Gi        RWO            Retain           Released    dependency-track/data-dependency-track-postgresql-0   gp2                        215d
pvc-ef3b95f9-8731-410f-b7fc-0823339e7f80   1Gi        RWO            Retain           Released    minio/cluster-example-with-backup-2                   standard                   61d
pvc-f532bd09-87bc-4e71-bf9b-3cfc19524664   8Gi        RWO            Retain           Bound       default/data-dependency-track-postgresql-0            gp2                        219d
pvc-f8656c7f-0a71-4612-a4ac-29eac4fc48f9   8Gi        RWO            Retain           Bound       default/data-postgresql-0                             gp2                        219d

As you can see, a significant number of persistent volumes are categorized as Released in the STATUS field. This indicates that the corresponding persistent volume claims have been deleted, while the actual volumes still exist in the cloud. Adding to the complexity, the RECLAIM POLICY is set to Retain" Therefore, if you were to simply delete these persistent volumes using the kubectl delete command, they would remain orphaned in the cloud infrastructure. Consequently, you would still incur costs for these volumes, despite them no longer being referenced or used within your cluster.

How to clean this up quickly?

These volumes can be quickly cleaned-up in the correct manner, using acloud-toolkit. If you are on MacOS, you can quickly install it using Homebrew:

brew install avisi-cloud/tools/acloud-toolkit

Other installation options are available.

Once installed, we can start using the storage prune sub command to gather an overview of any volume that is unused and can be removed. By default, the command runs in dry-run mode. This is to avoid accidently removing persistent volumes that you did not intend to delete.

❯ acloud-toolkit storage prune
pruning persistent volume "postgresdb-persistent-volume" ("dependency-track/db-persistent-volume-claim") ... (dry run)
pruning persistent volume "pvc-085c94b0-939a-4150-b79f-c5857ba72f06" ("minio/minio") ... (dry run)
pruning persistent volume "pvc-36a7efd4-e8c0-4d79-9c09-c81677748b1d" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-45bc639d-f6b8-4921-b7aa-87d6f1a8bb21" ("minio/cluster-example-with-backup-1") ... (dry run)
pruning persistent volume "pvc-533652d0-42a6-42f1-aab6-262fc8d1106c" ("minio/cluster-example-with-backup-3") ... (dry run)
pruning persistent volume "pvc-6456dc25-41b9-4f9b-888b-6eabe082bcc5" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-6a2b5a29-a44d-4ca7-b89e-38c79ed0f38f" ("minio/cluster-example-with-backup-1") ... (dry run)
pruning persistent volume "pvc-8259c5df-ee09-4b2e-adba-59d9fdd48c93" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-9182dd06-251b-4fee-b9e9-47225dae935a" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-abed312e-f913-492f-a1d8-806e77e99914" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-b04a4ccd-4295-4582-8436-111c52d9b0c6" ("minio/cluster-example-with-backup-1") ... (dry run)
pruning persistent volume "pvc-bcfc6bce-d100-47f3-8637-e0116e651982" ("dependency-track/dependency-track-apiserver") ... (dry run)
pruning persistent volume "pvc-cf0dd4f9-e8a6-4410-9763-fe7a62f5b1ac" ("dependency-track/data-dependency-track-postgresql-0") ... (dry run)
pruning persistent volume "pvc-ef3b95f9-8731-410f-b7fc-0823339e7f80" ("minio/cluster-example-with-backup-2") ... (dry run)
total storage pruned: 79Gi (dry run)

We can dubble check the volumes it is going to delete for us. We also see the total amount of storage that will be deleted by performing this action. In this case, 79Gi.

When we confirm we are happy with the volumes it will delete, we can run the same command using --dry-run=false.

❯ acloud-toolkit storage prune --dry-run=false
pruning persistent volume "postgresdb-persistent-volume" ("dependency-track/db-persistent-volume-claim") ...
pruning persistent volume "pvc-085c94b0-939a-4150-b79f-c5857ba72f06" ("minio/minio") ...
pruning persistent volume "pvc-36a7efd4-e8c0-4d79-9c09-c81677748b1d" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-45bc639d-f6b8-4921-b7aa-87d6f1a8bb21" ("minio/cluster-example-with-backup-1") ...
pruning persistent volume "pvc-533652d0-42a6-42f1-aab6-262fc8d1106c" ("minio/cluster-example-with-backup-3") ...
pruning persistent volume "pvc-6456dc25-41b9-4f9b-888b-6eabe082bcc5" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-6a2b5a29-a44d-4ca7-b89e-38c79ed0f38f" ("minio/cluster-example-with-backup-1") ...
pruning persistent volume "pvc-8259c5df-ee09-4b2e-adba-59d9fdd48c93" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-9182dd06-251b-4fee-b9e9-47225dae935a" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-abed312e-f913-492f-a1d8-806e77e99914" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-b04a4ccd-4295-4582-8436-111c52d9b0c6" ("minio/cluster-example-with-backup-1") ...
pruning persistent volume "pvc-bcfc6bce-d100-47f3-8637-e0116e651982" ("dependency-track/dependency-track-apiserver") ...
pruning persistent volume "pvc-cf0dd4f9-e8a6-4410-9763-fe7a62f5b1ac" ("dependency-track/data-dependency-track-postgresql-0") ...
pruning persistent volume "pvc-ef3b95f9-8731-410f-b7fc-0823339e7f80" ("minio/cluster-example-with-backup-2") ...
total storage pruned: 79Gi

The executed command accomplishes the following actions:

  • Modifies the reclaim policy of the persistent volumes from “Retain” to “Delete” specifically for volumes marked as Released. It’s important to note that this command does not affect any persistent volumes with Available or Bound statuses.
  • Removes the persisted volumes from the Kubernetes cluster. The associated CSI (Container Storage Interface) provider, such as AWS EBS, Ceph, or others, will handle the deletion of the volumes within the underlying storage backend.

Following these steps allows for efficient cleanup of unused persistent volumes across your Kubernetes clusters. By executing this procedure, you can effectively manage resources and optimize storage utilization within your environment.