Skip to content

Commit 37ea2f3

Browse files
author
Rahul Sharma
committed
add flag to set nodebalancer subnet and make sure nb backend ips lie within that subnet
1 parent dcaae71 commit 37ea2f3

File tree

7 files changed

+196
-12
lines changed

7 files changed

+196
-12
lines changed

cloud/linode/cloud.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ var Options struct {
3939
EnableRouteController bool
4040
EnableTokenHealthChecker bool
4141
// Deprecated: use VPCNames instead
42-
VPCName string
43-
VPCNames string
44-
SubnetNames string
45-
LoadBalancerType string
46-
BGPNodeSelector string
47-
IpHolderSuffix string
48-
LinodeExternalNetwork *net.IPNet
49-
NodeBalancerTags []string
50-
DefaultNBType string
51-
GlobalStopChannel chan<- struct{}
42+
VPCName string
43+
VPCNames string
44+
SubnetNames string
45+
LoadBalancerType string
46+
BGPNodeSelector string
47+
IpHolderSuffix string
48+
LinodeExternalNetwork *net.IPNet
49+
NodeBalancerTags []string
50+
DefaultNBType string
51+
NodeBalancerBackendIPv4Subnet string
52+
GlobalStopChannel chan<- struct{}
5253
}
5354

5455
type linodeCloud struct {

cloud/linode/loadbalancers.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"net"
89
"net/http"
910
"os"
1011
"reflect"
@@ -384,8 +385,11 @@ func (l *loadbalancers) updateNodeBalancer(
384385
// Add all of the Nodes to the config
385386
newNBNodes := make([]linodego.NodeBalancerConfigRebuildNodeOptions, 0, len(nodes))
386387
subnetID := 0
387-
_, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
388+
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
388389
if ok {
390+
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
391+
return err
392+
}
389393
id, err := l.getSubnetIDForSVC(ctx, service)
390394
if err != nil {
391395
sentry.CaptureError(ctx, err)
@@ -664,6 +668,9 @@ func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName stri
664668

665669
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
666670
if ok {
671+
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
672+
return nil, err
673+
}
667674
subnetID, err := l.getSubnetIDForSVC(ctx, service)
668675
if err != nil {
669676
return nil, err
@@ -824,8 +831,11 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam
824831
configs := make([]*linodego.NodeBalancerConfigCreateOptions, 0, len(ports))
825832

826833
subnetID := 0
827-
_, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
834+
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
828835
if ok {
836+
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
837+
return nil, err
838+
}
829839
id, err := l.getSubnetIDForSVC(ctx, service)
830840
if err != nil {
831841
return nil, err
@@ -1086,3 +1096,32 @@ func getServiceBoolAnnotation(service *v1.Service, name string) bool {
10861096
boolValue, err := strconv.ParseBool(value)
10871097
return err == nil && boolValue
10881098
}
1099+
1100+
// validateNodeBalancerBackendIPv4Range validates the NodeBalancerBackendIPv4Range
1101+
// annotation to be within the NodeBalancerBackendIPv4Subnet if it is set.
1102+
func validateNodeBalancerBackendIPv4Range(backendIPv4Range string) error {
1103+
if Options.NodeBalancerBackendIPv4Subnet == "" {
1104+
return nil
1105+
}
1106+
withinCIDR, err := isCIDRWithinCIDR(Options.NodeBalancerBackendIPv4Subnet, backendIPv4Range)
1107+
if err != nil {
1108+
return fmt.Errorf("invalid IPv4 range: %v", err)
1109+
}
1110+
if !withinCIDR {
1111+
return fmt.Errorf("IPv4 range %s is not within the subnet %s", backendIPv4Range, Options.NodeBalancerBackendIPv4Subnet)
1112+
}
1113+
return nil
1114+
}
1115+
1116+
// isCIDRWithinCIDR returns true if the inner CIDR is within the outer CIDR.
1117+
func isCIDRWithinCIDR(outer, inner string) (bool, error) {
1118+
_, ipNet1, err := net.ParseCIDR(outer)
1119+
if err != nil {
1120+
return false, fmt.Errorf("invalid CIDR: %v", err)
1121+
}
1122+
_, ipNet2, err := net.ParseCIDR(inner)
1123+
if err != nil {
1124+
return false, fmt.Errorf("invalid CIDR: %v", err)
1125+
}
1126+
return ipNet1.Contains(ipNet2.IP), nil
1127+
}

cloud/linode/loadbalancers_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ func TestCCMLoadBalancers(t *testing.T) {
156156
name: "Create Load Balancer With VPC Backend",
157157
f: testCreateNodeBalancerWithVPCBackend,
158158
},
159+
{
160+
name: "Update Load Balancer With VPC Backend",
161+
f: testUpdateNodeBalancerWithVPCBackend,
162+
},
159163
{
160164
name: "Create Load Balancer With VPC Backend - Overwrite VPC Name and Subnet with Annotation",
161165
f: testCreateNodeBalancerWithVPCAnnotationOverwrite,
@@ -525,15 +529,110 @@ func testCreateNodeBalancerWithVPCBackend(t *testing.T, client *linodego.Client,
525529
if err != nil {
526530
t.Fatalf("expected a nil error, got %v", err)
527531
}
532+
533+
f.ResetRequests()
534+
535+
// test with IPv4Range outside of defined NodeBalancer subnet
536+
nodebalancerBackendIPv4Subnet := Options.NodeBalancerBackendIPv4Subnet
537+
defer func() {
538+
Options.NodeBalancerBackendIPv4Subnet = nodebalancerBackendIPv4Subnet
539+
}()
540+
Options.NodeBalancerBackendIPv4Subnet = "10.99.0.0/24"
541+
if err := testCreateNodeBalancer(t, client, f, ann, nil); err == nil {
542+
t.Fatalf("expected nodebalancer creation to fail")
543+
}
544+
}
545+
546+
func testUpdateNodeBalancerWithVPCBackend(t *testing.T, client *linodego.Client, f *fakeAPI) {
547+
// provision vpc and test
548+
vpcNames := Options.VPCNames
549+
subnetNames := Options.SubnetNames
550+
defer func() {
551+
Options.VPCNames = vpcNames
552+
Options.SubnetNames = subnetNames
553+
}()
554+
Options.VPCNames = "test1"
555+
Options.SubnetNames = "default"
556+
_, _ = client.CreateVPC(context.TODO(), linodego.VPCCreateOptions{
557+
Label: "test1",
558+
Description: "",
559+
Region: "us-west",
560+
Subnets: []linodego.VPCSubnetCreateOptions{
561+
{
562+
Label: "default",
563+
IPv4: "10.0.0.0/8",
564+
},
565+
},
566+
})
567+
568+
svc := &v1.Service{
569+
ObjectMeta: metav1.ObjectMeta{
570+
Name: randString(),
571+
UID: "foobar123",
572+
Annotations: map[string]string{
573+
annotations.NodeBalancerBackendIPv4Range: "10.100.0.0/30",
574+
},
575+
},
576+
Spec: v1.ServiceSpec{
577+
Ports: []v1.ServicePort{
578+
{
579+
Name: randString(),
580+
Protocol: "TCP",
581+
Port: int32(80),
582+
NodePort: int32(30000),
583+
},
584+
},
585+
},
586+
}
587+
588+
nodes := []*v1.Node{
589+
{
590+
Status: v1.NodeStatus{
591+
Addresses: []v1.NodeAddress{
592+
{
593+
Type: v1.NodeInternalIP,
594+
Address: "127.0.0.1",
595+
},
596+
},
597+
},
598+
},
599+
}
600+
601+
lb := newLoadbalancers(client, "us-west").(*loadbalancers)
602+
fakeClientset := fake.NewSimpleClientset()
603+
lb.kubeClient = fakeClientset
604+
605+
defer func() {
606+
_ = lb.EnsureLoadBalancerDeleted(context.TODO(), "linodelb", svc)
607+
}()
608+
609+
lbStatus, err := lb.EnsureLoadBalancer(context.TODO(), "linodelb", svc, nodes)
610+
if err != nil {
611+
t.Errorf("EnsureLoadBalancer returned an error: %s", err)
612+
}
613+
svc.Status.LoadBalancer = *lbStatus
614+
615+
stubService(fakeClientset, svc)
616+
svc.ObjectMeta.SetAnnotations(map[string]string{
617+
annotations.NodeBalancerBackendIPv4Range: "10.100.1.0/30",
618+
})
619+
620+
err = lb.UpdateLoadBalancer(context.TODO(), "linodelb", svc, nodes)
621+
if err != nil {
622+
t.Errorf("UpdateLoadBalancer returned an error while updated annotations: %s", err)
623+
}
528624
}
529625

530626
func testCreateNodeBalancerWithVPCAnnotationOverwrite(t *testing.T, client *linodego.Client, f *fakeAPI) {
531627
// provision multiple vpcs
532628
vpcNames := Options.VPCNames
629+
nodebalancerBackendIPv4Subnet := Options.NodeBalancerBackendIPv4Subnet
533630
defer func() {
534631
Options.VPCNames = vpcNames
632+
Options.NodeBalancerBackendIPv4Subnet = nodebalancerBackendIPv4Subnet
535633
}()
536634
Options.VPCNames = "test1"
635+
Options.NodeBalancerBackendIPv4Subnet = "10.100.0.0/24"
537636

538637
_, _ = client.CreateVPC(context.TODO(), linodego.VPCCreateOptions{
539638
Label: "test1",
@@ -3887,3 +3986,39 @@ func Test_loadbalancers_GetLinodeNBType(t *testing.T) {
38873986
})
38883987
}
38893988
}
3989+
3990+
func Test_validateNodeBalancerBackendIPv4Range(t *testing.T) {
3991+
type args struct {
3992+
backendIPv4Range string
3993+
}
3994+
tests := []struct {
3995+
name string
3996+
args args
3997+
wantErr bool
3998+
}{
3999+
{
4000+
name: "Valid IPv4 range",
4001+
args: args{backendIPv4Range: "10.100.0.0/30"},
4002+
wantErr: false,
4003+
},
4004+
{
4005+
name: "Invalid IPv4 range",
4006+
args: args{backendIPv4Range: "10.100.0.0"},
4007+
wantErr: true,
4008+
},
4009+
}
4010+
4011+
nbBackendSubnet := Options.NodeBalancerBackendIPv4Subnet
4012+
defer func() {
4013+
Options.NodeBalancerBackendIPv4Subnet = nbBackendSubnet
4014+
}()
4015+
Options.NodeBalancerBackendIPv4Subnet = "10.100.0.0/24"
4016+
4017+
for _, tt := range tests {
4018+
t.Run(tt.name, func(t *testing.T) {
4019+
if err := validateNodeBalancerBackendIPv4Range(tt.args.backendIPv4Range); (err != nil) != tt.wantErr {
4020+
t.Errorf("validateNodeBalancerBackendIPv4Range() error = %v, wantErr %v", err, tt.wantErr)
4021+
}
4022+
})
4023+
}
4024+
}

deploy/chart/templates/daemonset.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ spec:
100100
{{- if .Values.defaultNBType }}
101101
- --default-nodebalancer-type={{ .Values.defaultNBType }}
102102
{{- end }}
103+
{{- if .Values.nodeBalancerBackendIPv4Subnet }}
104+
- --nodebalancer-backend-ipv4-subnet={{ .Values.nodeBalancerBackendIPv4Subnet }}
105+
{{- end }}
103106
{{- with .Values.containerSecurityContext }}
104107
securityContext:
105108
{{- toYaml . | nindent 12 }}

deploy/chart/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ tolerations:
8686
# Default NodeBalancer type to create("common" or "premium"). Default is "common"
8787
# defaultNBType: "common"
8888

89+
# nodeBalancerBackendIPv4Subnet is the subnet to use for the backend ips of the NodeBalancer
90+
# nodeBalancerBackendIPv4Subnet: ""
91+
8992
# This section adds the ability to pass environment variables to adjust CCM defaults
9093
# https://github.com/linode/linode-cloud-controller-manager/blob/master/cloud/linode/loadbalancers.go
9194
# LINODE_HOSTNAME_ONLY_INGRESS type bool is supported

docs/configuration/loadbalancer.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ metadata:
171171
service.beta.kubernetes.io/linode-loadbalancer-subnet-name: "subnet1"
172172
```
173173

174+
If CCM is started with `--nodebalancer-backend-ipv4-subnet` flag, then it will not allow provisioning of nodebalancer unless subnet specified in service annotation lie within the subnet specified using the flag. This is to prevent accidental overlap between nodebalancer backend ips and pod CIDRs.
175+
174176
## Advanced Configuration
175177

176178
### Using Existing NodeBalancers

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func main() {
9090
command.Flags().StringVar(&linode.Options.BGPNodeSelector, "bgp-node-selector", "", "node selector to use to perform shared IP fail-over with BGP (e.g. cilium-bgp-peering=true")
9191
command.Flags().StringVar(&linode.Options.IpHolderSuffix, "ip-holder-suffix", "", "suffix to append to the ip holder name when using shared IP fail-over with BGP (e.g. ip-holder-suffix=my-cluster-name")
9292
command.Flags().StringVar(&linode.Options.DefaultNBType, "default-nodebalancer-type", string(linodego.NBTypeCommon), "default type of NodeBalancer to create (options: common, premium)")
93+
command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4Subnet, "nodebalancer-backend-ipv4-subnet", "", "ipv4 subnet to use for NodeBalancer backends")
9394
command.Flags().StringSliceVar(&linode.Options.NodeBalancerTags, "nodebalancer-tags", []string{}, "Linode tags to apply to all NodeBalancers")
9495

9596
// Set static flags

0 commit comments

Comments
 (0)