k3s-io/k3s · error

invalid node-external-ip: %w

Error message

invalid node-external-ip: %w

What it means

The agent parses envInfo.NodeExternalIP (the --node-external-ip flag, comma-separated) with util.ParseStringSliceToIPs, which accepts bare IP addresses only; if any element fails net.ParseIP the underlying error is wrapped as 'invalid node-external-ip: ...'. CIDRs, hostnames, ranges and empty elements (trailing comma) are all invalid.

Source

Thrown at pkg/agent/config/config.go:505

	if err := os.MkdirAll(nodeConfigPath, 0755); err != nil {
		return nil, err
	}

	oldNodePasswordFile := filepath.Join(envInfo.DataDir, "agent", "node-password.txt")
	newNodePasswordFile := filepath.Join(nodeConfigPath, "password")
	upgradeOldNodePasswordPath(oldNodePasswordFile, newNodePasswordFile)

	if controlConfig.ClusterIPRange != nil {
		if utilsnet.IPFamilyOfCIDR(controlConfig.ClusterIPRange) != utilsnet.IPFamilyOf(nodeIPs[0]) && len(nodeIPs) > 1 {
			firstNodeIP := nodeIPs[0]
			nodeIPs[0] = nodeIPs[1]
			nodeIPs[1] = firstNodeIP
		}
	}

	nodeExternalIPs, err := util.ParseStringSliceToIPs(envInfo.NodeExternalIP.Value())
	if err != nil {
		return nil, fmt.Errorf("invalid node-external-ip: %w", err)
	}

	if envInfo.WithNodeID {
		nodeID, err := ensureNodeID(filepath.Join(nodeConfigPath, "id"))
		if err != nil {
			return nil, err
		}
		nodeName += "-" + nodeID
	}

	os.Setenv("NODE_NAME", nodeName)

	kubeconfigKubelet := filepath.Join(envInfo.DataDir, "agent", "kubelet.kubeconfig")
	clientKubeProxyCert := filepath.Join(envInfo.DataDir, "agent", "client-kube-proxy.crt")
	clientKubeProxyKey := filepath.Join(envInfo.DataDir, "agent", "client-kube-proxy.key")
	kubeconfigKubeproxy := filepath.Join(envInfo.DataDir, "agent", "kubeproxy.kubeconfig")
	clientK3sControllerCert := filepath.Join(envInfo.DataDir, "agent", "client-"+version.Program+"-controller.crt")
	clientK3sControllerKey := filepath.Join(envInfo.DataDir, "agent", "client-"+version.Program+"-controller.key")

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Pass plain IPs only: --node-external-ip=203.0.113.7 (or a comma-separated list of IPs)
  2. Resolve hostnames to IPs yourself before passing the flag
  3. Strip whitespace and drop empty items from the value

Example fix

# before
--node-external-ip="203.0.113.7/32,"

# after
--node-external-ip="203.0.113.7"
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range strings.Split(value, ",") {
    s = strings.TrimSpace(s)
    if s == "" || net.ParseIP(s) == nil {
        return fmt.Errorf("--node-external-ip accepts bare IPs only, got %q", s)
    }
}

Type guard

func validExternalIPs(v string) bool {
    for _, s := range strings.Split(v, ",") {
        if net.ParseIP(strings.TrimSpace(s)) == nil {
            return false
        }
    }
    return true
}

Prevention

When it happens

Trigger: --node-external-ip=203.0.113.7/32 (CIDR), --node-external-ip=nat.example.com (hostname), '1.2.3.4,' (trailing comma), whitespace inside elements, or a malformed IPv6 literal.

Common situations: Copy/pasting a CIDR from cluster-cidr docs; templates injecting an unset variable leaving stray commas; expecting hostname resolution where none is supported.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/d580f32feb5f5680. Report an issue: GitHub.