kubernetes/kops · error

instance group %q neither control-plane nor api-server

Error message

instance group %q neither control-plane nor api-server

What it means

createInternalLB (pkg/model/gcemodel/api_loadbalancer.go:184) classifies each API-serving instance group as control-plane or API-server-only; anything else cannot be attached to the API load balancer and is rejected. This catches specs where a non-control-plane, non-apiserver IG (e.g. a plain Node IG) is configured in a way that makes RunsAPIServer() true, or a role the classifier does not recognize.

Source

Thrown at pkg/model/gcemodel/api_loadbalancer.go:184

	for _, ig := range b.InstanceGroups {
		if !ig.RunsAPIServer() {
			continue
		}
		if len(ig.Spec.Zones) > 1 {
			return fmt.Errorf("instance group %q has %d zones, which is not yet supported for GCP", ig.GetName(), len(ig.Spec.Zones))
		}
		if len(ig.Spec.Zones) == 0 {
			return fmt.Errorf("instance group %q must specify exactly one zone", ig.GetName())
		}
		zone := ig.Spec.Zones[0]
		igm := &gcetasks.InstanceGroupManager{Name: s(gce.NameForInstanceGroupManager(b.Cluster.ObjectMeta.Name, ig.ObjectMeta.Name, zone)), Zone: s(zone)}
		apiIGMs = append(apiIGMs, igm)
		if ig.IsControlPlane() {
			controlPlaneIGMs = append(controlPlaneIGMs, igm)
		} else if ig.IsAPIServerOnly() {
			requireEtcdLB = b.Cluster.UsesNoneDNS()
		} else {
			return fmt.Errorf("instance group %q neither control-plane nor api-server", ig.GetName())
		}
	}
	backendService := &gcetasks.BackendService{
		Name:                  s(b.NameForBackendService("api")),
		Protocol:              s("TCP"),
		HealthChecks:          []*gcetasks.HealthCheck{hc},
		Lifecycle:             b.Lifecycle,
		LoadBalancingScheme:   s("INTERNAL"),
		InstanceGroupManagers: apiIGMs,
	}
	c.AddTask(backendService)

	// controlPlaneBS is a backend service that only targets ControlPlane MIGs.
	// It is used for kops-controller and etcd forwarding rules, which only run
	// on ControlPlane nodes. When there are no dedicated APIServer IGs, this is
	// the same set of backends as the API backend service.
	controlPlaneBS := backendService
	if b.HasAPIServerOnlyInstanceGroups() {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Inspect the named IG's spec.role and fix it to ControlPlane (Master), Node, or APIServer as intended
  2. If a Node IG is wrongly marked as API-serving (e.g. kubelet api-server related flags), revert it via 'kops edit ig'
  3. Regenerate the IG with 'kops create ig' rather than hand-editing internal fields
  4. Upgrade/downgrade kOps to a version matching the spec's role semantics

Example fix

// before
metadata:
  name: nodes
spec:
  role: Node
  machineType: n1-standard-2
  # plus stale apiserver-role fields left from a template
// after
metadata:
  name: apiserver
spec:
  role: APIServer
  machineType: n1-standard-2
Defensive patterns

Strategy: validation

Validate before calling

for _, ig := range instanceGroups {
	if ig.RunsAPIServer() && !ig.IsControlPlane() && !ig.IsAPIServerOnly() {
		return fmt.Errorf("IG %q is API-serving but role %q is neither ControlPlane nor APIServer", ig.Name, ig.Spec.Role)
	}
}

Type guard

func classifiableAPIIG(ig *kops.InstanceGroup) bool {
	return !ig.RunsAPIServer() || ig.IsControlPlane() || ig.IsAPIServerOnly()
}

Prevention

When it happens

Trigger: Looping over b.InstanceGroups, an IG with RunsAPIServer() true is neither IsControlPlane() nor IsAPIServerOnly() — typically a Node-role IG with an api-server role override, or a corrupted/legacy IG spec with role combinations the current kOps cannot classify.

Common situations: Hand-edited manifests mixing role fields; migrating clusters between kOps versions where the APIServer-only role handling changed; tooling that emits instance groups with unexpected role values.

Related errors


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