Change:
<!-- This form is for bug reports and feature requests ONLY! If you're looking for help check and the . --Is this a BUG REPORT or FEATURE REQUEST?: /kind bug What happened: For IPv6, the node CIDR size is hard coded to /64. This causes an error bringing up the kube-controller, as the size must be for a smaller subnet (larger value) that what is used for the pod subnet, which is specified for some CNI plugins (e.g. Calico). In addition, this is a catch-22, as the pod subnet prefix cannot be for a larger network (smaller value) than /66, so we cannot set it to /63, for example. What you expected to happen: The node CIDR size should be computed, based on the pod subnet specified, so that it can meet the requirements. How to reproduce it (as minimally and precisely as possible): Bring up a cluster using KubeAdm, specifying IPv6 and choose any pod subnet prefix desired. Anything else we need to know?: Note, there is a second requirement, that the difference between pod subnet size and node CIDR size be 16 or less (issue ). I'll be submitting a proposed PR to address this. Environment: Kubernetes version (use ): latest on master, using 1.9.2 in my case Cloud provider or hardware configuration: bare metal, single node OS (e.g. from /etc/os-release): Ubuntu 16.04 Kernel (e.g. ): Linux bxb-c2-77 4.4.0-109-generic -Ubuntu SMP Tue Jan 9 19:52:39 UTC 2018 x8664 x8664 x86_64 GNU/Linux Install tools: kubeadm Others: /area ipv6 /sig network
/assign pmichali

Discussion:
"net" "os" "path/filepath" <ins> "strconv" </ins> "strings" "k8s.io/api/core/v1"

return command } <ins> // calcNodeCidrSize determines the size of the subnets used on each node, based // on the pod subnet provided.  For IPv4, we assume that the pod subnet will // be /16 and use /24. If the pod subnet cannot be parsed, the IPv4 value will // be used (/24). // // For IPv6, the algorithm will do two three. First, the node CIDR will be set // to a multiple of 8, using the available bits for easier readability by user. // Second, the number of nodes will be 512 to 64K to attempt to maximize the // number of nodes (see NOTE below). Third, pod networks of /113 and larger will // be rejected, as the amount of bits available is too small. // // A special case is when the pod network size is /112, where /120 will be used, // only allowing 256 nodes and 256 pods. // // If the pod network size is /113 or larger, the node CIDR will be set to the same // size and this will be rejected later in validation. // // NOTE: Currently, the pod network must be /66 or larger. It is not reflected here, // but a smaller value will fail later validation. // // NOTE: Currently, the design allows a maximum of 64K nodes. This algorithm splits // the available bits to maximize the number used for nodes, but still have the node // CIDR be a multiple of eight. // func calcNodeCidrSize(podSubnet string) string { maskSize := "24" if ip, podCidr, err := net.ParseCIDR(podSubnet); err == nil { if ip.To4() == nil { var nodeCidrSize int podNetSize, totalBits := podCidr.Mask.Size() switch { case podNetSize == 112: // Special case, allows 256 nodes, 256 pods/node nodeCidrSize = 120 case podNetSize < 112: // Use multiple of 8 for node CIDR, with 512 to 64K nodes nodeCidrSize = totalBits - ((totalBits-podNetSize-1)/8-1)*8 default: // Not enough bits, will fail later, when validate nodeCidrSize = podNetSize } maskSize = strconv.Itoa(nodeCidrSize) } } return maskSize } </ins> // getControllerManagerCommand builds the right controller manager command from the given config object and version func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string { defaultArguments := map[string]string{

// Let the controller-manager allocate Node CIDRs for the Pod network. // Each node will get a subspace of the address CIDR provided with --pod-network-cidr. if cfg.Networking.PodSubnet != "" { <del> maskSize := "24" if ip, _, err := net.ParseCIDR(cfg.Networking.PodSubnet); err == nil { if ip.To4() == nil { maskSize = "64" } } </del> <ins> maskSize := calcNodeCidrSize(cfg.Networking.PodSubnet) </ins> command = append(command, "--allocate-node-cidrs=true", "--cluster-cidr="+cfg.Networking.PodSubnet, "--node-cidr-mask-size="+maskSize) }

}, { cfg: &kubeadmapi.MasterConfiguration{ <del> Networking:        kubeadmapi.Networking{PodSubnet: "2001:101:115::/48"}, </del> <ins> Networking:        kubeadmapi.Networking{PodSubnet: "2001:db8::/64"}, </ins> CertificatesDir:   testCertsDir, KubernetesVersion: "v1.7.0", },

"--use-service-account-credentials=true", "--controllers=*,bootstrapsigner,tokencleaner", "--allocate-node-cidrs=true", <del> "--cluster-cidr=2001:101:115::/48", "--node-cidr-mask-size=64", </del> <ins> "--cluster-cidr=2001:db8::/64", "--node-cidr-mask-size=80", </ins> }, }, }

} } <ins> func TestCalcNodeCidrSize(t *testing.T) { tests := []struct { name           string podSubnet      string expectedPrefix string }{ { name:           "Malformed pod subnet", podSubnet:      "10.10.10/160", expectedPrefix: "24", }, { name:           "V4: Always uses 24", podSubnet:      "10.10.10.10/16", expectedPrefix: "24", }, { name:           "V6: Use pod subnet size, when not enough space", podSubnet:      "2001:db8::/128", expectedPrefix: "128", }, { name:           "V6: Use pod subnet size, when not enough space", podSubnet:      "2001:db8::/113", expectedPrefix: "113", }, { name:           "V6: Special case with 256 nodes", podSubnet:      "2001:db8::/112", expectedPrefix: "120", }, { name:           "V6: Using /120 for node CIDR", podSubnet:      "2001:db8::/104", expectedPrefix: "120", }, { name:           "V6: Using /112 for node CIDR", podSubnet:      "2001:db8::/103", expectedPrefix: "112", }, { name:           "V6: Using /112 for node CIDR", podSubnet:      "2001:db8::/96", expectedPrefix: "112", }, { name:           "V6: Using /104 for node CIDR", podSubnet:      "2001:db8::/95", expectedPrefix: "104", }, { name:           "V6: Largest subnet currently supported", podSubnet:      "2001:db8::/66", expectedPrefix: "80", }, { name:           "V6: For /64 pod net, use /80", podSubnet:      "2001:db8::/64", expectedPrefix: "80", }, } for _, test := range tests { actualPrefix := calcNodeCidrSize(test.podSubnet) if actualPrefix != test.expectedPrefix { t.Errorf("Case [%s]nCalc of node CIDR size for pod subnet %q failed: Expected %q, saw %q", test.name, test.podSubnet, test.expectedPrefix, actualPrefix) } } } </ins> func TestGetControllerManagerCommandExternalCA(t *testing.T) { tests := []struct {