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:
- Creation: You add a finalizer string (e.g.,
my-app.example.com/cleanup) to your object when you create it. - Deletion Request: When a user calls
kubectl delete, Kubernetes notices the finalizer. Instead of deleting the object, it sets themetadata.deletionTimestampfield. - Reconciliation: Your controller detects the
deletionTimestamp. It runs your custom cleanup logic (e.g., calling an API to delete the external database). - Removal: Once the cleanup succeeds, your controller removes the finalizer string from the object's metadata.
- Final Deletion: Now that the
finalizerslist 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.