Skip to content

Commit 8f9483d

Browse files
committed
Fixing bugs related to Endpoint Slices
This should fix a bug that could break masters when the EndpointSlice feature gate was enabled. This was all tied to how the apiserver creates and manages it's own services and endpoints (or in this case endpoint slices). Consumers of endpoint slices also need to know about the corresponding service. Previously we were trying to set an owner reference here for this purpose, but that came with potential downsides and increased complexity. This commit changes behavior of the apiserver endpointslice integration to set the service name label instead of owner references, and simplifies consumer logic to reference that (both are set by the EndpointSlice controller). Additionally, this should fix a bug with the EndpointSlice GenerateName value that had previously been set with a "." as a suffix.
1 parent 975d073 commit 8f9483d

File tree

22 files changed

+132
-78
lines changed

22 files changed

+132
-78
lines changed

cmd/kube-controller-manager/app/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ go_library(
112112
"//pkg/volume/util:go_default_library",
113113
"//pkg/volume/vsphere_volume:go_default_library",
114114
"//staging/src/k8s.io/api/core/v1:go_default_library",
115+
"//staging/src/k8s.io/api/discovery/v1alpha1:go_default_library",
115116
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
116117
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
117118
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",

cmd/kube-controller-manager/app/discovery.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ package app
2323
import (
2424
"net/http"
2525

26-
"k8s.io/apimachinery/pkg/runtime/schema"
26+
discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1"
27+
"k8s.io/klog"
2728
endpointslicecontroller "k8s.io/kubernetes/pkg/controller/endpointslice"
2829
)
2930

3031
func startEndpointSliceController(ctx ControllerContext) (http.Handler, bool, error) {
31-
if !ctx.AvailableResources[schema.GroupVersionResource{Group: "discovery", Version: "v1alpha1", Resource: "endpointslices"}] {
32+
if !ctx.AvailableResources[discoveryv1alpha1.SchemeGroupVersion.WithResource("endpointslices")] {
33+
klog.Warningf("Not starting endpointslice-controller, discovery.k8s.io/v1alpha1 resources are not available")
3234
return nil, false, nil
3335
}
3436

pkg/apis/discovery/validation/validation.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ var (
3636
maxEndpoints = 1000
3737
)
3838

39+
// ValidateEndpointSliceName can be used to check whether the given endpoint
40+
// slice name is valid. Prefix indicates this name will be used as part of
41+
// generation, in which case trailing dashes are allowed.
42+
var ValidateEndpointSliceName = apimachineryvalidation.NameIsDNSSubdomain
43+
3944
// ValidateEndpointSlice validates an EndpointSlice.
4045
func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorList {
41-
validateEndpointSliceName := apimachineryvalidation.NameIsDNSSubdomain
42-
allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, validateEndpointSliceName, field.NewPath("metadata"))
46+
allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, ValidateEndpointSliceName, field.NewPath("metadata"))
4347

4448
addrType := discovery.AddressType("")
4549
if endpointSlice.AddressType == nil {

pkg/controller/.import-restrictions

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@
188188
"k8s.io/kubernetes/pkg/apis/core/v1",
189189
"k8s.io/kubernetes/pkg/apis/core/v1/helper",
190190
"k8s.io/kubernetes/pkg/apis/core/validation",
191+
"k8s.io/kubernetes/pkg/apis/discovery",
192+
"k8s.io/kubernetes/pkg/apis/discovery/validation",
191193
"k8s.io/kubernetes/pkg/cloudprovider",
192194
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce",
193195
"k8s.io/kubernetes/pkg/controller",

pkg/controller/endpointslice/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
deps = [
1414
"//pkg/api/v1/pod:go_default_library",
1515
"//pkg/apis/core:go_default_library",
16+
"//pkg/apis/discovery/validation:go_default_library",
1617
"//pkg/controller:go_default_library",
1718
"//pkg/controller/util/endpoint:go_default_library",
1819
"//pkg/util/hash:go_default_library",

pkg/controller/endpointslice/endpointslice_controller.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"time"
2222

2323
v1 "k8s.io/api/core/v1"
24+
discovery "k8s.io/api/discovery/v1alpha1"
2425
apierrors "k8s.io/apimachinery/pkg/api/errors"
2526
"k8s.io/apimachinery/pkg/labels"
2627
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -42,10 +43,6 @@ import (
4243
)
4344

4445
const (
45-
// serviceNameLabel is used to indicate the name of a Kubernetes service
46-
// associated with an EndpointSlice.
47-
serviceNameLabel = "kubernetes.io/service-name"
48-
4946
// maxRetries is the number of times a service will be retried before it is
5047
// dropped out of the queue. Any sync error, such as a failure to create or
5148
// update an EndpointSlice could trigger a retry. With the current
@@ -276,7 +273,7 @@ func (c *Controller) syncService(key string) error {
276273
return err
277274
}
278275

279-
esLabelSelector := labels.Set(map[string]string{serviceNameLabel: service.Name}).AsSelectorPreValidated()
276+
esLabelSelector := labels.Set(map[string]string{discovery.LabelServiceName: service.Name}).AsSelectorPreValidated()
280277
endpointSlices, err := c.endpointSliceLister.EndpointSlices(service.Namespace).List(esLabelSelector)
281278

282279
if err != nil {

pkg/controller/endpointslice/endpointslice_controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func TestSyncServiceWithSelector(t *testing.T) {
108108
assert.Len(t, sliceList.Items, 1, "Expected 1 endpoint slices")
109109
slice := sliceList.Items[0]
110110
assert.Regexp(t, "^"+serviceName, slice.Name)
111-
assert.Equal(t, serviceName, slice.Labels[serviceNameLabel])
111+
assert.Equal(t, serviceName, slice.Labels[discovery.LabelServiceName])
112112
assert.EqualValues(t, []discovery.EndpointPort{}, slice.Ports)
113113
assert.EqualValues(t, []discovery.Endpoint{}, slice.Endpoints)
114114
assert.NotEmpty(t, slice.Annotations["endpoints.kubernetes.io/last-change-trigger-time"])
@@ -189,11 +189,11 @@ func TestSyncServiceEndpointSliceSelection(t *testing.T) {
189189

190190
// 3 slices, 2 with matching labels for our service
191191
endpointSlices := []*discovery.EndpointSlice{{
192-
ObjectMeta: metav1.ObjectMeta{Name: "matching-1", Namespace: ns, Labels: map[string]string{serviceNameLabel: serviceName}},
192+
ObjectMeta: metav1.ObjectMeta{Name: "matching-1", Namespace: ns, Labels: map[string]string{discovery.LabelServiceName: serviceName}},
193193
}, {
194-
ObjectMeta: metav1.ObjectMeta{Name: "matching-2", Namespace: ns, Labels: map[string]string{serviceNameLabel: serviceName}},
194+
ObjectMeta: metav1.ObjectMeta{Name: "matching-2", Namespace: ns, Labels: map[string]string{discovery.LabelServiceName: serviceName}},
195195
}, {
196-
ObjectMeta: metav1.ObjectMeta{Name: "not-matching-1", Namespace: ns, Labels: map[string]string{serviceNameLabel: "something-else"}},
196+
ObjectMeta: metav1.ObjectMeta{Name: "not-matching-1", Namespace: ns, Labels: map[string]string{discovery.LabelServiceName: "something-else"}},
197197
}}
198198

199199
// need to add them to both store and fake clientset

pkg/controller/endpointslice/reconciler_test.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/stretchr/testify/assert"
2424

2525
corev1 "k8s.io/api/core/v1"
26-
v1 "k8s.io/api/core/v1"
2726
discovery "k8s.io/api/discovery/v1alpha1"
2827
apiequality "k8s.io/apimachinery/pkg/api/equality"
2928
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,7 +51,7 @@ func TestReconcileEmpty(t *testing.T) {
5251
assert.Len(t, slices, 1, "Expected 1 endpoint slices")
5352

5453
assert.Regexp(t, "^"+svc.Name, slices[0].Name)
55-
assert.Equal(t, svc.Name, slices[0].Labels[serviceNameLabel])
54+
assert.Equal(t, svc.Name, slices[0].Labels[discovery.LabelServiceName])
5655
assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports)
5756
assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints)
5857
}
@@ -83,7 +82,7 @@ func TestReconcile1Pod(t *testing.T) {
8382
slices := fetchEndpointSlices(t, client, namespace)
8483
assert.Len(t, slices, 1, "Expected 1 endpoint slices")
8584
assert.Regexp(t, "^"+svc.Name, slices[0].Name)
86-
assert.Equal(t, svc.Name, slices[0].Labels[serviceNameLabel])
85+
assert.Equal(t, svc.Name, slices[0].Labels[discovery.LabelServiceName])
8786
assert.Equal(t, slices[0].Annotations, map[string]string{
8887
"endpoints.kubernetes.io/last-change-trigger-time": triggerTime.Format(time.RFC3339Nano),
8988
})
@@ -125,7 +124,7 @@ func TestReconcile1EndpointSlice(t *testing.T) {
125124
assert.Len(t, slices, 1, "Expected 1 endpoint slices")
126125

127126
assert.Regexp(t, "^"+svc.Name, slices[0].Name)
128-
assert.Equal(t, svc.Name, slices[0].Labels[serviceNameLabel])
127+
assert.Equal(t, svc.Name, slices[0].Labels[discovery.LabelServiceName])
129128
assert.EqualValues(t, []discovery.EndpointPort{}, slices[0].Ports)
130129
assert.EqualValues(t, []discovery.Endpoint{}, slices[0].Endpoints)
131130
}
@@ -362,9 +361,9 @@ func TestReconcileEndpointSlicesUpdatePacking(t *testing.T) {
362361
// ensure that endpoints in each slice will be marked for update.
363362
for i, pod := range pods {
364363
if i%10 == 0 {
365-
pod.Status.Conditions = []v1.PodCondition{{
366-
Type: v1.PodReady,
367-
Status: v1.ConditionFalse,
364+
pod.Status.Conditions = []corev1.PodCondition{{
365+
Type: corev1.PodReady,
366+
Status: corev1.ConditionFalse,
368367
}}
369368
}
370369
}
@@ -397,10 +396,10 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
397396

398397
svc := corev1.Service{
399398
ObjectMeta: metav1.ObjectMeta{Name: "named-port-example", Namespace: namespace},
400-
Spec: v1.ServiceSpec{
401-
Ports: []v1.ServicePort{{
399+
Spec: corev1.ServiceSpec{
400+
Ports: []corev1.ServicePort{{
402401
TargetPort: portNameIntStr,
403-
Protocol: v1.ProtocolTCP,
402+
Protocol: corev1.ProtocolTCP,
404403
}},
405404
Selector: map[string]string{"foo": "bar"},
406405
},
@@ -412,10 +411,10 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
412411
ready := !(i%3 == 0)
413412
portOffset := i % 5
414413
pod := newPod(i, namespace, ready, 1)
415-
pod.Spec.Containers[0].Ports = []v1.ContainerPort{{
414+
pod.Spec.Containers[0].Ports = []corev1.ContainerPort{{
416415
Name: portNameIntStr.StrVal,
417416
ContainerPort: int32(8080 + portOffset),
418-
Protocol: v1.ProtocolTCP,
417+
Protocol: corev1.ProtocolTCP,
419418
}}
420419
pods = append(pods, pod)
421420
}
@@ -433,7 +432,7 @@ func TestReconcileEndpointSlicesNamedPorts(t *testing.T) {
433432
expectUnorderedSlicesWithLengths(t, fetchedSlices, []int{60, 60, 60, 60, 60})
434433

435434
// generate data structures for expected slice ports and address types
436-
protoTCP := v1.ProtocolTCP
435+
protoTCP := corev1.ProtocolTCP
437436
ipAddressType := discovery.AddressTypeIP
438437
expectedSlices := []discovery.EndpointSlice{}
439438
for i := range fetchedSlices {

pkg/controller/endpointslice/utils.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"k8s.io/klog"
3333
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
3434
api "k8s.io/kubernetes/pkg/apis/core"
35+
"k8s.io/kubernetes/pkg/apis/discovery/validation"
3536
"k8s.io/kubernetes/pkg/util/hash"
3637
)
3738

@@ -158,8 +159,8 @@ func newEndpointSlice(service *corev1.Service, endpointMeta *endpointMeta) *disc
158159
ownerRef := metav1.NewControllerRef(service, gvk)
159160
return &discovery.EndpointSlice{
160161
ObjectMeta: metav1.ObjectMeta{
161-
Labels: map[string]string{serviceNameLabel: service.Name},
162-
GenerateName: fmt.Sprintf("%s.", service.Name),
162+
Labels: map[string]string{discovery.LabelServiceName: service.Name},
163+
GenerateName: getEndpointSlicePrefix(service.Name),
163164
OwnerReferences: []metav1.OwnerReference{*ownerRef},
164165
Namespace: service.Namespace,
165166
},
@@ -169,6 +170,16 @@ func newEndpointSlice(service *corev1.Service, endpointMeta *endpointMeta) *disc
169170
}
170171
}
171172

173+
// getEndpointSlicePrefix returns a suitable prefix for an EndpointSlice name.
174+
func getEndpointSlicePrefix(serviceName string) string {
175+
// use the dash (if the name isn't too long) to make the pod name a bit prettier
176+
prefix := fmt.Sprintf("%s-", serviceName)
177+
if len(validation.ValidateEndpointSliceName(prefix, true)) != 0 {
178+
prefix = serviceName
179+
}
180+
return prefix
181+
}
182+
172183
// boolPtrChanged returns true if a set of bool pointers have different values.
173184
func boolPtrChanged(ptr1, ptr2 *bool) bool {
174185
if (ptr1 == nil) != (ptr2 == nil) {

pkg/controller/endpointslice/utils_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"time"
2323

2424
"github.com/stretchr/testify/assert"
25-
corev1 "k8s.io/api/core/v1"
2625
v1 "k8s.io/api/core/v1"
2726
discovery "k8s.io/api/discovery/v1alpha1"
2827
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -58,8 +57,8 @@ func TestNewEndpointSlice(t *testing.T) {
5857

5958
expectedSlice := discovery.EndpointSlice{
6059
ObjectMeta: metav1.ObjectMeta{
61-
Labels: map[string]string{serviceNameLabel: service.Name},
62-
GenerateName: fmt.Sprintf("%s.", service.Name),
60+
Labels: map[string]string{discovery.LabelServiceName: service.Name},
61+
GenerateName: fmt.Sprintf("%s-", service.Name),
6362
OwnerReferences: []metav1.OwnerReference{*ownerRef},
6463
Namespace: service.Namespace,
6564
},
@@ -81,7 +80,7 @@ func TestPodToEndpoint(t *testing.T) {
8180

8281
multiIPPod.Status.PodIPs = []v1.PodIP{{IP: "1.2.3.4"}, {IP: "1234::5678:0000:0000:9abc:def0"}}
8382

84-
node1 := &corev1.Node{
83+
node1 := &v1.Node{
8584
ObjectMeta: metav1.ObjectMeta{
8685
Name: readyPod.Spec.NodeName,
8786
Labels: map[string]string{
@@ -288,14 +287,14 @@ func newClientset() *fake.Clientset {
288287
return client
289288
}
290289

291-
func newServiceAndendpointMeta(name, namespace string) (corev1.Service, endpointMeta) {
290+
func newServiceAndendpointMeta(name, namespace string) (v1.Service, endpointMeta) {
292291
portNum := int32(80)
293292
portNameIntStr := intstr.IntOrString{
294293
Type: intstr.Int,
295294
IntVal: portNum,
296295
}
297296

298-
svc := corev1.Service{
297+
svc := v1.Service{
299298
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
300299
Spec: v1.ServiceSpec{
301300
Ports: []v1.ServicePort{{
@@ -317,7 +316,7 @@ func newServiceAndendpointMeta(name, namespace string) (corev1.Service, endpoint
317316
return svc, endpointMeta
318317
}
319318

320-
func newEmptyEndpointSlice(n int, namespace string, endpointMeta endpointMeta, svc corev1.Service) *discovery.EndpointSlice {
319+
func newEmptyEndpointSlice(n int, namespace string, endpointMeta endpointMeta, svc v1.Service) *discovery.EndpointSlice {
321320
return &discovery.EndpointSlice{
322321
ObjectMeta: metav1.ObjectMeta{
323322
Name: fmt.Sprintf("%s.%d", svc.Name, n),

0 commit comments

Comments
 (0)