1

I am experiencing an issue using the helm_release resource in Terraform.

I basically deployed a kube-prometheus-stack that includes many k8s resources and that works smoothly.

The problem arose when I tried to destroy (or remove) this part, since Helm does not delete all resources (it probably has to do with some garbage collection rule that keeps them up and running after delete) when uninstalls the chart. That means I end-up with:

  • chart uninstalled
  • resources still up and running
  • having to manually go there and remove everything, otherwise if I re-create the thing I get plenty of duplicates

I previously asked a question (that now I am closing) related to understanding if it was a problem with Helm (and is not, by design it deletes everything when it can, I am not sure if in the chart something can be done, but anyway I am assuming it won't be done now and quickly) now I would like to ask if somebody has ab idea on how I can manage this directly from Terraform.

Is there something I can use to, for instance, run a kubectl delete command on the labelled resources (or maybe the whole namespace) when the helm_release resource gets destroyed?

Note: I am not adding any code, since this has nothing to do with the code, but more about finding some hook or hack to run a cleanup only after destroy.

p.s.: I also tried to exploit Terraform cloud hooks post-apply, but I would prefer to solve this within depending on Terraform Cloud and, anyway, I didn't manage to create a dependency on whether the helm_release had been destroyed.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
Bertone
  • 756
  • 2
  • 9
  • 23
  • If you follow this tutorial, resources does not get deleted after section Clean up your infrastructure? https://developer.hashicorp.com/terraform/tutorials/kubernetes/helm-provider#clean-up-your-infrastructure – Roar S. Jul 09 '23 at 14:00
  • 1
    Unfortunately the tutorial is not really about my issue. Helm could end up leaving resources up and running after uninstalling a release. In those cases I need a plan to clean that up from terraform... i.e., in my case a Terraform destroy on the resources plane leaves pods and other stuff running. This is due to a known behavior of Helm, not a problem in the chart. – Bertone Jul 10 '23 at 14:14
  • May be you should try https://github.com/hashicorp/terraform-provider-helm. It worked for me both create and destroy. – Richard Rublev Jul 11 '23 at 11:16

1 Answers1

2

In you need to solve this problem directly from Terraform, you could consider using a null_resource with a local-exec provisioner that is triggered when the helm_release resource gets destroyed.
The local-exec provisioner invokes a local executable, in this case kubectl, after a resource is destroyed.

Here is a brief example of how this could work:

resource "helm_release" "example" {
  name       = "example"
  namespace  = "default"
  chart      = "stable/kube-prometheus-stack"
  # add your values here...
}

resource "null_resource" "cleanup" {
  triggers = {
    helm_release_id = helm_release.example.id
  }

  provisioner "local-exec" {
    when    = destroy
    command = "kubectl delete namespace ${helm_release.example.namespace}"
  }
}

The above script will run kubectl delete namespace command on the namespace after the helm_release resource is destroyed.

Do test that carefully: deleting the entire namespace, not just the resources created by the Helm chart, is not a casual operation!
If there are other resources in the namespace that you do not want to delete, you will need to modify the kubectl command to delete only the resources you want.

And note that you would need to have kubectl configured on the machine running Terraform and it needs to have appropriate permissions to delete resources in your Kubernetes cluster.

Also, this null_resource will not get created until after the helm_release is created, due to the dependency in the triggers block. So, if the helm_release creation fails for some reason, the null_resource and its provisioners will not be triggered.


Unfortunately, I am using Terraform Cloud in a CI/CD pipe, therefore I won't be able to exploit the local-exec. But the answer is close to what I was looking for and since I didn't specify about Terraform Cloud is actually right.
Do you have any other idea?

The local-exec provisioner indeed cannot be used in the Terraform Cloud as it does not support running arbitrary commands on the host running Terraform.

Kubernetes Provider lifecycle management

An alternative solution in this context would be to use Kubernetes providers in Terraform to manage lifecycle of the resources that are left behind.

For example, let's say your Helm chart leaves behind a PersistentVolumeClaim resource. You could manage this using the Kubernetes provider in Terraform:

provider "kubernetes" {
  # configuration for your Kubernetes cluster
}

resource "helm_release" "example" {
  name       = "example"
  namespace  = "default"
  chart      = "stable/kube-prometheus-stack"
  # add your values
}

data "kubernetes_persistent_volume_claim" "pvc" {
  metadata {
    name      = "my-pvc"
    namespace = helm_release.example.namespace
  }
}

resource "kubernetes_persistent_volume_claim" "pvc" {
  depends_on = [helm_release.example]

  metadata {
    name      = data.kubernetes_persistent_volume_claim.pvc.metadata.0.name
    namespace = data.kubernetes_persistent_volume_claim.pvc.metadata.0.namespace
  }

  spec {
    access_modes = data.kubernetes_persistent_volume_claim.pvc.spec.0.access_modes
    resources {
      requests = {
        storage = data.kubernetes_persistent_volume_claim.pvc.spec.0.resources.0.requests["storage"]
      }
    }

    volume_name = data.kubernetes_persistent_volume_claim.pvc.spec.0.volume_name
  }
}

In this example, the kubernetes_persistent_volume_claim resource will delete the PVC when the Terraform stack is destroyed.

You would have to do this for every type of resource that is left behind, so it can be a bit tedious, but it is an option.

Kubernetes Provider for Job or a script

Another approach would be using the Kubernetes provider to call a Kubernetes Job or a script that cleans up the resources left behind:

provider "kubernetes" {
  # configuration for your Kubernetes cluster goes here
}

resource "helm_release" "example" {
  name       = "example"
  namespace  = "default"
  chart      = "stable/kube-prometheus-stack"
  # add your values here...
}

resource "kubernetes_job" "cleanup" {
  metadata {
    name      = "cleanup-job"
    namespace = helm_release.example.namespace
  }

  spec {
    template {
      metadata {}
      spec {
        container {
          name    = "cleanup"
          image   = "appropriate/curl" # or any image that has kubectl or equivalent tool
          command = ["sh", "-c", "kubectl delete ..."] # replace ... with the actual cleanup commands
        }
        
        restart_policy = "Never"
      }
    }

    backoff_limit = 4
  }

  depends_on = [helm_release.example]
}

In this second example, the kubernetes_job resource is triggered when the helm_release resource is created, running a cleanup script. The cleanup script could delete any resources that are left behind by the Helm chart.

Remember that in both cases, the Kubernetes provider needs to be properly configured and that the Kubernetes cluster permissions must allow the actions you are trying to perform.


Regarding the second example, the OP asks if it is possible for the kubernetes_job to be triggered automatically when the helm_release resource gets destroyed.

Unfortunately, Terraform's built-in resources and providers do not provide a direct way to execute something only upon the destruction of another resource. The provisioner block is a way to do this, but as we discussed, it is not suitable for Terraform Cloud and cannot be used with the Kubernetes provider directly.

As an indirect solution, you can create a Kubernetes job that is configured to delete the resources as soon as it is launched, and then use a depends_on reference to the helm_release in the job's configuration. That way, whenever the Helm release is created, the job will be launched as well. When you run terraform destroy, the Helm release will be destroyed and the job will be launched once more, thereby cleaning up the resources.

However, this approach is not perfect because it will also run the job when the resources are first created, not only when they are destroyed.

To address that, you could write your cleanup script such that it is idempotent and will not fail or cause any negative side effects if it is run when it is not necessary (i.e., upon creation of the Helm release).
For example, your script could first check if the resources it is supposed to clean up actually exist before attempting to delete them:

provider "kubernetes" {
  # configuration for your Kubernetes cluster goes here
}

resource "helm_release" "example" {
  name       = "example"
  namespace  = "default"
  chart      = "stable/kube-prometheus-stack"
  # add your values here...
}

resource "kubernetes_job" "cleanup" {
  depends_on = [helm_release.example]

  metadata {
    name      = "cleanup-job"
    namespace = helm_release.example.namespace
  }

  spec {
    template {
      metadata {}
      spec {
        container {
          name    = "cleanup"
          image   = "appropriate/curl" # or any image that has kubectl or equivalent tool
          command = ["sh", "-c", 
                     "if kubectl get <resource> <name>; then kubectl delete <resource> <name>; fi"]
                     # replace <resource> and <name> with the actual resource and name
        }

        restart_policy = "Never"
      }
    }

    backoff_limit = 4
  }
}

In this example, the command checks if a specific Kubernetes resource exists before attempting to delete it. That way, the job can be safely run whether the Helm release is being created or destroyed, and the cleanup will only occur if the resource exists.

Do replace <resource> and <name> with the actual resource and name of the resource you wish to check and potentially delete.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thanks for the great answer, unfortunately I am using Terraform Cloud in a CI/CD pipe, therefore I won't be able to exploit the local-exec. But the answer is close to what I was looking for and since I didn't specify about Terraform Cloud is actually right. I will wait some more days to see if something else pops up that works even with Terraform Cloud and otherwise accept yours that will come in handy in general. By any chance, do you have any other idea? – Bertone Jul 12 '23 at 05:50
  • @Bertone any other idea? Sure. I have edited the answer to include them. – VonC Jul 12 '23 at 08:08
  • Wow, thanks so much for the great answer, there is a lot to learn here. I am making this the accepted answer for sure, glad the bounty worked. I will just ask you a clarification is there any way to make the second example (kubernetes_job) run/trigger when resource is destroyed? Otherwise I will opt for the other one, a bit tedious but it will fix my issue once and for all. – Bertone Jul 13 '23 at 13:50
  • @Bertone Thank you for your feedback. Regarding your last comment, "Is it possible for the kubernetes_job to be triggered automatically when the helm_release resource gets destroyed?", I have edited the answer to address it. – VonC Jul 13 '23 at 14:17
  • Thanks, great help! – Bertone Jul 13 '23 at 16:12