Skip to main content
By default the WebApp delete flow in this operator relies solely on owner references. That works for in-cluster resources like Deployments, Services, and ConfigMaps because the Kubernetes garbage collector will remove dependent objects automatically when their owner is deleted. However, some cleanup tasks cannot be inferred by Kubernetes — for example, cleaning up external cloud resources, revoking credentials, or performing any operator-specific final work. For those cases you need a finalizer on the custom resource so the controller can run cleanup before the API server actually deletes the object. The problem with the current behavior (before adding a finalizer) is demonstrated here: creating the WebApp and immediately deleting it completes quickly because nothing blocks the deletion.
That fast disappearance prevents the controller from performing any needed cleanup. The solution is to add a finalizer string owned by the operator and implement the standard finalizer pattern in the controller’s Reconcile logic.
controller-runtime helpers simplify adding, checking, and removing finalizers: each controller should only add and remove its own finalizer string. Using these helpers avoids manual metadata slice manipulation.

Finalizer name and placement

Use a unique, domain-style finalizer string and declare it as a package-level constant so the controller uses a single canonical value everywhere.
For reference, the controller struct looks like:

The finalizer pattern in Reconcile

A well-structured Reconcile should clearly separate two paths:
  1. Normal (non-deleting) path — ensure the finalizer is present.
  2. Deletion path — perform cleanup and remove the finalizer so Kubernetes can finish deletion.
Key points:
  • Only add the finalizer when the object is not being deleted (DeletionTimestamp == zero).
  • When adding the finalizer, persist the object with r.Update and then requeue so the next reconcile will operate on the object with the updated resourceVersion.
  • When deletion is in progress (DeletionTimestamp non-nil), only perform cleanup if your finalizer is present; then remove it and Update so the API server can complete deletion.

Add finalizer during normal reconciliation

Check the DeletionTimestamp and add the finalizer if missing. Use controllerutil.ContainsFinalizer and controllerutil.AddFinalizer to avoid unnecessary updates.

Deletion branch — perform cleanup, then remove finalizer

When the Resource’s DeletionTimestamp is set the API server has accepted a delete request but is holding the object until all finalizers are removed. Act only if your finalizer is present; perform any required controller-specific cleanup, then remove your finalizer and Update the object so Kubernetes can finish deletion. A full Reconcile example that demonstrates both branches:
Do not remove finalizers that you do not own. Always check ContainsFinalizer before calling RemoveFinalizer to avoid interfering with other controllers’ cleanup. Also return errors from Update so controller-runtime will retry on transient failures.

Verify deletion completed

Once the controller has removed its finalizer and the API server finishes deletion, a subsequent get should return NotFound:
A NotFound response indicates the delete request completed successfully and the finalizer flow worked: your controller performed cleanup and released its finalizer so Kubernetes could complete deletion.

Quick summary and checklist

References

Watch Video

Practice Lab