Course contentsShow
← Docker, Kubernetes & CI/CD
Lesson 478 of 8027. Package Management and AutomationFree lesson

Implementing Finalizers for Graceful Resource Deletion

You will add finalizers to your controllers to perform clean-up operations before a custom resource is permanently purged from the cluster.

In this lesson, you will learn how to use finalizers to ensure your custom controllers perform essential cleanup tasks before a Kubernetes resource is permanently deleted.

Why Finalizers Matter

When you delete a custom resource (CR), Kubernetes typically removes it immediately from etcd. However, your resource might have "external" dependencies—like a cloud database, an S3 bucket, or a network firewall rule—that aren't managed by Kubernetes' native garbage collection. If you delete the CR without cleaning these up, you end up with "zombie" resources in your cloud provider that cost money and clutter your infrastructure.

The Mechanism

A finalizer is a string key added to the metadata.finalizers list of your object. When a controller sees this key, it knows it is responsible for "cleaning up" before the object is gone.

The process follows a specific lifecycle:

  1. Creation: You add a finalizer string (e.g., my-app.example.com/cleanup) to your object when you create it.
  2. Deletion Request: When a user calls kubectl delete, Kubernetes notices the finalizer. Instead of deleting the object, it sets the metadata.deletionTimestamp field.
  3. Reconciliation: Your controller detects the deletionTimestamp. It runs your custom cleanup logic (e.g., calling an API to delete the external database).
  4. Removal: Once the cleanup succeeds, your controller removes the finalizer string from the object's metadata.
  5. Final Deletion: Now that the finalizers list is empty, Kubernetes finally purges the object from the cluster.

Code Sketch

Inside your reconciliation loop, you handle the logic like this:

if resource.ObjectMeta.DeletionTimestamp.IsZero() {
    // The object is NOT being deleted, ensure finalizer exists
    if !containsString(resource.Finalizers, myFinalizerName) {
        resource.Finalizers = append(resource.Finalizers, myFinalizerName)
        client.Update(ctx, resource)
    }
} else {
    // The object is marked for deletion, run cleanup
    if containsString(resource.Finalizers, myFinalizerName) {
        if err := performExternalCleanup(resource); err != nil {
            return err // Retry later
        }
        // Remove finalizer to allow deletion
        resource.Finalizers = removeString(resource.Finalizers, myFinalizerName)
        client.Update(ctx, resource)
    }
}

Key Takeaway: Finalizers prevent the immediate deletion of a resource, giving your controller a "blocking" window to execute teardown logic for external systems.