Skip to content

Commit 971477d

Browse files
committed
kubelet: Set dual-stack hostNetwork pod IPs on dual-stack nodes
Add nodeutil.GetNodeHostIPs to return dual-stack node IPs (in dual-stack clusters), and make kubelet use it.
1 parent 0b43753 commit 971477d

File tree

7 files changed

+345
-26
lines changed

7 files changed

+345
-26
lines changed

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_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/volume_host.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,11 @@ func (kvh *kubeletVolumeHost) GetHostName() string {
231231
}
232232

233233
func (kvh *kubeletVolumeHost) GetHostIP() (net.IP, error) {
234-
return kvh.kubelet.GetHostIP()
234+
hostIPs, err := kvh.kubelet.GetHostIPs()
235+
if err != nil {
236+
return nil, err
237+
}
238+
return hostIPs[0], err
235239
}
236240

237241
func (kvh *kubeletVolumeHost) GetNodeAllocatable() (v1.ResourceList, error) {

pkg/util/node/BUILD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ go_library(
1111
srcs = ["node.go"],
1212
importpath = "k8s.io/kubernetes/pkg/util/node",
1313
deps = [
14+
"//pkg/features:go_default_library",
1415
"//staging/src/k8s.io/api/core/v1:go_default_library",
1516
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
1617
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
1718
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
1819
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
1920
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
21+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
2022
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
2123
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
2224
"//vendor/k8s.io/klog/v2:go_default_library",
25+
"//vendor/k8s.io/utils/net:go_default_library",
2326
],
2427
)
2528

@@ -28,8 +31,11 @@ go_test(
2831
srcs = ["node_test.go"],
2932
embed = [":go_default_library"],
3033
deps = [
34+
"//pkg/features:go_default_library",
3135
"//staging/src/k8s.io/api/core/v1:go_default_library",
3236
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
37+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
38+
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
3339
],
3440
)
3541

pkg/util/node/node.go

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ import (
3333
"k8s.io/apimachinery/pkg/types"
3434
"k8s.io/apimachinery/pkg/util/strategicpatch"
3535
"k8s.io/apimachinery/pkg/util/wait"
36+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3637
clientset "k8s.io/client-go/kubernetes"
3738
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
39+
"k8s.io/kubernetes/pkg/features"
40+
utilnet "k8s.io/utils/net"
3841
)
3942

4043
const (
@@ -90,22 +93,55 @@ func GetPreferredNodeAddress(node *v1.Node, preferredAddressTypes []v1.NodeAddre
9093
return "", &NoMatchError{addresses: node.Status.Addresses}
9194
}
9295

93-
// GetNodeHostIP returns the provided node's IP, based on the priority:
94-
// 1. NodeInternalIP
95-
// 2. NodeExternalIP
96-
func GetNodeHostIP(node *v1.Node) (net.IP, error) {
97-
addresses := node.Status.Addresses
98-
addressMap := make(map[v1.NodeAddressType][]v1.NodeAddress)
99-
for i := range addresses {
100-
addressMap[addresses[i].Type] = append(addressMap[addresses[i].Type], addresses[i])
96+
// GetNodeHostIPs returns the provided node's IP(s); either a single "primary IP" for the
97+
// node in a single-stack cluster, or a dual-stack pair of IPs in a dual-stack cluster
98+
// (for nodes that actually have dual-stack IPs). Among other things, the IPs returned
99+
// from this function are used as the `.status.PodIPs` values for host-network pods on the
100+
// node, and the first IP is used as the `.status.HostIP` for all pods on the node.
101+
func GetNodeHostIPs(node *v1.Node) ([]net.IP, error) {
102+
// Re-sort the addresses with InternalIPs first and then ExternalIPs
103+
allIPs := make([]net.IP, 0, len(node.Status.Addresses))
104+
for _, addr := range node.Status.Addresses {
105+
if addr.Type == v1.NodeInternalIP {
106+
ip := net.ParseIP(addr.Address)
107+
if ip != nil {
108+
allIPs = append(allIPs, ip)
109+
}
110+
}
111+
}
112+
for _, addr := range node.Status.Addresses {
113+
if addr.Type == v1.NodeExternalIP {
114+
ip := net.ParseIP(addr.Address)
115+
if ip != nil {
116+
allIPs = append(allIPs, ip)
117+
}
118+
}
101119
}
102-
if addresses, ok := addressMap[v1.NodeInternalIP]; ok {
103-
return net.ParseIP(addresses[0].Address), nil
120+
if len(allIPs) == 0 {
121+
return nil, fmt.Errorf("host IP unknown; known addresses: %v", node.Status.Addresses)
122+
}
123+
124+
nodeIPs := []net.IP{allIPs[0]}
125+
if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
126+
for _, ip := range allIPs {
127+
if utilnet.IsIPv6(ip) != utilnet.IsIPv6(nodeIPs[0]) {
128+
nodeIPs = append(nodeIPs, ip)
129+
break
130+
}
131+
}
104132
}
105-
if addresses, ok := addressMap[v1.NodeExternalIP]; ok {
106-
return net.ParseIP(addresses[0].Address), nil
133+
134+
return nodeIPs, nil
135+
}
136+
137+
// GetNodeHostIP returns the provided node's "primary" IP; see GetNodeHostIPs for more details
138+
func GetNodeHostIP(node *v1.Node) (net.IP, error) {
139+
ips, err := GetNodeHostIPs(node)
140+
if err != nil {
141+
return nil, err
107142
}
108-
return nil, fmt.Errorf("host IP unknown; known addresses: %v", addresses)
143+
// GetNodeHostIPs always returns at least one IP if it didn't return an error
144+
return ips[0], nil
109145
}
110146

111147
// GetNodeIP returns an IP (as with GetNodeHostIP) for the node with the provided name.

0 commit comments

Comments
 (0)