Skip to content

Commit e46572e

Browse files
committed
Improve EndpointController's handling of headless services under dual-stack
EndpointController was accidentally requiring all headless services to be IPv4-only in clusters with IPv6DualStack enabled. This still leaves "legacy" (ie, IPFamily-less) headless services as always IPv4-only because the controller doesn't currently have easy access to the information that would allow it to fix that. (EndpointSliceController had the same problem already, and still does.) This can be fixed, if needed, by manually setting IPFamily, and the proposed API for 1.20 will handle this situation better.
1 parent 9023d19 commit e46572e

File tree

6 files changed

+39
-32
lines changed

6 files changed

+39
-32
lines changed

pkg/controller/endpoint/endpoints_controller.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,19 +215,13 @@ func podToEndpointAddressForService(svc *v1.Service, pod *v1.Pod) (*v1.EndpointA
215215
var endpointIP string
216216

217217
if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
218+
// In a legacy cluster, the pod IP is guaranteed to be usable
218219
endpointIP = pod.Status.PodIP
219220
} else {
220-
// api-server service controller ensured that the service got the correct IP Family
221-
// according to user setup, here we only need to match EndPoint IPs' family to service
222-
// actual IP family. as in, we don't need to check service.IPFamily
223-
224-
ipv6ClusterIP := utilnet.IsIPv6String(svc.Spec.ClusterIP)
221+
ipv6Service := endpointutil.IsIPv6Service(svc)
225222
for _, podIP := range pod.Status.PodIPs {
226223
ipv6PodIP := utilnet.IsIPv6String(podIP.IP)
227-
// same family?
228-
// TODO (khenidak) when we remove the max of 2 PodIP limit from pods
229-
// we will have to return multiple endpoint addresses
230-
if ipv6ClusterIP == ipv6PodIP {
224+
if ipv6Service == ipv6PodIP {
231225
endpointIP = podIP.IP
232226
break
233227
}

pkg/controller/endpoint/endpoints_controller_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,21 +1249,21 @@ func TestPodToEndpointAddressForService(t *testing.T) {
12491249

12501250
expectedEndpointFamily: ipv6,
12511251
},
1252-
// {
1253-
// name: "v6 headless service, in a dual stack cluster",
1254-
//
1255-
// enableDualStack: true,
1256-
// ipFamilies: ipv4ipv6,
1257-
//
1258-
// service: v1.Service{
1259-
// Spec: v1.ServiceSpec{
1260-
// ClusterIP: v1.ClusterIPNone,
1261-
// IPFamily: &ipv6,
1262-
// },
1263-
// },
1264-
//
1265-
// expectedEndpointFamily: ipv6,
1266-
// },
1252+
{
1253+
name: "v6 headless service, in a dual stack cluster",
1254+
1255+
enableDualStack: true,
1256+
ipFamilies: ipv4ipv6,
1257+
1258+
service: v1.Service{
1259+
Spec: v1.ServiceSpec{
1260+
ClusterIP: v1.ClusterIPNone,
1261+
IPFamily: &ipv6,
1262+
},
1263+
},
1264+
1265+
expectedEndpointFamily: ipv6,
1266+
},
12671267
{
12681268
name: "v6 legacy headless service, in a dual stack cluster",
12691269

pkg/controller/endpointslice/reconciler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type endpointMeta struct {
5959
func (r *reconciler) reconcile(service *corev1.Service, pods []*corev1.Pod, existingSlices []*discovery.EndpointSlice, triggerTime time.Time) error {
6060
addressType := discovery.AddressTypeIPv4
6161

62-
if isIPv6Service(service) {
62+
if endpointutil.IsIPv6Service(service) {
6363
addressType = discovery.AddressTypeIPv6
6464
}
6565

pkg/controller/endpointslice/utils.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,14 @@ func getEndpointAddresses(podStatus corev1.PodStatus, service *corev1.Service) [
120120

121121
for _, podIP := range podStatus.PodIPs {
122122
isIPv6PodIP := utilnet.IsIPv6String(podIP.IP)
123-
if isIPv6PodIP == isIPv6Service(service) {
123+
if isIPv6PodIP == endpointutil.IsIPv6Service(service) {
124124
addresses = append(addresses, podIP.IP)
125125
}
126126
}
127127

128128
return addresses
129129
}
130130

131-
// isIPv6Service returns true if the Service uses IPv6 addresses.
132-
func isIPv6Service(service *corev1.Service) bool {
133-
// IPFamily is not guaranteed to be set, even in an IPv6 only cluster.
134-
return (service.Spec.IPFamily != nil && *service.Spec.IPFamily == corev1.IPv6Protocol) || utilnet.IsIPv6String(service.Spec.ClusterIP)
135-
}
136-
137131
// endpointsEqualBeyondHash returns true if endpoints have equal attributes
138132
// but excludes equality checks that would have already been covered with
139133
// endpoint hashing (see hashEndpoint func for more info).

pkg/controller/util/endpoint/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ go_library(
1010
visibility = ["//visibility:public"],
1111
deps = [
1212
"//pkg/api/v1/pod:go_default_library",
13+
"//pkg/apis/core/v1/helper:go_default_library",
1314
"//pkg/controller:go_default_library",
1415
"//pkg/util/hash:go_default_library",
1516
"//staging/src/k8s.io/api/core/v1:go_default_library",
@@ -19,6 +20,7 @@ go_library(
1920
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
2021
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
2122
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
23+
"//vendor/k8s.io/utils/net:go_default_library",
2224
],
2325
)
2426

pkg/controller/util/endpoint/controller_utils.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ import (
3232
v1listers "k8s.io/client-go/listers/core/v1"
3333
"k8s.io/client-go/tools/cache"
3434
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
35+
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
3536
"k8s.io/kubernetes/pkg/controller"
3637
"k8s.io/kubernetes/pkg/util/hash"
38+
utilnet "k8s.io/utils/net"
3739
)
3840

3941
// ServiceSelectorCache is a cache of service selectors to avoid high CPU consumption caused by frequent calls to AsSelectorPreValidated (see #73527)
@@ -275,3 +277,18 @@ func (sl portsInOrder) Less(i, j int) bool {
275277
h2 := DeepHashObjectToString(sl[j])
276278
return h1 < h2
277279
}
280+
281+
// IsIPv6Service checks if svc should have IPv6 endpoints
282+
func IsIPv6Service(svc *v1.Service) bool {
283+
if helper.IsServiceIPSet(svc) {
284+
return utilnet.IsIPv6String(svc.Spec.ClusterIP)
285+
} else if svc.Spec.IPFamily != nil {
286+
return *svc.Spec.IPFamily == v1.IPv6Protocol
287+
} else {
288+
// FIXME: for legacy headless Services with no IPFamily, the current
289+
// thinking is that we should use the cluster default. Unfortunately
290+
// the endpoint controller doesn't know the cluster default. For now,
291+
// assume it's IPv4.
292+
return false
293+
}
294+
}

0 commit comments

Comments
 (0)