kubernetes/kops · error

error deleting Subnetwork %s: %v

Error message

error deleting Subnetwork %s: %v

What it means

Thrown by deleteSubnet in pkg/resources/gce/gce.go when the GCE Compute Subnetworks().Delete API returns an error other than NotFound (which is treated as already-deleted success). The message wraps the subnetwork self-link and the underlying Google API error. It signals cluster teardown failed to remove the subnetwork resource.

Source

Thrown at pkg/resources/gce/gce.go:952

}

func deleteSubnet(cloud fi.Cloud, r *resources.Resource) error {
	c := cloud.(gce.GCECloud)
	o := r.Obj.(*compute.Subnetwork)

	klog.V(2).Infof("deleting GCE Subnetwork %s", o.SelfLink)
	u, err := gce.ParseGoogleCloudURL(o.SelfLink)
	if err != nil {
		return err
	}

	op, err := c.Compute().Subnetworks().Delete(u.Project, u.Region, u.Name)
	if err != nil {
		if gce.IsNotFound(err) {
			klog.Infof("Subnetwork not found, assuming deleted: %q", o.SelfLink)
			return nil
		}
		return fmt.Errorf("error deleting Subnetwork %s: %v", o.SelfLink, err)
	}

	return c.WaitForOp(op)
}

func (d *clusterDiscoveryGCE) listRouters() ([]*resources.Resource, error) {
	c := d.gceCloud

	var resourceTrackers []*resources.Resource
	ctx := context.Background()

	routers, err := c.Compute().Routers().List(ctx, c.Project(), c.Region())
	if err != nil {
		return nil, fmt.Errorf("error listing routers: %v", err)
	}

	for _, o := range routers {
		if !d.matchesClusterName(o.Name) {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Inspect the wrapped %v error to find the root cause (403 permission, 400 failedPrecondition 'still in use', 429 rate limit, 5xx).
  2. Delete dependent resources first (forwarding rules, target pools, instances using the subnet), then retry deletion.
  3. Grant the service account compute.networkAdmin (or compute.subnetworks.delete permission) in the project.
  4. Retry after backoff if the cause is a transient 429/5xx; kops delete cluster is idempotent (NotFound is tolerated).

Example fix

// before
return fmt.Errorf("error deleting Subnetwork %s: %v", o.SelfLink, err)
// after (retry transient errors before failing)
if gce.IsNotFound(err) {
	return nil
}
if apiErr, ok := err.(*googleapi.Error); ok && (apiErr.Code == 429 || apiErr.Code >= 500) {
	return fmt.Errorf("error deleting Subnetwork %s (transient, retry): %w", o.SelfLink, err)
}
return fmt.Errorf("error deleting Subnetwork %s: %w", o.SelfLink, err)
Defensive patterns

Strategy: retry

Validate before calling

// before teardown, check subnet dependencies
for _, subnet := range []string{"mysubnet"} {
	inUse, _ := subnetHasForwardingRules(project, region, subnet)
	if inUse {
		return fmt.Errorf("subnet %s still referenced; clean up first", subnet)
	}
}

Type guard

func isGoogleAPIError(err error) (*googleapi.Error, bool) {
	ge, ok := err.(*googleapi.Error)
	return ge, ok
}

Try / catch

// treat NotFound as success, retry transient
call := func() error { return deleteSubnet(project, region, name) }
if err := retry.OnError(wait.Backoff{Steps: 5, Duration: time.Second}, isTransient, call); err != nil {
	klog.Errorf("subnet delete failed permanently: %v", err)
}

Prevention

When it happens

Trigger: Calling kops delete cluster on GCE where the subnetwork delete API call fails with a non-404 error: e.g. the subnetwork is still in use by resources (forwarding rules, instances with alias IPs), permission denied on the compute service account, quota/billing issues, or transient Google API 5xx errors.

Common situations: Cluster deletion fails partway leaving orphaned resources that still reference the subnet; IAM roles for the deletion service account lack compute.subnetworks.delete; regional subnet still used by a lingering instance group or load balancer; API outage/rate limiting during teardown.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/94657cb5b96c65b3. Report an issue: GitHub.