Skip to content

Commit 0fac200

Browse files
authored
Add support for specifying the load balancer's name via annotation (#1880)
This adds a new "aws-load-balancer-name" annotation that can be used to specify the associated load balancer's name, overriding the default name format. Some implementation notes: * This doesn't perform any input validation on the annotation value to ensure it matches the allowed naming format. I'm happy to add this but didnt see validation on any other annotation value strings. * Adding or updating the annotation on existing ingresses has no effect once a load balancer already exists.
1 parent f3b196c commit 0fac200

File tree

5 files changed

+144
-0
lines changed

5 files changed

+144
-0
lines changed

pkg/annotations/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050
SvcLBSuffixLoadBalancerType = "aws-load-balancer-type"
5151
SvcLBSuffixInternal = "aws-load-balancer-internal"
5252
SvcLBSuffixIPAddressType = "aws-load-balancer-ip-address-type"
53+
SvcLBSuffixLoadBalancerName = "aws-load-balancer-name"
5354
SvcLBSuffixProxyProtocol = "aws-load-balancer-proxy-protocol"
5455
SvcLBSuffixAccessLogEnabled = "aws-load-balancer-access-log-enabled"
5556
SvcLBSuffixAccessLogS3BucketName = "aws-load-balancer-access-log-s3-bucket-name"

pkg/service/model_build_load_balancer.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ func (t *defaultModelBuildTask) buildLoadBalancerAttributes(_ context.Context) (
231231
var invalidLoadBalancerNamePattern = regexp.MustCompile("[[:^alnum:]]")
232232

233233
func (t *defaultModelBuildTask) buildLoadBalancerName(_ context.Context, scheme elbv2model.LoadBalancerScheme) string {
234+
var name string
235+
if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixLoadBalancerName, &name, t.service.Annotations); exists {
236+
return name
237+
}
234238
uuidHash := sha256.New()
235239
_, _ = uuidHash.Write([]byte(t.clusterName))
236240
_, _ = uuidHash.Write([]byte(t.service.UID))

pkg/service/model_build_load_balancer_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,3 +708,51 @@ func Test_defaultModelBuildTask_buildAdditionalResourceTags(t *testing.T) {
708708
})
709709
}
710710
}
711+
712+
func Test_defaultModelBuildTask_buildLoadBalancerName(t *testing.T) {
713+
tests := []struct {
714+
name string
715+
service *corev1.Service
716+
clusterName string
717+
scheme elbv2.LoadBalancerScheme
718+
want string
719+
}{
720+
{
721+
name: "no name annotation",
722+
723+
service: &corev1.Service{
724+
ObjectMeta: metav1.ObjectMeta{
725+
Namespace: "foo",
726+
Name: "bar",
727+
Annotations: map[string]string{},
728+
},
729+
},
730+
scheme: elbv2.LoadBalancerSchemeInternetFacing,
731+
want: "k8s-foo-bar-e053368fb2",
732+
},
733+
{
734+
name: "non-empty name annotation",
735+
service: &corev1.Service{
736+
ObjectMeta: metav1.ObjectMeta{
737+
Namespace: "foo",
738+
Name: "bar",
739+
Annotations: map[string]string{
740+
"service.beta.kubernetes.io/aws-load-balancer-name": "baz",
741+
},
742+
},
743+
},
744+
want: "baz",
745+
},
746+
}
747+
for _, tt := range tests {
748+
t.Run(tt.name, func(t *testing.T) {
749+
task := &defaultModelBuildTask{
750+
service: tt.service,
751+
clusterName: tt.clusterName,
752+
annotationParser: annotations.NewSuffixAnnotationParser("service.beta.kubernetes.io"),
753+
}
754+
got := task.buildLoadBalancerName(context.Background(), tt.scheme)
755+
assert.Equal(t, tt.want, got)
756+
})
757+
}
758+
}

test/e2e/service/service.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type TargetGroupHC struct {
4848
}
4949

