kubernetes/kops · error

instance group %q has %d zones, which is not yet supported f

Error message

instance group %q has %d zones, which is not yet supported for GCP

What it means

The GCE internal API load balancer model (pkg/model/gcemodel/api_loadbalancer.go:171) creates one regional InstanceGroupManager per zone. GCE internal LB support in kOps requires each API-serving instance group to be pinned to exactly one zone; if an IG spans multiple zones, createInternalLB cannot map it and returns this error naming the IG and zone count.

Source

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

		Name:      s(b.NameForHealthCheck("api")),
		Port:      wellknownports.KubeAPIServer,
		Protocol:  gcetasks.HealthCheckProtocolTCP,
		Lifecycle: b.Lifecycle,
	}
	c.AddTask(hc)

	// Collect ControlPlane and APIServer MIGs separately. The API backend service
	// includes both (both serve the kube-apiserver), while the kops-controller and
	// etcd backend services only include ControlPlane MIGs.
	var apiIGMs []*gcetasks.InstanceGroupManager
	var controlPlaneIGMs []*gcetasks.InstanceGroupManager // Currently these contain etcd instances
	requireEtcdLB := false
	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"),

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Split the API-serving instance group into one IG per zone, each with a single zone in spec.zones
  2. Or configure a public load balancer / DNS-based API endpoint if multi-zone internal LB is not supported by your kOps version
  3. Upgrade kOps — newer versions may have relaxed this limitation; check release notes before restructuring
  4. Run 'kops update cluster' to validate after re-zoning

Example fix

// before
spec:
  role: ControlPlane
  zones: [us-central1-a, us-central1-b]
// after (two instance groups)
- role: ControlPlane
  zones: [us-central1-a]
- role: ControlPlane
  zones: [us-central1-b]
Defensive patterns

Strategy: validation

Validate before calling

for _, ig := range instanceGroups {
	if ig.Spec.Role == kops.InstanceGroupRoleControlPlane && len(ig.Spec.Zones) > 1 {
		return fmt.Errorf("IG %q: GCP internal LB needs one zone per IG; split %v into separate IGs", ig.Name, ig.Spec.Zones)
	}
}

Type guard

func singleZoneAPIIG(ig *kops.InstanceGroup) bool {
	return !ig.RunsAPIServer() || len(ig.Spec.Zones) == 1
}

Prevention

When it happens

Trigger: createInternalLB (called from Build) encounters an instance group with RunsAPIServer() true whose spec.zones has length > 1 — e.g. a control-plane IG with zones [us-central1-a, us-central1-b] while using an internal (None-DNS) API load balancer.

Common situations: Multi-zone GCP clusters configured before GCP internal LB multi-zone support existed; templates built for AWS (which allows multi-zone IGs) reused for GCP; users adding zones to the control-plane IG for HA.

Related errors


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