Skip to content

Commit 05a46db

Browse files
authored
Merge pull request kubernetes#95239 from danwinship/dual-stack-node-ips-basic
DualStack: basic dual-stack node IP handling
2 parents 0a14265 + 971477d commit 05a46db

File tree

17 files changed

+486
-71
lines changed

17 files changed

+486
-71
lines changed

cmd/kubelet/app/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ go_library(
124124
"//vendor/github.com/spf13/pflag:go_default_library",
125125
"//vendor/k8s.io/klog/v2:go_default_library",
126126
"//vendor/k8s.io/utils/exec:go_default_library",
127+
"//vendor/k8s.io/utils/net:go_default_library",
127128
] + select({
128129
"@io_bazel_rules_go//go/platform:android": [
129130
"//vendor/k8s.io/utils/inotify:go_default_library",

cmd/kubelet/app/options/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) {
325325

326326
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).")
327327

328-
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.")
328+
fs.StringVar(&f.NodeIP, "node-ip", f.NodeIP, "IP address (or comma-separated dual-stack IP addresses) of 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.")
329329

330330
fs.StringVar(&f.CertDirectory, "cert-dir", f.CertDirectory, "The directory where the TLS certs are located. "+
331331
"If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.")

cmd/kubelet/app/server.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import (
102102
"k8s.io/kubernetes/pkg/volume/util/hostutil"
103103
"k8s.io/kubernetes/pkg/volume/util/subpath"
104104
"k8s.io/utils/exec"
105+
utilnet "k8s.io/utils/net"
105106
)
106107

107108
const (
@@ -1086,6 +1087,27 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
10861087
// Setup event recorder if required.
10871088
makeEventRecorder(kubeDeps, nodeName)
10881089

1090+
var nodeIPs []net.IP
1091+
if kubeServer.NodeIP != "" {
1092+
for _, ip := range strings.Split(kubeServer.NodeIP, ",") {
1093+
parsedNodeIP := net.ParseIP(strings.TrimSpace(ip))
1094+
if parsedNodeIP == nil {
1095+
klog.Warningf("Could not parse --node-ip value %q; ignoring", ip)
1096+
} else {
1097+
nodeIPs = append(nodeIPs, parsedNodeIP)
1098+
}
1099+
}
1100+
}
1101+
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(nodeIPs) > 1 {
1102+
return fmt.Errorf("dual-stack --node-ip %q not supported in a single-stack cluster", kubeServer.NodeIP)
1103+
} else if len(nodeIPs) > 2 || (len(nodeIPs) == 2 && utilnet.IsIPv6(nodeIPs[0]) == utilnet.IsIPv6(nodeIPs[1])) {
1104+
return fmt.Errorf("bad --node-ip %q; must contain either a single IP or a dual-stack pair of IPs", kubeServer.NodeIP)
1105+
} else if len(nodeIPs) == 2 && kubeServer.CloudProvider != "" {
1106+
return fmt.Errorf("dual-stack --node-ip %q not supported when using a cloud provider", kubeServer.NodeIP)
1107+
} else if len(nodeIPs) == 2 && (nodeIPs[0].IsUnspecified() || nodeIPs[1].IsUnspecified()) {
1108+
return fmt.Errorf("dual-stack --node-ip %q cannot include '0.0.0.0' or '::'", kubeServer.NodeIP)
1109+
}
1110+
10891111
capabilities.Initialize(capabilities.Capabilities{
10901112
AllowPrivileged: true,
10911113
})
@@ -1104,7 +1126,7 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
11041126
hostname,
11051127
hostnameOverridden,
11061128
nodeName,
1107-
kubeServer.NodeIP,
1129+
nodeIPs,
11081130
kubeServer.ProviderID,
11091131
kubeServer.CloudProvider,
11101132
kubeServer.CertDirectory,
@@ -1178,7 +1200,7 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
11781200
hostname string,
11791201
hostnameOverridden bool,
11801202
nodeName types.NodeName,
1181-
nodeIP string,
1203+
nodeIPs []net.IP,
11821204
providerID string,
11831205
cloudProvider string,
11841206
certDirectory string,
@@ -1209,7 +1231,7 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
12091231
hostname,
12101232
hostnameOverridden,
12111233
nodeName,
1212-
nodeIP,
1234+
nodeIPs,
12131235
providerID,
12141236
cloudProvider,
12151237
certDirectory,

pkg/kubelet/kubelet.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
334334
hostname string,
335335
hostnameOverridden bool,
336336
nodeName types.NodeName,
337-
nodeIP string,
337+
nodeIPs []net.IP,
338338
providerID string,
339339
cloudProvider string,
340340
certDirectory string,
@@ -462,7 +462,6 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
462462
}
463463
}
464464
httpClient := &http.Client{}
465-
parsedNodeIP := net.ParseIP(nodeIP)
466465

467466
klet := &Kubelet{
468467
hostname: hostname,
@@ -477,7 +476,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
477476
registerNode: registerNode,
478477
registerWithTaints: registerWithTaints,
479478
registerSchedulable: registerSchedulable,
480-
dnsConfigurer: dns.NewConfigurer(kubeDeps.Recorder, nodeRef, parsedNodeIP, clusterDNS, kubeCfg.ClusterDomain, kubeCfg.ResolverConfig),
479+
dnsConfigurer: dns.NewConfigurer(kubeDeps.Recorder, nodeRef, nodeIPs, clusterDNS, kubeCfg.ClusterDomain, kubeCfg.ResolverConfig),
481480
serviceLister: serviceLister,
482481
serviceHasSynced: serviceHasSynced,
483482
nodeLister: nodeLister,
@@ -506,7 +505,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
506505
containerManager: kubeDeps.ContainerManager,
507506
containerRuntimeName: containerRuntime,
508507
redirectContainerStreaming: crOptions.RedirectContainerStreaming,
509-
nodeIP: parsedNodeIP,
508+
nodeIPs: nodeIPs,
510509
nodeIPValidator: validateNodeIP,
511510
clock: clock.RealClock{},
512511
enableControllerAttachDetach: kubeCfg.EnableControllerAttachDetach,
@@ -1042,8 +1041,8 @@ type Kubelet struct {
10421041
// oneTimeInitializer is used to initialize modules that are dependent on the runtime to be up.
10431042
oneTimeInitializer sync.Once
10441043

1045-
// If non-nil, use this IP address for the node
1046-
nodeIP net.IP
1044+
// If set, use this IP address or addresses for the node
1045+
nodeIPs []net.IP
10471046

10481047
// use this function to validate the kubelet nodeIP
10491048
nodeIPValidator func(net.IP) error

pkg/kubelet/kubelet_getters.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -262,23 +262,23 @@ func (kl *Kubelet) GetPodCgroupRoot() string {
262262
return kl.containerManager.GetPodCgroupRoot()
263263
}
264264

265-
// GetHostIP returns host IP or nil in case of error.
266-
func (kl *Kubelet) GetHostIP() (net.IP, error) {
265+
// GetHostIPs returns host IPs or nil in case of error.
266+
func (kl *Kubelet) GetHostIPs() ([]net.IP, error) {
267267
node, err := kl.GetNode()
268268
if err != nil {
269269
return nil, fmt.Errorf("cannot get node: %v", err)
270270
}
271-
return utilnode.GetNodeHostIP(node)
271+
return utilnode.GetNodeHostIPs(node)
272272
}
273273

274-
// getHostIPAnyway attempts to return the host IP from kubelet's nodeInfo, or
274+
// getHostIPsAnyWay attempts to return the host IPs from kubelet's nodeInfo, or
275275
// the initialNode.
276-
func (kl *Kubelet) getHostIPAnyWay() (net.IP, error) {
276+
func (kl *Kubelet) getHostIPsAnyWay() ([]net.IP, error) {
277277
node, err := kl.getNodeAnyWay()
278278
if err != nil {
279279
return nil, err
280280
}
281-
return utilnode.GetNodeHostIP(node)
281+
return utilnode.GetNodeHostIPs(node)
282282
}
283283

284284
// GetExtraSupplementalGroupsForPod returns a list of the extra

pkg/kubelet/kubelet_network_linux.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func (kl *Kubelet) initNetworkUtil() {
3535
exec := utilexec.New()
3636

3737
// At this point in startup we don't know the actual node IPs, so we configure dual stack iptables
38-
// rules if the node _might_ be dual-stack, and single-stack based on requested nodeIP otherwise.
38+
// rules if the node _might_ be dual-stack, and single-stack based on requested nodeIPs[0] otherwise.
3939
maybeDualStack := utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack)
40-
ipv6Primary := kl.nodeIP != nil && utilnet.IsIPv6(kl.nodeIP)
40+
ipv6Primary := kl.nodeIPs != nil && utilnet.IsIPv6(kl.nodeIPs[0])
4141

4242
var iptClients []utiliptables.Interface
4343
if maybeDualStack || !ipv6Primary {

pkg/kubelet/kubelet_node_status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(*v1.Node) error {
587587
}
588588
var setters []func(n *v1.Node) error
589589
setters = append(setters,
590-
nodestatus.NodeAddress(kl.nodeIP, kl.nodeIPValidator, kl.hostname, kl.hostnameOverridden, kl.externalCloudProvider, kl.cloud, nodeAddressesFunc),
590+
nodestatus.NodeAddress(kl.nodeIPs, kl.nodeIPValidator, kl.hostname, kl.hostnameOverridden, kl.externalCloudProvider, kl.cloud, nodeAddressesFunc),
591591
nodestatus.MachineInfo(string(kl.nodeName), kl.maxPods, kl.podsPerCore, kl.GetCachedMachineInfo, kl.containerManager.GetCapacity,
592592
kl.containerManager.GetDevicePluginResourceCapacity, kl.containerManager.GetNodeAllocatableReservation, kl.recordEvent),
593593
nodestatus.VersionInfo(kl.cadvisor.VersionInfo, kl.containerRuntime.Type, kl.containerRuntime.Version),

pkg/kubelet/kubelet_pods.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -811,11 +811,11 @@ func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod
811811
case "spec.serviceAccountName":
812812
return pod.Spec.ServiceAccountName, nil
813813
case "status.hostIP":
814-
hostIP, err := kl.getHostIPAnyWay()
814+
hostIPs, err := kl.getHostIPsAnyWay()
815815
if err != nil {
816816
return "", err
817817
}
818-
return hostIP.String(), nil
818+
return hostIPs[0].String(), nil
819819
case "status.podIP":
820820
return podIP, nil
821821
case "status.podIPs":
@@ -1531,14 +1531,17 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po
15311531
})
15321532

15331533
if kl.kubeClient != nil {
1534-
hostIP, err := kl.getHostIPAnyWay()
1534+
hostIPs, err := kl.getHostIPsAnyWay()
15351535
if err != nil {
1536-
klog.V(4).Infof("Cannot get host IP: %v", err)
1536+
klog.V(4).Infof("Cannot get host IPs: %v", err)
15371537
} else {
1538-
s.HostIP = hostIP.String()
1538+
s.HostIP = hostIPs[0].String()
15391539
if kubecontainer.IsHostNetworkPod(pod) && s.PodIP == "" {
1540-
s.PodIP = hostIP.String()
1540+
s.PodIP = hostIPs[0].String()
15411541
s.PodIPs = []v1.PodIP{{IP: s.PodIP}}
1542+
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(hostIPs) == 2 {
1543+
s.PodIPs = append(s.PodIPs, v1.PodIP{IP: hostIPs[1].String()})
1544+
}
15421545
}
15431546
}
15441547
}

pkg/kubelet/kubelet_pods_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"io/ioutil"
2323
"os"
2424
"path/filepath"
25+
"reflect"
2526
"sort"
2627
"testing"
2728

@@ -2489,3 +2490,126 @@ func TestPodResourcesAreReclaimed(t *testing.T) {
24892490
})
24902491
}
24912492
}
2493+
2494+
func TestGenerateAPIPodStatusHostNetworkPodIPs(t *testing.T) {
2495+
testcases := []struct {
2496+
name string
2497+
dualStack bool
2498+
nodeAddresses []v1.NodeAddress
2499+
criPodIPs []string
2500+
podIPs []v1.PodIP
2501+
}{
2502+
{
2503+
name: "Simple",
2504+
nodeAddresses: []v1.NodeAddress{
2505+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2506+
},
2507+
podIPs: []v1.PodIP{
2508+
{IP: "10.0.0.1"},
2509+
},
2510+
},
2511+
{
2512+
name: "InternalIP is preferred over ExternalIP",
2513+
nodeAddresses: []v1.NodeAddress{
2514+
{Type: v1.NodeExternalIP, Address: "192.168.0.1"},
2515+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2516+
},
2517+
podIPs: []v1.PodIP{
2518+
{IP: "10.0.0.1"},
2519+
},
2520+
},
2521+
{
2522+
name: "Dual-stack addresses are ignored in single-stack cluster",
2523+
nodeAddresses: []v1.NodeAddress{
2524+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2525+
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
2526+
},
2527+
podIPs: []v1.PodIP{
2528+
{IP: "10.0.0.1"},
2529+
},
2530+
},
2531+
{
2532+
name: "Single-stack addresses in dual-stack cluster",
2533+
nodeAddresses: []v1.NodeAddress{
2534+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2535+
},
2536+
dualStack: true,
2537+
podIPs: []v1.PodIP{
2538+
{IP: "10.0.0.1"},
2539+
},
2540+
},
2541+
{
2542+
name: "Multiple single-stack addresses in dual-stack cluster",
2543+
nodeAddresses: []v1.NodeAddress{
2544+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2545+
{Type: v1.NodeInternalIP, Address: "10.0.0.2"},
2546+
{Type: v1.NodeExternalIP, Address: "192.168.0.1"},
2547+
},
2548+
dualStack: true,
2549+
podIPs: []v1.PodIP{
2550+
{IP: "10.0.0.1"},
2551+
},
2552+
},
2553+
{
2554+
name: "Dual-stack addresses in dual-stack cluster",
2555+
nodeAddresses: []v1.NodeAddress{
2556+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2557+
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
2558+
},
2559+
dualStack: true,
2560+
podIPs: []v1.PodIP{
2561+
{IP: "10.0.0.1"},
2562+
{IP: "fd01::1234"},
2563+
},
2564+
},
2565+
{
2566+
name: "CRI PodIPs override NodeAddresses",
2567+
nodeAddresses: []v1.NodeAddress{
2568+
{Type: v1.NodeInternalIP, Address: "10.0.0.1"},
2569+
{Type: v1.NodeInternalIP, Address: "fd01::1234"},
2570+
},
2571+
dualStack: true,
2572+
criPodIPs: []string{"192.168.0.1"},
2573+
podIPs: []v1.PodIP{
2574+
{IP: "192.168.0.1"},
2575+
},
2576+
},
2577+
}
2578+
2579+
for _, tc := range testcases {
2580+
t.Run(tc.name, func(t *testing.T) {
2581+
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
2582+
defer testKubelet.Cleanup()
2583+
kl := testKubelet.kubelet
2584+
2585+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.IPv6DualStack, tc.dualStack)()
2586+
2587+
kl.nodeLister = testNodeLister{nodes: []*v1.Node{
2588+
{
2589+
ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)},
2590+
Status: v1.NodeStatus{
2591+
Addresses: tc.nodeAddresses,
2592+
},
2593+
},
2594+
}}
2595+
2596+
pod := podWithUIDNameNs("12345", "test-pod", "test-namespace")
2597+
pod.Spec.HostNetwork = true
2598+
2599+
criStatus := &kubecontainer.PodStatus{
2600+
ID: pod.UID,
2601+
Name: pod.Name,
2602+
Namespace: pod.Namespace,
2603+
IPs: tc.criPodIPs,
2604+
}
2605+
2606+
status := kl.generateAPIPodStatus(pod, criStatus)
2607+
if !reflect.DeepEqual(status.PodIPs, tc.podIPs) {
2608+
t.Fatalf("Expected PodIPs %#v, got %#v", tc.podIPs, status.PodIPs)
2609+
}
2610+
if tc.criPodIPs == nil && status.HostIP != status.PodIPs[0].IP {
2611+
t.Fatalf("Expected HostIP %q to equal PodIPs[0].IP %q", status.HostIP, status.PodIPs[0].IP)
2612+
}
2613+
})
2614+
}
2615+
}

pkg/kubelet/network/dns/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ go_library(
1515
"//staging/src/k8s.io/cri-api/pkg/apis/runtime/v1alpha2:go_default_library",
1616
"//vendor/k8s.io/klog/v2:go_default_library",
1717
"//vendor/k8s.io/utils/io:go_default_library",
18+
"//vendor/k8s.io/utils/net:go_default_library",
1819
],
1920
)
2021

0 commit comments

Comments
 (0)