5050
type LoadBalancerExpectation struct {
51+
Name *string
5152
Type string
5253
Scheme string
5354
TargetType string
@@ -166,6 +167,19 @@ func (m *ServiceTest) CheckLoadBalancerListeners(ctx context.Context, f *framewo
166167
return nil
167168
}
168169

170+
func (m *ServiceTest) CheckLoadBalancerName(ctx context.Context, f *framework.Framework, lbArns []string, lbName string) error {
171+
By("Describing AWS Load Balancer", func() {
172+
lbs, err := f.Cloud.ELBV2().DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{
173+
LoadBalancerArns: aws.StringSlice(lbArns),
174+
})
175+
Expect(err).NotTo(HaveOccurred())
176+
Expect(len(lbs.LoadBalancers)).To(Equal(1))
177+
lb := lbs.LoadBalancers[0]
178+
Expect(aws.StringValue(lb.LoadBalancerName)).To(Equal(lbName))
179+
})
180+
return nil
181+
}
182+
169183
func (m *ServiceTest) VerifyLoadBalancerAttributes(ctx context.Context, f *framework.Framework, expectedAttrs map[string]string) error {
170184
lbArns, err := m.GetAwsLoadBalancerArns(ctx, f)
171185
Expect(err).ToNot(HaveOccurred())
@@ -252,6 +266,11 @@ func (m *ServiceTest) VerifyAWSLoadBalancerResources(ctx context.Context, f *fra
252266

253267
err = m.CheckTargetGroups(ctx, f, lbArns[0], expected)
254268
Expect(err).ToNot(HaveOccurred())
269+
270+
if expected.Name != nil {
271+
err = m.CheckLoadBalancerName(ctx, f, lbArns, *expected.Name)
272+
Expect(err).ToNot(HaveOccurred())
273+
}
255274
})
256275
return nil
257276
}

test/e2e/service/service_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,76 @@ var _ = Describe("Service", func() {
310310
})
311311
})
312312
})
313+
314+
Context("NLB IP Load Balancer with name", func() {
315+
var (
316+
svcTest service.ServiceTest
317+
svc *corev1.Service
318+
)
319+
BeforeEach(func() {
320+
svcTest = service.ServiceTest{}
321+
svc = &corev1.Service{
322+
ObjectMeta: metav1.ObjectMeta{
323+
Name: name,
324+
Namespace: ns.Name,
325+
Annotations: map[string]string{
326+
"service.beta.kubernetes.io/aws-load-balancer-name": name,
327+
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb-ip",
328+
},
329+
},
330+
Spec: corev1.ServiceSpec{
331+
Type: corev1.ServiceTypeLoadBalancer,
332+
Selector: labels,
333+
Ports: []corev1.ServicePort{
334+
{
335+
Port: 80,
336+
TargetPort: intstr.FromInt(80),
337+
Protocol: corev1.ProtocolTCP,
338+
},
339+
},
340+
},
341+
}
342+
})
343+
It("Should create and verify service", func() {
344+
By("Creating service", func() {
345+
err := svcTest.Create(ctx, tf, svc)
346+
Expect(err).ToNot(HaveOccurred())
347+
})
348+
By("Verify Service with AWS", func() {
349+
err := svcTest.VerifyAWSLoadBalancerResources(ctx, tf, service.LoadBalancerExpectation{
350+
Name: &name,
351+
Type: "network",
352+
Scheme: "internet-facing",
353+
TargetType: "ip",
354+
Listeners: map[string]string{
355+
"80": "TCP",
356+
},
357+
TargetGroups: map[string]string{
358+
"80": "TCP",
359+
},
360+
NumTargets: int(numReplicas),
361+
TargetGroupHC: &service.TargetGroupHC{
362+
Protocol: "TCP",
363+
Port: "traffic-port",
364+
Interval: 10,
365+
Timeout: 10,
366+
HealthyThreshold: 3,
367+
UnhealthyThreshold: 3,
368+
},
369+
})
370+
Expect(err).ToNot(HaveOccurred())
371+
})
372+
By("Send traffic to LB", func() {
373+
err := svcTest.SendTrafficToLB(ctx, tf)
374+
Expect(err).ToNot(HaveOccurred())
375+
})
376+
By("Deleting service", func() {
377+
err := svcTest.Cleanup(ctx, tf, svc)
378+
Expect(err).ToNot(HaveOccurred())
379+
newSvc := &corev1.Service{}
380+
err = tf.K8sClient.Get(ctx, k8s.NamespacedName(svc), newSvc)
381+
Expect(apierrs.IsNotFound(err)).To(BeTrue())
382+
})
383+
})
384+
})
313385
})

0 commit comments

Comments
 (0)