diff --git a/cmd/kubelet/app/options/options.go b/cmd/kubelet/app/options/options.go index 7401c510deb1..76b83d5fd3f6 100644 --- a/cmd/kubelet/app/options/options.go +++ b/cmd/kubelet/app/options/options.go @@ -365,7 +365,7 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) { fs.StringVar(&f.HostnameOverride, "hostname-override", f.HostnameOverride, "If non-empty, will use this string as identification instead of the actual hostname. If --cloud-provider is set, the cloud provider determines the name of the node (consult cloud provider documentation to determine if and how the hostname is used).") - fs.StringVar(&f.NodeIP, "node-ip", f.NodeIP, "IP address of the node. If set, kubelet will use this IP address for the node") + fs.StringVar(&f.NodeIP, "node-ip", f.NodeIP, "IP address of the node. If set, kubelet will use this IP address for the node. If unset, kubelet will use the node's default IPv4 address, if any, or its default IPv6 address if it has no IPv4 addresses. You can pass `::` to make it prefer the default IPv6 address rather than the default IPv4 address.") fs.StringVar(&f.ProviderID, "provider-id", f.ProviderID, "Unique identifier for identifying the node in a machine database, i.e cloudprovider") diff --git a/pkg/kubelet/nodestatus/setters.go b/pkg/kubelet/nodestatus/setters.go index a5db1e7ca62f..5b94047ada00 100644 --- a/pkg/kubelet/nodestatus/setters.go +++ b/pkg/kubelet/nodestatus/setters.go @@ -65,8 +65,12 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP cloud cloudprovider.Interface, // typically Kubelet.cloud nodeAddressesFunc func() ([]v1.NodeAddress, error), // typically Kubelet.cloudResourceSyncManager.NodeAddresses ) Setter { + preferIPv4 := nodeIP == nil || nodeIP.To4() != nil + isPreferredIPFamily := func(ip net.IP) bool { return (ip.To4() != nil) == preferIPv4 } + nodeIPSpecified := nodeIP != nil && !nodeIP.IsUnspecified() + return func(node *v1.Node) error { - if nodeIP != nil { + if nodeIPSpecified { if err := validateNodeIPFunc(nodeIP); err != nil { return fmt.Errorf("failed to validate nodeIP: %v", err) } @@ -74,7 +78,7 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP } if externalCloudProvider { - if nodeIP != nil { + if nodeIPSpecified { if node.ObjectMeta.Annotations == nil { node.ObjectMeta.Annotations = make(map[string]string) } @@ -101,7 +105,7 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP // that address Type (like InternalIP and ExternalIP), meaning other addresses of the same Type are discarded. // See #61921 for more information: some cloud providers may supply secondary IPs, so nodeIP serves as a way to // ensure that the correct IPs show up on a Node object. - if nodeIP != nil { + if nodeIPSpecified { enforcedNodeAddresses := []v1.NodeAddress{} nodeIPTypes := make(map[v1.NodeAddressType]bool) @@ -125,6 +129,23 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP } nodeAddresses = enforcedNodeAddresses + } else if nodeIP != nil { + // nodeIP is "0.0.0.0" or "::"; sort cloudNodeAddresses to + // prefer addresses of the matching family + sortedAddresses := make([]v1.NodeAddress, 0, len(cloudNodeAddresses)) + for _, nodeAddress := range cloudNodeAddresses { + ip := net.ParseIP(nodeAddress.Address) + if ip == nil || isPreferredIPFamily(ip) { + sortedAddresses = append(sortedAddresses, nodeAddress) + } + } + for _, nodeAddress := range cloudNodeAddresses { + ip := net.ParseIP(nodeAddress.Address) + if ip != nil && !isPreferredIPFamily(ip) { + sortedAddresses = append(sortedAddresses, nodeAddress) + } + } + nodeAddresses = sortedAddresses } else { // If nodeIP is unset, just use the addresses provided by the cloud provider as-is nodeAddresses = cloudNodeAddresses @@ -168,12 +189,14 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP var ipAddr net.IP var err error - // 1) Use nodeIP if set + // 1) Use nodeIP if set (and not "0.0.0.0"/"::") // 2) If the user has specified an IP to HostnameOverride, use it - // 3) Lookup the IP from node name by DNS and use the first valid IPv4 address. - // If the node does not have a valid IPv4 address, use the first valid IPv6 address. + // 3) Lookup the IP from node name by DNS // 4) Try to get the IP from the network interface used as default gateway - if nodeIP != nil { + // + // For steps 3 and 4, IPv4 addresses are preferred to IPv6 addresses + // unless nodeIP is "::", in which case it is reversed. + if nodeIPSpecified { ipAddr = nodeIP } else if addr := net.ParseIP(hostname); addr != nil { ipAddr = addr @@ -182,18 +205,17 @@ func NodeAddress(nodeIP net.IP, // typically Kubelet.nodeIP addrs, _ = net.LookupIP(node.Name) for _, addr := range addrs { if err = validateNodeIPFunc(addr); err == nil { - if addr.To4() != nil { + if isPreferredIPFamily(addr) { ipAddr = addr break - } - if addr.To16() != nil && ipAddr == nil { + } else if ipAddr == nil { ipAddr = addr } } } if ipAddr == nil { - ipAddr, err = utilnet.ChooseHostInterface() + ipAddr, err = utilnet.ResolveBindAddress(nodeIP) } } diff --git a/pkg/kubelet/nodestatus/setters_test.go b/pkg/kubelet/nodestatus/setters_test.go index 7ed8b8b542c0..d3ac8a3dc950 100644 --- a/pkg/kubelet/nodestatus/setters_test.go +++ b/pkg/kubelet/nodestatus/setters_test.go @@ -293,6 +293,94 @@ func TestNodeAddress(t *testing.T) { hostnameOverride: true, shouldError: false, }, + { + name: "Dual-stack cloud, IPv4 first, no nodeIP", + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + shouldError: false, + }, + { + name: "Dual-stack cloud, IPv6 first, no nodeIP", + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + shouldError: false, + }, + { + name: "Dual-stack cloud, IPv4 first, request IPv4", + nodeIP: net.ParseIP("0.0.0.0"), + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + }, + shouldError: false, + }, + { + name: "Dual-stack cloud, IPv6 first, request IPv4", + nodeIP: net.ParseIP("0.0.0.0"), + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + }, + shouldError: false, + }, + { + name: "Dual-stack cloud, IPv4 first, request IPv6", + nodeIP: net.ParseIP("::"), + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + }, + shouldError: false, + }, + { + name: "Dual-stack cloud, IPv6 first, request IPv6", + nodeIP: net.ParseIP("::"), + nodeAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + }, + expectedAddresses: []v1.NodeAddress{ + {Type: v1.NodeInternalIP, Address: "fc01:1234::5678"}, + {Type: v1.NodeHostName, Address: testKubeletHostname}, + {Type: v1.NodeInternalIP, Address: "10.1.1.1"}, + }, + shouldError: false, + }, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) {