From 526da36d7de7ed46233e5f17ef42c5ca58f339a6 Mon Sep 17 00:00:00 2001 From: Fabian Hardt Date: Sun, 2 Aug 2026 11:54:39 +0200 Subject: [PATCH] docs(ske): warn that load balancers block cluster deletion A Service of type LoadBalancer makes the cloud controller create a load balancer in the project. It belongs to no Terraform state, so destroy removes the cluster and nobody removes the load balancer - the shoot then waits for it, and the cluster stays in STATE_DELETING until this resource times out after 90 minutes. Nothing points at the cause today. The state is indistinguishable from "slow", and the error after the timeout is "Cluster deletion waiting: ..." without a hint. In our case that cost an hour before we found the load balancer still sitting in STATUS_READY. This adds a note to the resource description and names the likely cause in the timeout error, including the command to check for leftovers. It does not change behaviour - the underlying issue is tracked in #1646, where the service side would either clean the load balancers up or expose what the deletion is waiting for. Signed-off-by: Fabian Hardt --- docs/resources/ske_cluster.md | 3 +++ stackit/internal/services/ske/cluster/resource.go | 14 +++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/resources/ske_cluster.md b/docs/resources/ske_cluster.md index b1fc8176a..99f0c904d 100644 --- a/docs/resources/ske_cluster.md +++ b/docs/resources/ske_cluster.md @@ -5,6 +5,7 @@ subcategory: "" description: |- SKE Cluster Resource schema. Must have a region specified in the provider configuration. -> When updating node_pools of a stackit_ske_cluster, the Terraform plan might appear incorrect as it matches the node pools by index rather than by name. However, the SKE API correctly identifies node pools by name and applies the intended changes. Please review your changes carefully to ensure the correct configuration will be applied. + ~> Before destroying a cluster, remove any Service of type LoadBalancer from it. Such a service makes the cloud controller create a load balancer in the project, which belongs to no Terraform state. If it still exists, the cluster stays in STATE_DELETING until this resource times out after 90 minutes. --- # stackit_ske_cluster (Resource) @@ -13,6 +14,8 @@ SKE Cluster Resource schema. Must have a `region` specified in the provider conf -> When updating `node_pools` of a `stackit_ske_cluster`, the Terraform plan might appear incorrect as it matches the node pools by index rather than by name. However, the SKE API correctly identifies node pools by name and applies the intended changes. Please review your changes carefully to ensure the correct configuration will be applied. +~> Before destroying a cluster, remove any `Service` of type `LoadBalancer` from it. Such a service makes the cloud controller create a load balancer in the project, which belongs to no Terraform state. If it still exists, the cluster stays in `STATE_DELETING` until this resource times out after 90 minutes. + ## Example Usage ```terraform diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index 3d4e9a594..0db7adf45 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -404,6 +404,9 @@ var descriptions = map[string]string{ "main": "SKE Cluster Resource schema. Must have a `region` specified in the provider configuration.", "node_pools_plan_note": "When updating `node_pools` of a `stackit_ske_cluster`, the Terraform plan might appear incorrect as it matches the node pools by index rather than by name. " + "However, the SKE API correctly identifies node pools by name and applies the intended changes. Please review your changes carefully to ensure the correct configuration will be applied.", + "destroy_load_balancer_note": "Before destroying a cluster, remove any `Service` of type `LoadBalancer` from it. " + + "Such a service makes the cloud controller create a load balancer in the project, which belongs to no Terraform state. " + + "If it still exists, the cluster stays in `STATE_DELETING` until this resource times out after 90 minutes.", "max_surge": "Maximum number of additional VMs that are created during an update.", "max_unavailable": "Maximum number of VMs that that can be unavailable during an update.", "nodepool_validators": "If set (larger than 0), then it must be at least the amount of zones configured for the nodepool. The `max_surge` and `max_unavailable` fields cannot both be unset at the same time.", @@ -417,9 +420,9 @@ var descriptions = map[string]string{ // Schema defines the schema for the resource. func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: fmt.Sprintf("%s\n%s", descriptions["main"], descriptions["node_pools_plan_note"]), + Description: fmt.Sprintf("%s\n%s\n%s", descriptions["main"], descriptions["node_pools_plan_note"], descriptions["destroy_load_balancer_note"]), // Callout block: https://developer.hashicorp.com/terraform/registry/providers/docs#callouts - MarkdownDescription: fmt.Sprintf("%s\n\n-> %s", descriptions["main"], descriptions["node_pools_plan_note"]), + MarkdownDescription: fmt.Sprintf("%s\n\n-> %s\n\n~> %s", descriptions["main"], descriptions["node_pools_plan_note"], descriptions["destroy_load_balancer_note"]), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`name`\".", @@ -2451,7 +2454,12 @@ func (r *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest _, err = skeWait.DeleteClusterWaitHandler(ctx, r.skeClient.DefaultAPI, projectId, region, name).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting cluster", fmt.Sprintf("Cluster deletion waiting: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting cluster", fmt.Sprintf( + "Cluster deletion waiting: %v.\n\n"+ + "A cluster can stay in STATE_DELETING because of resources it created outside of Terraform. "+ + "The most common one is a load balancer: a Service of type LoadBalancer makes the cloud controller "+ + "create one in the project, and it is not removed by deleting the cluster. Check with "+ + "`stackit load-balancer list -p %s` and remove any leftovers, then retry.", err, projectId)) return } tflog.Info(ctx, "SKE cluster deleted")