kubernetes/kubernetes · critical

serviceCIDR and secondaryServiceCIDR are not dualstack (from

Error message

serviceCIDR and secondaryServiceCIDR are not dualstack (from different IPfamiles)

What it means

Fires when both ServiceCIDR and SecondaryServiceCIDR are provided and parse cleanly, netutils.IsDualStackCIDRs returns no error, but the boolean result is false — meaning both CIDRs belong to the same IP family (both IPv4 or both IPv6). KCM requires the secondary range to be the opposite family so services can be dual-stack.

Source

Thrown at cmd/kube-controller-manager/app/core.go:128

		}
	}

	if len(strings.TrimSpace(controllerContext.ComponentConfig.NodeIPAMController.SecondaryServiceCIDR)) != 0 {
		_, secondaryServiceCIDR, err = netutils.ParseCIDRSloppy(controllerContext.ComponentConfig.NodeIPAMController.SecondaryServiceCIDR)
		if err != nil {
			logger.Info("Warning: unsuccessful parsing of service CIDR", "CIDR", controllerContext.ComponentConfig.NodeIPAMController.SecondaryServiceCIDR, "err", err)
		}
	}

	// the following checks are triggered if both serviceCIDR and secondaryServiceCIDR are provided
	if serviceCIDR != nil && secondaryServiceCIDR != nil {
		// should be dual stack (from different IPFamilies)
		dualstackServiceCIDR, err := netutils.IsDualStackCIDRs([]*net.IPNet{serviceCIDR, secondaryServiceCIDR})
		if err != nil {
			return nil, fmt.Errorf("failed to perform dualstack check on serviceCIDR and secondaryServiceCIDR error: %w", err)
		}
		if !dualstackServiceCIDR {
			return nil, fmt.Errorf("serviceCIDR and secondaryServiceCIDR are not dualstack (from different IPfamiles)")
		}
	}

	// only --node-cidr-mask-size-ipv4 and --node-cidr-mask-size-ipv6 supported with dual stack clusters.
	// --node-cidr-mask-size flag is incompatible with dual stack clusters.
	nodeCIDRMaskSizes, err := setNodeCIDRMaskSizes(controllerContext.ComponentConfig.NodeIPAMController, clusterCIDRs)
	if err != nil {
		return nil, err
	}

	client, err := controllerContext.NewClient("node-controller")
	if err != nil {
		return nil, err
	}

	nodeIpamController, err := nodeipamcontroller.NewNodeIpamController(
		ctx,
		controllerContext.InformerFactory.Core().V1().Nodes(),

View on GitHub (pinned to b882c60b40)

Solutions

  1. Make the secondary CIDR the opposite family of the primary (IPv6 if primary is IPv4, vice versa)
  2. If dual-stack is not the goal, remove --secondary-service-cluster-ip-range entirely
  3. Validate with: python3 -c "import ipaddress; print(ipaddress.ip_network('fd00::/112').version)" for each range

Example fix

# before
--service-cluster-ip-range=10.96.0.0/12
--secondary-service-cluster-ip-range=10.97.0.0/16   # both IPv4 -> fails
# after
--service-cluster-ip-range=10.96.0.0/12
--secondary-service-cluster-ip-range=fd00:1234::/112
Defensive patterns

Strategy: validation

Validate before calling

func assertDualStackFamilies(primary, secondary string) error {
    _, p, _ := net.ParseCIDR(primary)
    _, s, _ := net.ParseCIDR(secondary)
    if p == nil || s == nil { return errors.New("invalid CIDR") }
    if (p.IP.To4() == nil) == (s.IP.To4() == nil) {
        return errors.New("primary and secondary service CIDRs share an IP family; make one IPv4 and one IPv6")
    }
    return nil
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Operator sets --service-cluster-ip-range and --secondary-service-cluster-ip-range to two IPv4 CIDRs (or two IPv6 CIDRs) intending to widen the address space rather than enable dual-stack. The IsDualStackCIDRs check at core.go:127 returns false and KCM refuses to start.

Common situations: Adding a second IPv4 service range to avoid exhaustion without realizing the secondary flag is reserved for the other family; migrating to dual-stack by copying the primary CIDR and editing only the prefix; docs/templates that show two ranges of the same family.

Related errors


AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07). Data as JSON: /api/errors/6c1a6e434c22d2b1. Report an issue: GitHub.