kubernetes/kops · error

error deleting server group

Error message

error deleting server group: %v

What it means

deleteServerGroup calls the Nova servergroups.Delete API. Errors are wrapped as "error deleting server group: %v" unless the error is a 404 (isNotFound), which is treated as success because the desired end state (group gone) is already reached. After deleteBackoff retries are exhausted, the wrapped error propagates.

Solutions

  1. Delete remaining instances in the server group first ('openstack server list --group <group-id>' then delete them), then retry.
  2. Check auth/roles: ensure the credentials own the server group and have compute:os-server-groups delete permissions.
  3. Read the wrapped HTTP status: 409 -> wait for in-flight operations on the group to finish; 401 -> refresh credentials.
  4. If the group is already gone, ignore — isNotFound maps it to success; verify with 'openstack server group show <group-id>'.
  5. Retry after transient nova-api failures; the internal backoff already retries persistent 5xx.

Example fix

// before: deleting group while instances still exist -> 403/409
if err := cloud.DeleteServerGroup(group.ID); err != nil { return err }
// after: drain members first
for _, inst := range membersOfGroup(group.ID) {
	if err := deleteServer(instance.ID); err != nil { return err }
}
if err := cloud.DeleteServerGroup(group.ID); err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

// Check group membership and ownership before deleting
members, _ := listServersInGroup(groupID)
if len(members) > 0 {
	return fmt.Errorf("server group %s still has %d members; delete instances first", groupID, len(members))
}

Try / catch

err := cloud.DeleteServerGroup(groupID)
if err != nil {
	var gerr gophercloud.ErrUnexpectedResponseCode
	if errors.As(err, &gerr) {
		switch gerr.Actual {
		case 403, 409:
			return fmt.Errorf("server group %s is busy or owned elsewhere; drain members and check permissions: %w", groupID, err)
		case 404:
			return nil // already gone
		}
	}
	if errors.Is(err, wait.ErrWaitTimeout) {
		return fmt.Errorf("server group %s deletion timed out after retries", groupID)
	}
	return err
}

Prevention

When it happens

Trigger: servergroups.Delete(...).ExtractErr() returns a non-404 error: 403 because the server group still has members or belongs to another project, 404 handled as success (not this error), 401 expired token, 409 conflict (group busy/being modified), or network failure to the compute endpoint.

Common situations: Tearing down a cluster whose server group still contains running instances the cloud forbids deleting; cross-project/role misconfiguration so the token cannot delete resources; nova-api outage during kops delete cluster; a hard 409 while another process mutates the group.

Related errors


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

Appendix: source

Thrown at upup/pkg/fi/cloudup/openstack/server_group.go:147

			klog.Warningf("Unable to find fixed ip for %s: %v", instance.Name, err)
		}

		cm.PrivateIP = ip
		cm.Roles = []string{instance.Metadata["KopsRole"]}
		cm.State = cloudinstances.State(instance.Status)
	}
	return cg, nil
}

func (c *openstackCloud) DeleteServerGroup(groupID string) error {
	return deleteServerGroup(c, groupID)
}

func deleteServerGroup(c OpenstackCloud, groupID string) error {
	done, err := vfs.RetryWithBackoff(deleteBackoff, func() (bool, error) {
		err := servergroups.Delete(context.TODO(), c.ComputeClient(), groupID).ExtractErr()
		if err != nil && !isNotFound(err) {
			return false, fmt.Errorf("error deleting server group: %v", err)
		}
		if isNotFound(err) {
			return true, nil
		}
		return false, nil
	})
	if err != nil {
		return err
	} else if done {
		return nil
	} else {
		return wait.ErrWaitTimeout
	}
}

View on GitHub (pinned to 4c8573c808)