Skip to content

Commit 720f13d

Browse files
authored
Merge pull request #92 from Charliekenney23/feat/hostname-only-ingress-ann
add loadbalancer-hostname-only-ingress annotation
2 parents 65f097f + 47015fd commit 720f13d

File tree

5 files changed

+171
-26
lines changed

5 files changed

+171
-26
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Annotation (Suffix) | Values | Default | Description
5555
`check-passive` | [bool](#annotation-bool-values) | `false` | When `true`, `5xx` status codes will cause the health check to fail
5656
`preserve` | [bool](#annotation-bool-values) | `false` | When `true`, deleting a `LoadBalancer` service does not delete the underlying NodeBalancer. This will also prevent deletion of the former LoadBalancer when another one is specified with the `nodebalancer-id` annotation.
5757
`nodebalancer-id` | string | | The ID of the NodeBalancer to front the service. When not specified, a new NodeBalancer will be created. This can be configured on service creation or patching
58+
`hostname-only-ingress` | [bool](#annotation-bool-values) | `false` | When `true`, the LoadBalancerStatus for the service will only contain the Hostname. This is useful for bypassing kube-proxy's rerouting of in-cluster requests originally intended for the external LoadBalancer to the service's constituent pod IPs.
5859

5960
#### Deprecated Annotations
6061

cloud/linode/loadbalancers.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const (
4444

4545
annLinodeLoadBalancerPreserve = "service.beta.kubernetes.io/linode-loadbalancer-preserve"
4646
annLinodeNodeBalancerID = "service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-id"
47+
48+
annLinodeHostnameOnlyIngress = "service.beta.kubernetes.io/linode-loadbalancer-hostname-only-ingress"
4749
)
4850

4951
type lbNotFoundError struct {
@@ -122,12 +124,12 @@ func (l *loadbalancers) getLatestServiceLoadBalancerStatus(ctx context.Context,
122124
// most recent LoadBalancer status.
123125
func (l *loadbalancers) getNodeBalancerByStatus(ctx context.Context, service *v1.Service) (nb *linodego.NodeBalancer, err error) {
124126
for _, ingress := range service.Status.LoadBalancer.Ingress {
125-
if ingress.Hostname != "" {
126-
return l.getNodeBalancerByHostname(ctx, service, ingress.Hostname)
127-
}
128127
if ingress.IP != "" {
129128
return l.getNodeBalancerByIPv4(ctx, service, ingress.IP)
130129
}
130+
if ingress.Hostname != "" {
131+
return l.getNodeBalancerByHostname(ctx, service, ingress.Hostname)
132+
}
131133
}
132134
return nil, lbNotFoundError{serviceNn: getServiceNn(service)}
133135
}
@@ -195,7 +197,7 @@ func (l *loadbalancers) GetLoadBalancer(ctx context.Context, clusterName string,
195197
return nil, false, err
196198
}
197199

198-
return makeLoadBalancerStatus(nb), true, nil
200+
return makeLoadBalancerStatus(service, nb), true, nil
199201
}
200202

201203
// EnsureLoadBalancer ensures that the cluster is running a load balancer for
@@ -231,7 +233,7 @@ func (l *loadbalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri
231233
}
232234

233235
klog.Infof("NodeBalancer (%d) has been ensured for service (%s)", nb.ID, serviceNn)
234-
lbStatus = makeLoadBalancerStatus(nb)
236+
lbStatus = makeLoadBalancerStatus(service, nb)
235237

236238
if !l.shouldPreserveNodeBalancer(service) {
237239
if err := l.cleanupOldNodeBalancer(ctx, service); err != nil {
@@ -383,12 +385,7 @@ func (l *loadbalancers) deleteUnusedConfigs(ctx context.Context, nbConfigs []lin
383385
// shouldPreserveNodeBalancer determines whether a NodeBalancer should be deleted based on the
384386
// service's preserve annotation.
385387
func (l *loadbalancers) shouldPreserveNodeBalancer(service *v1.Service) bool {
386-
preserveRaw, ok := getServiceAnnotation(service, annLinodeLoadBalancerPreserve)
387-
if !ok {
388-
return false
389-
}
390-
preserve, err := strconv.ParseBool(preserveRaw)
391-
return err == nil && preserve
388+
return getServiceBoolAnnotation(service, annLinodeLoadBalancerPreserve)
392389
}
393390

394391
// EnsureLoadBalancerDeleted deletes the specified loadbalancer if it exists.
@@ -769,11 +766,16 @@ func getConnectionThrottle(service *v1.Service) int {
769766
return connThrottle
770767
}
771768

772-
func makeLoadBalancerStatus(nb *linodego.NodeBalancer) *v1.LoadBalancerStatus {
769+
func makeLoadBalancerStatus(service *v1.Service, nb *linodego.NodeBalancer) *v1.LoadBalancerStatus {
770+
ingress := v1.LoadBalancerIngress{
771+
Hostname: *nb.Hostname,
772+
}
773+
if !getServiceBoolAnnotation(service, annLinodeHostnameOnlyIngress) {
774+
ingress.IP = *nb.IPv4
775+
}
776+
773777
return &v1.LoadBalancerStatus{
774-
Ingress: []v1.LoadBalancerIngress{{
775-
Hostname: *nb.Hostname,
776-
}},
778+
Ingress: []v1.LoadBalancerIngress{ingress},
777779
}
778780
}
779781

@@ -789,3 +791,12 @@ func getServiceAnnotation(service *v1.Service, name string) (string, bool) {
789791
val, ok := service.Annotations[name]
790792
return val, ok
791793
}
794+
795+
func getServiceBoolAnnotation(service *v1.Service, name string) bool {
796+
value, ok := getServiceAnnotation(service, name)
797+
if !ok {
798+
return false
799+
}
800+
boolValue, err := strconv.ParseBool(value)
801+
return err == nil && boolValue
802+
}

cloud/linode/loadbalancers_test.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ func TestCCMLoadBalancers(t *testing.T) {
162162
name: "getNodeBalancerForService - NodeBalancerID does not exist",
163163
f: testGetNodeBalancerForServiceIDDoesNotExist,
164164
},
165+
{
166+
name: "makeLoadBalancerStatus",
167+
f: testMakeLoadBalancerStatus,
168+
},
165169
}
166170

167171
for _, tc := range testCases {
@@ -571,7 +575,7 @@ func testUpdateLoadBalancerAddProxyProtocol(t *testing.T, client *linodego.Clien
571575
t.Fatalf("failed to create NodeBalancer: %s", err)
572576
}
573577

574-
svc.Status.LoadBalancer = *makeLoadBalancerStatus(nodeBalancer)
578+
svc.Status.LoadBalancer = *makeLoadBalancerStatus(svc, nodeBalancer)
575579
svc.ObjectMeta.SetAnnotations(map[string]string{
576580
annLinodeDefaultProxyProtocol: string(tc.proxyProtocolConfig),
577581
})
@@ -649,7 +653,7 @@ func testUpdateLoadBalancerAddNodeBalancerID(t *testing.T, client *linodego.Clie
649653
t.Fatalf("failed to create NodeBalancer: %s", err)
650654
}
651655

652-
svc.Status.LoadBalancer = *makeLoadBalancerStatus(nodeBalancer)
656+
svc.Status.LoadBalancer = *makeLoadBalancerStatus(svc, nodeBalancer)
653657

654658
newNodeBalancer, err := client.CreateNodeBalancer(context.TODO(), linodego.NodeBalancerCreateOptions{
655659
Region: lb.zone,
@@ -672,7 +676,7 @@ func testUpdateLoadBalancerAddNodeBalancerID(t *testing.T, client *linodego.Clie
672676
t.Errorf("GetLoadBalancer returned an error: %s", err)
673677
}
674678

675-
expectedLBStatus := makeLoadBalancerStatus(newNodeBalancer)
679+
expectedLBStatus := makeLoadBalancerStatus(svc, newNodeBalancer)
676680
if !reflect.DeepEqual(expectedLBStatus, lbStatus) {
677681
t.Errorf("LoadBalancer status mismatch: expected %v, got %v", expectedLBStatus, lbStatus)
678682
}
@@ -1185,7 +1189,7 @@ func testEnsureLoadBalancerPreserveAnnotation(t *testing.T, client *linodego.Cli
11851189
t.Fatal(err)
11861190
}
11871191

1188-
svc.Status.LoadBalancer = *makeLoadBalancerStatus(nb)
1192+
svc.Status.LoadBalancer = *makeLoadBalancerStatus(svc, nb)
11891193
err = lb.EnsureLoadBalancerDeleted(context.TODO(), "linodelb", svc)
11901194

11911195
didDelete := fake.didRequestOccur(http.MethodDelete, fmt.Sprintf("/nodebalancers/%d", nb.ID), "")
@@ -1318,7 +1322,7 @@ func testEnsureExistingLoadBalancer(t *testing.T, client *linodego.Client, _ *fa
13181322
t.Fatal(err)
13191323
}
13201324

1321-
svc.Status.LoadBalancer = *makeLoadBalancerStatus(nb)
1325+
svc.Status.LoadBalancer = *makeLoadBalancerStatus(svc, nb)
13221326
defer func() { _ = lb.EnsureLoadBalancerDeleted(context.TODO(), "linodelb", svc) }()
13231327
getLBStatus, exists, err := lb.GetLoadBalancer(context.TODO(), "linodelb", svc)
13241328
if err != nil {
@@ -1406,6 +1410,40 @@ func testEnsureExistingLoadBalancer(t *testing.T, client *linodego.Client, _ *fa
14061410
}
14071411
}
14081412

1413+
func testMakeLoadBalancerStatus(t *testing.T, client *linodego.Client, _ *fakeAPI) {
1414+
ipv4 := "192.168.0.1"
1415+
hostname := "nb-192-168-0-1.newark.nodebalancer.linode.com"
1416+
nb := &linodego.NodeBalancer{
1417+
IPv4: &ipv4,
1418+
Hostname: &hostname,
1419+
}
1420+
1421+
svc := &v1.Service{
1422+
ObjectMeta: metav1.ObjectMeta{
1423+
Name: "test",
1424+
Annotations: make(map[string]string, 1),
1425+
},
1426+
}
1427+
1428+
expectedStatus := &v1.LoadBalancerStatus{
1429+
Ingress: []v1.LoadBalancerIngress{{
1430+
Hostname: hostname,
1431+
IP: ipv4,
1432+
}},
1433+
}
1434+
status := makeLoadBalancerStatus(svc, nb)
1435+
if !reflect.DeepEqual(status, expectedStatus) {
1436+
t.Errorf("expected status for basic service to be %#v; got %#v", expectedStatus, status)
1437+
}
1438+
1439+
svc.Annotations[annLinodeHostnameOnlyIngress] = "true"
1440+
expectedStatus.Ingress[0] = v1.LoadBalancerIngress{Hostname: hostname}
1441+
status = makeLoadBalancerStatus(svc, nb)
1442+
if !reflect.DeepEqual(status, expectedStatus) {
1443+
t.Errorf("expected status for %q annotated service to be %#v; got %#v", annLinodeHostnameOnlyIngress, expectedStatus, status)
1444+
}
1445+
}
1446+
14091447
func testGetNodeBalancerForServiceIDDoesNotExist(t *testing.T, client *linodego.Client, _ *fakeAPI) {
14101448
lb := &loadbalancers{client, "us-west", nil}
14111449
bogusNodeBalancerID := "123456"
@@ -1579,7 +1617,7 @@ func testGetLoadBalancer(t *testing.T, client *linodego.Client, _ *fakeAPI) {
15791617
}
15801618
defer func() { _ = lb.EnsureLoadBalancerDeleted(context.TODO(), "linodelb", svc) }()
15811619

1582-
lbStatus := makeLoadBalancerStatus(nb)
1620+
lbStatus := makeLoadBalancerStatus(svc, nb)
15831621
svc.Status.LoadBalancer = *lbStatus
15841622

15851623
testcases := []struct {

e2e/test/ccm_e2e_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var _ = Describe("e2e tests", func() {
4040
annLinodeHealthCheckAttempts = "service.beta.kubernetes.io/linode-loadbalancer-check-attempts"
4141
annLinodeHealthCheckPassive = "service.beta.kubernetes.io/linode-loadbalancer-check-passive"
4242
annLinodeNodeBalancerID = "service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-id"
43+
annLinodeHostnameOnlyIngress = "service.beta.kubernetes.io/linode-loadbalancer-hostname-only-ingress"
4344
)
4445

4546
BeforeEach(func() {
@@ -174,6 +175,26 @@ var _ = Describe("e2e tests", func() {
174175
Expect(err).NotTo(HaveOccurred())
175176
}
176177

178+
var checkLBStatus = func(service string, hasIP bool) {
179+
Eventually(func() bool {
180+
nb, err := f.LoadBalancer.GetNodeBalancer(service)
181+
Expect(err).NotTo(HaveOccurred())
182+
183+
svc, err := f.LoadBalancer.GetServiceWithLoadBalancerStatus(service, f.LoadBalancer.Namespace())
184+
Expect(err).NotTo(HaveOccurred())
185+
186+
ingress := svc.Status.LoadBalancer.Ingress[0]
187+
Expect(nb.Hostname).ToNot(BeNil())
188+
Expect(ingress.Hostname).Should(Equal(*nb.Hostname))
189+
190+
if hasIP {
191+
return nb.IPv4 != nil && ingress.IP == *nb.IPv4
192+
} else {
193+
return ingress.IP == ""
194+
}
195+
}).Should(BeTrue())
196+
}
197+
177198
var checkNodeBalancerConfigForPort = func(port int, args checkArgs) {
178199
By("Getting NodeBalancer Configuration for port " + strconv.Itoa(port))
179200
nbConfig, err := f.LoadBalancer.GetNodeBalancerConfigForPort(framework.TestServerResourceName, port)
@@ -406,6 +427,72 @@ var _ = Describe("e2e tests", func() {
406427
})
407428
})
408429

430+
Context("With Hostname only ingress", func() {
431+
var (
432+
pods []string
433+
labels map[string]string
434+
servicePorts []core.ServicePort
435+
436+
annotations = map[string]string{}
437+
)
438+
439+
BeforeEach(func() {
440+
pods = []string{"test-pod-1"}
441+
ports := []core.ContainerPort{
442+
{
443+
Name: "http-1",
444+
ContainerPort: 80,
445+
},
446+
}
447+
servicePorts = []core.ServicePort{
448+
{
449+
Name: "http-1",
450+
Port: 80,
451+
TargetPort: intstr.FromInt(80),
452+
Protocol: "TCP",
453+
},
454+
}
455+
456+
labels = map[string]string{
457+
"app": "test-loadbalancer-with-hostname-only-ingress",
458+
}
459+
460+
By("Creating Pod")
461+
createPodWithLabel(pods, ports, framework.TestServerImage, labels, false)
462+
463+
By("Creating Service")
464+
createServiceWithAnnotations(labels, map[string]string{}, servicePorts, false)
465+
})
466+
467+
AfterEach(func() {
468+
By("Deleting the Pods")
469+
deletePods(pods)
470+
471+
By("Deleting the Service")
472+
deleteService()
473+
})
474+
475+
It("can update service to only use Hostname in ingress", func() {
476+
By("Checking LB Status has IP")
477+
checkLBStatus(framework.TestServerResourceName, true)
478+
479+
By("Annotating service with " + annLinodeHostnameOnlyIngress)
480+
updateServiceWithAnnotations(labels, map[string]string{
481+
annLinodeHostnameOnlyIngress: "true",
482+
}, servicePorts, false)
483+
484+
By("Checking LB Status does not have IP")
485+
checkLBStatus(framework.TestServerResourceName, false)
486+
})
487+
488+
annotations[annLinodeHostnameOnlyIngress] = "true"
489+
490+
It("can create a service that only uses Hostname in ingress", func() {
491+
By("Creating a service annotated with " + annLinodeHostnameOnlyIngress)
492+
checkLBStatus(framework.TestServerResourceName, true)
493+
})
494+
})
495+
409496
Context("With ProxyProtocol", func() {
410497
var (
411498
pods []string

e2e/test/framework/loadbalancer_suite.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,32 @@ func (i *lbInvocation) GetHTTPEndpoints() ([]string, error) {
1414
return i.getLoadBalancerURLs()
1515
}
1616

17-
func (i *lbInvocation) GetNodeBalancerID(svcName string) (int, error) {
17+
func (i *lbInvocation) GetNodeBalancer(svcName string) (*linodego.NodeBalancer, error) {
1818
hostname, err := i.waitForLoadBalancerHostname(svcName)
1919
if err != nil {
20-
return -1, err
20+
return nil, err
2121
}
2222

2323
nbList, errListNodeBalancers := i.linodeClient.ListNodeBalancers(context.Background(), nil)
2424

2525
if errListNodeBalancers != nil {
26-
return -1, errListNodeBalancers
26+
return nil, errListNodeBalancers
2727
}
2828

2929
for _, nb := range nbList {
3030
if *nb.Hostname == hostname {
31-
return nb.ID, nil
31+
return &nb, nil
3232
}
3333
}
34-
return -1, fmt.Errorf("no NodeBalancer Found for service %v", svcName)
34+
return nil, fmt.Errorf("no NodeBalancer Found for service %v", svcName)
35+
}
36+
37+
func (i *lbInvocation) GetNodeBalancerID(svcName string) (int, error) {
38+
nb, err := i.GetNodeBalancer(svcName)
39+
if err != nil {
40+
return -1, err
41+
}
42+
return nb.ID, nil
3543
}
3644

3745
func (i *lbInvocation) WaitForNodeBalancerReady(svcName string, expectedID int) error {

0 commit comments

Comments
 (0)