{"record":{"id":"5a1af43596d9eb3f","repo":"kubernetes/kops","slug":"cannot-mix-publicip-values-in-private-or-ipv6-capa","errorCode":null,"errorMessage":"cannot mix publicIP values in private or IPv6-capable subnets","messagePattern":"cannot mix publicIP values in private or IPv6-capable subnets","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/model/awsmodel/network.go","lineNumber":435,"sourceCode":"\n\t\tallUnmanaged := true\n\t\tfor _, subnetSpec := range info.NATSubnets {\n\t\t\tif !isUnmanaged(subnetSpec) {\n\t\t\t\tallUnmanaged = false\n\t\t\t}\n\t\t}\n\t\tif allUnmanaged {\n\t\t\tklog.V(4).Infof(\"skipping network configuration in zone %s - all subnets unmanaged\", zone)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Verify we don't have mixed values for egress/publicIP - the code doesn't handle it\n\t\tfor _, subnet := range info.NATSubnets {\n\t\t\tif subnet.Egress != egress {\n\t\t\t\treturn fmt.Errorf(\"cannot mix egress values in private or IPv6-capable subnets\")\n\t\t\t}\n\t\t\tif subnet.PublicIP != publicIP {\n\t\t\t\treturn fmt.Errorf(\"cannot mix publicIP values in private or IPv6-capable subnets\")\n\t\t\t}\n\t\t}\n\n\t\tvar ngw *awstasks.NatGateway\n\t\tvar tgwID *string\n\t\tvar in *awstasks.Instance\n\t\tif egress != \"\" {\n\t\t\tif strings.HasPrefix(egress, \"nat-\") {\n\n\t\t\t\tngw = &awstasks.NatGateway{\n\t\t\t\t\tName:                 new(zone + \".\" + b.ClusterName()),\n\t\t\t\t\tLifecycle:            b.Lifecycle,\n\t\t\t\t\tSubnet:               egressSubnet,\n\t\t\t\t\tID:                   new(egress),\n\t\t\t\t\tAssociatedRouteTable: egressRouteTable,\n\t\t\t\t\t// If we're here, it means this NatGateway was specified, so we are Shared\n\t\t\t\t\tShared: new(true),\n\t\t\t\t\tTags:   b.CloudTags(zone+\".\"+b.ClusterName(), true),","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/model/awsmodel/network.go#L417-L453","documentation":"The same per-zone consistency check in the AWS network builder also requires every NAT-routing subnet in a zone to share the same `publicIP` value (an explicitly requested Elastic IP allocation for the zone's NAT gateway). If subnets in one zone declare different `publicIP` values, the builder cannot decide which EIP to associate and fails. Raised at model-build time during `kops update cluster`.","triggerScenarios":"`kops update cluster` where two or more private/IPv6-capable subnets in the same zone specify different `publicIP` fields (e.g. one set to an existing EIP allocation string and another left empty or set to a different EIP).","commonSituations":"Reusing pre-created Elastic IPs across a zone but forgetting to set publicIP on every subnet block; templating that injects publicIP only into some subnets; adopting an existing cluster and annotating only part of the subnets.","solutions":["Make `publicIP` identical (all set to the same EIP, or all empty) for every private/IPv6 subnet in the affected zone.","To reuse one EIP per zone, set it on every subnet spec in that zone, not just the first.","Apply via `kops replace -f cluster.yaml` or `kops edit cluster`, then re-run `kops update cluster`.","If you need distinct EIPs per subnet, split the subnets across zones — the check is per-zone."],"exampleFix":"# before\n- name: us-east-1a-private\n  type: Private\n  zone: us-east-1a\n  publicIP: eipalloc-0aaa\n- name: us-east-1a-private2\n  type: Private\n  zone: us-east-1a\n  publicIP: eipalloc-0bbb\n# after\n- name: us-east-1a-private\n  type: Private\n  zone: us-east-1a\n  publicIP: eipalloc-0aaa\n- name: us-east-1a-private2\n  type: Private\n  zone: us-east-1a\n  publicIP: eipalloc-0aaa","handlingStrategy":"validation","validationCode":"#!/bin/bash\n# Fail fast if publicIP values differ within a zone\nkops get cluster -oyaml | yq '.spec.subnets | group_by(.zone) | .[] | select((map(.publicIP // \"\") | unique | length) > 1) | .[0].zone' | grep . && echo \"mixed publicIP in zone\" && exit 1 || true","typeGuard":"func uniformPublicIPPerZone(subnets []kops.ClusterSubnetSpec) bool {\n    m := map[string]struct{}{}\n    for _, s := range subnets {\n        m[s.Zone+\"|\"+s.PublicIP] = struct{}{}\n    }\n    zones := map[string]struct{}{}\n    for k := range m {\n        zones[strings.SplitN(k, \"|\", 2)[0]] = struct{}{}\n    }\n    return len(m) == len(zones)\n}","tryCatchPattern":"err := updateCluster(ctx)\nif err != nil && strings.Contains(err.Error(), \"cannot mix publicIP values\") {\n    // set the same publicIP on every private subnet in the offending zone, then re-run\n}","preventionTips":["When reusing a pre-created EIP for a zone, set publicIP on EVERY private subnet block in that zone.","Keep publicIP empty everywhere and let kops allocate/track Elastic IPs unless reuse is required.","Add a spec-lint step (yq/jq script) to CI that asserts one publicIP per zone.","Document EIP reuse in your cluster template so operators don't annotate only some subnets."],"tags":["aws","networking","elastic-ip","cluster-spec"],"backgroundTag":"mixed-publicip-subnet-config","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}