Using Finalizers

Finalizers allow controllers to implement asynchronous pre-delete hooks. Letsay you create an external resource such as a storage bucket for each object ofthe API type you are implementing and you would like to clean up the external resourcewhen the corresponding object is deleted from Kubernetes, you can use afinalizer to delete the external resource.

You can read more about the finalizers in the kubernetes reference docs. Section belowdemonstrates how to register and trigger pre-delete hooks in the Reconcilemethod of a controller.

Highlights:

  • If object is not being deleted and does not have the finalizer registered,then add the finalizer and update the object in kubernetes.
  • If object is being deleted and the finalizer is still present in finalizers list,then execute the pre-delete logic and remove the finalizer and update theobject.
  • You should implement the pre-delete logic in such a way that it is safe to invoke it multiple times for the same object.
  1. func (r *Reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
  2. err := r.Get(context.TODO(), request.NamespacedName, instance)
  3. if err != nil {
  4. // handle error
  5. }
  6. // name of your custom finalizer
  7. myFinalizerName := "storage.finalizers.example.com"
  8. if instance.ObjectMeta.DeletionTimestamp.IsZero() {
  9. // The object is not being deleted, so if it does not have our finalizer,
  10. // then lets add the finalizer and update the object.
  11. if !containsString(instance.ObjectMeta.Finalizers, myFinalizerName) {
  12. instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, myFinalizerName)
  13. if err := r.Update(context.Background(), instance); err != nil {
  14. return reconcile.Result{}, err
  15. }
  16. }
  17. } else {
  18. // The object is being deleted
  19. if containsString(instance.ObjectMeta.Finalizers, myFinalizerName) {
  20. // our finalizer is present, so lets handle our external dependency
  21. if err := r.deleteExternalDependency(instance); err != nil {
  22. // if fail to delete the external dependency here, return with error
  23. // so that it can be retried
  24. return reconcile.Result{}, err
  25. }
  26. // remove our finalizer from the list and update it.
  27. instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, myFinalizerName)
  28. if err := r.Update(context.Background(), instance); err != nil {
  29. return reconcile.Result{}, err
  30. }
  31. }
  32. // Our finalizer has finished, so the reconciler can do nothing.
  33. return reconcile.Result{}, nil
  34. }
  35. ....
  36. ....
  37. }
  38. func (r *Reconciler) deleteExternalDependency(instance *MyType) error {
  39. log.Printf("deleting the external dependencies")
  40. //
  41. // delete the external dependency here
  42. //
  43. // Ensure that delete implementation is idempotent and safe to invoke
  44. // multiple types for same object.
  45. }
  46. //
  47. // Helper functions to check and remove string from a slice of strings.
  48. //
  49. func containsString(slice []string, s string) bool {
  50. for _, item := range slice {
  51. if item == s {
  52. return true
  53. }
  54. }
  55. return false
  56. }
  57. func removeString(slice []string, s string) (result []string) {
  58. for _, item := range slice {
  59. if item == s {
  60. continue
  61. }
  62. result = append(result, item)
  63. }
  64. return
  65. }