Skip to content

Commit 3f37c52

Browse files
authored
[improvement][breaking] : allow auto-allocation of NB backend ips (linode#391)
* allow auto-allocation of NB backend ips * add priority for annotations and add unittests * fix couple of e2e tests and use latest linodego * address review comments and fix some failing tests * use var and also increase timeout for certain steps * add flag to disable nb-vpc integration, add docs for newly added flags * address review comments * move repeated code to separate script, address review comments * use subnet-id specified in flag if its set
1 parent e6f44b9 commit 3f37c52

File tree

49 files changed

+1268
-333
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1268
-333
lines changed

cloud/annotations/annotations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ const (
4747

4848
NodeBalancerBackendVPCName = "service.beta.kubernetes.io/linode-loadbalancer-backend-vpc-name"
4949
NodeBalancerBackendSubnetName = "service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-name"
50+
NodeBalancerBackendSubnetID = "service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-id"
5051
)

cloud/linode/cloud.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,25 @@ 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-
NodeBalancerBackendIPv4Subnet string
52-
GlobalStopChannel chan<- struct{}
53-
EnableIPv6ForLoadBalancers bool
54-
AllocateNodeCIDRs bool
55-
ClusterCIDRIPv4 string
56-
NodeCIDRMaskSizeIPv4 int
57-
NodeCIDRMaskSizeIPv6 int
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+
NodeBalancerBackendIPv4SubnetID int
53+
NodeBalancerBackendIPv4SubnetName string
54+
DisableNodeBalancerVPCBackends bool
55+
GlobalStopChannel chan<- struct{}
56+
EnableIPv6ForLoadBalancers bool
57+
AllocateNodeCIDRs bool
58+
ClusterCIDRIPv4 string
59+
NodeCIDRMaskSizeIPv4 int
60+
NodeCIDRMaskSizeIPv6 int
5861
}
5962

6063
type linodeCloud struct {
@@ -150,6 +153,22 @@ func newCloud() (cloudprovider.Interface, error) {
150153
Options.SubnetNames = ""
151154
}
152155

156+
if Options.NodeBalancerBackendIPv4SubnetID != 0 && Options.NodeBalancerBackendIPv4SubnetName != "" {
157+
return nil, fmt.Errorf("cannot have both --nodebalancer-backend-ipv4-subnet-id and --nodebalancer-backend-ipv4-subnet-name set")
158+
}
159+
160+
if Options.DisableNodeBalancerVPCBackends {
161+
klog.Infof("NodeBalancer VPC backends are disabled, no VPC backends will be created for NodeBalancers")
162+
Options.NodeBalancerBackendIPv4SubnetID = 0
163+
Options.NodeBalancerBackendIPv4SubnetName = ""
164+
} else if Options.NodeBalancerBackendIPv4SubnetName != "" {
165+
Options.NodeBalancerBackendIPv4SubnetID, err = getNodeBalancerBackendIPv4SubnetID(linodeClient)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to get backend IPv4 subnet ID for subnet name %s: %w", Options.NodeBalancerBackendIPv4SubnetName, err)
168+
}
169+
klog.Infof("Using NodeBalancer backend IPv4 subnet ID %d for subnet name %s", Options.NodeBalancerBackendIPv4SubnetID, Options.NodeBalancerBackendIPv4SubnetName)
170+
}
171+
153172
instanceCache = newInstances(linodeClient)
154173
routes, err := newRoutes(linodeClient, instanceCache)
155174
if err != nil {

cloud/linode/cloud_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,35 @@ func TestNewCloud(t *testing.T) {
8080
assert.Equal(t, "tt", Options.VPCNames, "expected vpcnames to be set to vpcname")
8181
})
8282

83+
t.Run("should fail if both nodeBalancerBackendIPv4SubnetID and nodeBalancerBackendIPv4SubnetName are set", func(t *testing.T) {
84+
Options.VPCNames = "tt"
85+
Options.NodeBalancerBackendIPv4SubnetID = 12345
86+
Options.NodeBalancerBackendIPv4SubnetName = "test-subnet"
87+
defer func() {
88+
Options.VPCNames = ""
89+
Options.NodeBalancerBackendIPv4SubnetID = 0
90+
Options.NodeBalancerBackendIPv4SubnetName = ""
91+
}()
92+
_, err := newCloud()
93+
assert.Error(t, err, "expected error when both nodeBalancerBackendIPv4SubnetID and nodeBalancerBackendIPv4SubnetName are set")
94+
})
95+
8396
t.Run("should fail if incorrect loadbalancertype is set", func(t *testing.T) {
8497
rtEnabled := Options.EnableRouteController
8598
Options.EnableRouteController = false
8699
Options.LoadBalancerType = "test"
100+
Options.VPCNames = "vpc-test1,vpc-test2"
101+
Options.NodeBalancerBackendIPv4SubnetName = "t1"
102+
vpcIDs = map[string]int{"vpc-test1": 1, "vpc-test2": 2, "vpc-test3": 3}
103+
subnetIDs = map[string]int{"t1": 1, "t2": 2, "t3": 3}
87104
defer func() {
88105
Options.LoadBalancerType = ""
89106
Options.EnableRouteController = rtEnabled
107+
Options.VPCNames = ""
108+
Options.NodeBalancerBackendIPv4SubnetID = 0
109+
Options.NodeBalancerBackendIPv4SubnetName = ""
110+
vpcIDs = map[string]int{}
111+
subnetIDs = map[string]int{}
90112
}()
91113
_, err := newCloud()
92114
assert.Error(t, err, "expected error if incorrect loadbalancertype is set")

cloud/linode/loadbalancers.go

Lines changed: 124 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -444,12 +444,16 @@ func (l *loadbalancers) updateNodeBalancer(
444444
// Add all of the Nodes to the config
445445
newNBNodes := make([]linodego.NodeBalancerConfigRebuildNodeOptions, 0, len(nodes))
446446
subnetID := 0
447+
if Options.NodeBalancerBackendIPv4SubnetID != 0 {
448+
subnetID = Options.NodeBalancerBackendIPv4SubnetID
449+
}
447450
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
448451
if ok {
449452
if err = validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
450453
return err
451454
}
452-
455+
}
456+
if Options.VPCNames != "" && !Options.DisableNodeBalancerVPCBackends {
453457
var id int
454458
id, err = l.getSubnetIDForSVC(ctx, service)
455459
if err != nil {
@@ -717,6 +721,86 @@ func (l *loadbalancers) GetLinodeNBType(service *v1.Service) linodego.NodeBalanc
717721
return linodego.NodeBalancerPlanType(Options.DefaultNBType)
718722
}
719723

724+
// getVPCCreateOptions returns the VPC options for the NodeBalancer creation.
725+
// Order of precedence:
726+
// 1. NodeBalancerBackendIPv4Range annotation
727+
// 2. NodeBalancerBackendVPCName and NodeBalancerBackendSubnetName annotation
728+
// 3. NodeBalancerBackendIPv4SubnetID/NodeBalancerBackendIPv4SubnetName flag
729+
// 4. NodeBalancerBackendIPv4Subnet flag
730+
// 5. Default to using the subnet ID of the service's VPC
731+
func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Service) ([]linodego.NodeBalancerVPCOptions, error) {
732+
// Evaluate subnetID based on annotations or flags
733+
subnetID, err := l.getSubnetIDForSVC(ctx, service)
734+
if err != nil {
735+
return nil, err
736+
}
737+
738+
// Precedence 1: If the user has specified a NodeBalancerBackendIPv4Range, use that
739+
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
740+
if ok {
741+
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
742+
return nil, err
743+
}
744+
// If the user has specified a NodeBalancerBackendIPv4Range, use that
745+
// for the NodeBalancer backend ipv4 range
746+
if backendIPv4Range != "" {
747+
vpcCreateOpts := []linodego.NodeBalancerVPCOptions{
748+
{
749+
SubnetID: subnetID,
750+
IPv4Range: backendIPv4Range,
751+
},
752+
}
753+
return vpcCreateOpts, nil
754+
}
755+
}
756+
757+
// Precedence 2: If the user wants to overwrite the default VPC name or subnet name
758+
// and have specified it in the annotations, use it to set subnetID
759+
// and auto-allocate subnets from it for the NodeBalancer
760+
_, vpcInAnnotation := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]
761+
_, subnetInAnnotation := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetName]
762+
if vpcInAnnotation || subnetInAnnotation {
763+
vpcCreateOpts := []linodego.NodeBalancerVPCOptions{
764+
{
765+
SubnetID: subnetID,
766+
},
767+
}
768+
return vpcCreateOpts, nil
769+
}
770+
771+
// Precedence 3: If the user has specified a NodeBalancerBackendIPv4SubnetID, use that
772+
// and auto-allocate subnets from it for the NodeBalancer
773+
if Options.NodeBalancerBackendIPv4SubnetID != 0 {
774+
vpcCreateOpts := []linodego.NodeBalancerVPCOptions{
775+
{
776+
SubnetID: Options.NodeBalancerBackendIPv4SubnetID,
777+
},
778+
}
779+
return vpcCreateOpts, nil
780+
}
781+
782+
// Precedence 4: If the user has specified a NodeBalancerBackendIPv4Subnet, use that
783+
// and auto-allocate subnets from it for the NodeBalancer
784+
if Options.NodeBalancerBackendIPv4Subnet != "" {
785+
vpcCreateOpts := []linodego.NodeBalancerVPCOptions{
786+
{
787+
SubnetID: subnetID,
788+
IPv4Range: Options.NodeBalancerBackendIPv4Subnet,
789+
IPv4RangeAutoAssign: true,
790+
},
791+
}
792+
return vpcCreateOpts, nil
793+
}
794+
795+
// Default to using the subnet ID of the service's VPC
796+
vpcCreateOpts := []linodego.NodeBalancerVPCOptions{
797+
{
798+
SubnetID: subnetID,
799+
},
800+
}
801+
return vpcCreateOpts, nil
802+
}
803+
720804
func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName string, service *v1.Service, configs []*linodego.NodeBalancerConfigCreateOptions) (lb *linodego.NodeBalancer, err error) {
721805
connThrottle := getConnectionThrottle(service)
722806

@@ -732,21 +816,11 @@ func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName stri
732816
Type: nbType,
733817
}
734818

735-
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
736-
if ok {
737-
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
738-
return nil, err
739-
}
740-
subnetID, err := l.getSubnetIDForSVC(ctx, service)
819+
if Options.VPCNames != "" && !Options.DisableNodeBalancerVPCBackends {
820+
createOpts.VPCs, err = l.getVPCCreateOptions(ctx, service)
741821
if err != nil {
742822
return nil, err
743823
}
744-
createOpts.VPCs = []linodego.NodeBalancerVPCOptions{
745-
{
746-
SubnetID: subnetID,
747-
IPv4Range: backendIPv4Range,
748-
},
749-
}
750824
}
751825

752826
fwid, ok := service.GetAnnotations()[annotations.AnnLinodeCloudFirewallID]
@@ -875,25 +949,49 @@ func (l *loadbalancers) addTLSCert(ctx context.Context, service *v1.Service, nbC
875949
return nil
876950
}
877951

878-
// getSubnetIDForSVC returns the subnet ID for the service's VPC and subnet.
879-
// By default, first VPCName and SubnetName are used to calculate subnet id for the service.
880-
// If the service has annotations specifying VPCName and SubnetName, they are used instead.
952+
// getSubnetIDForSVC returns the subnet ID for the service when running within VPC.
953+
// Following precedence rules are applied:
954+
// 1. If the service has an annotation for NodeBalancerBackendSubnetID, use that.
955+
// 2. If the service has annotations specifying VPCName or SubnetName, use them.
956+
// 3. If CCM is configured with --nodebalancer-backend-ipv4-subnet-id, it will be used as the subnet ID.
957+
// 4. Else, use first VPCName and SubnetName to calculate subnet id for the service.
881958
func (l *loadbalancers) getSubnetIDForSVC(ctx context.Context, service *v1.Service) (int, error) {
882959
if Options.VPCNames == "" {
883960
return 0, fmt.Errorf("CCM not configured with VPC, cannot create NodeBalancer with specified annotation")
884961
}
962+
// Check if the service has an annotation for NodeBalancerBackendSubnetID
963+
if specifiedSubnetID, ok := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetID]; ok {
964+
subnetID, err := strconv.Atoi(specifiedSubnetID)
965+
if err != nil {
966+
return 0, err
967+
}
968+
return subnetID, nil
969+
}
970+
971+
specifiedVPCName, vpcOk := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]
972+
specifiedSubnetName, subnetOk := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetName]
973+
974+
// If no VPCName or SubnetName is specified in annotations, but NodeBalancerBackendIPv4SubnetID is set,
975+
// use the NodeBalancerBackendIPv4SubnetID as the subnet ID.
976+
if !vpcOk && !subnetOk && Options.NodeBalancerBackendIPv4SubnetID != 0 {
977+
return Options.NodeBalancerBackendIPv4SubnetID, nil
978+
}
979+
885980
vpcName := strings.Split(Options.VPCNames, ",")[0]
886-
if specifiedVPCName, ok := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName]; ok {
981+
if vpcOk {
887982
vpcName = specifiedVPCName
888983
}
889984
vpcID, err := GetVPCID(ctx, l.client, vpcName)
890985
if err != nil {
891986
return 0, err
892987
}
988+
893989
subnetName := strings.Split(Options.SubnetNames, ",")[0]
894-
if specifiedSubnetName, ok := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetName]; ok {
990+
if subnetOk {
895991
subnetName = specifiedSubnetName
896992
}
993+
994+
// Use the VPC ID and Subnet Name to get the subnet ID
897995
return GetSubnetID(ctx, l.client, vpcID, subnetName)
898996
}
899997

@@ -907,11 +1005,17 @@ func (l *loadbalancers) buildLoadBalancerRequest(ctx context.Context, clusterNam
9071005
configs := make([]*linodego.NodeBalancerConfigCreateOptions, 0, len(ports))
9081006

9091007
subnetID := 0
1008+
if Options.NodeBalancerBackendIPv4SubnetID != 0 {
1009+
subnetID = Options.NodeBalancerBackendIPv4SubnetID
1010+
}
1011+
// Check for the NodeBalancerBackendIPv4Range annotation
9101012
backendIPv4Range, ok := service.GetAnnotations()[annotations.NodeBalancerBackendIPv4Range]
9111013
if ok {
9121014
if err := validateNodeBalancerBackendIPv4Range(backendIPv4Range); err != nil {
9131015
return nil, err
9141016
}
1017+
}
1018+
if Options.VPCNames != "" && !Options.DisableNodeBalancerVPCBackends {
9151019
id, err := l.getSubnetIDForSVC(ctx, service)
9161020
if err != nil {
9171021
return nil, err
@@ -1088,9 +1192,9 @@ func getPortConfigAnnotation(service *v1.Service, port int) (portConfigAnnotatio
10881192
}
10891193

10901194
// getNodePrivateIP provides the Linode Backend IP the NodeBalancer will communicate with.
1091-
// If a service specifies NodeBalancerBackendIPv4Range annotation, it will
1195+
// If CCM runs within VPC and DisableNodeBalancerVPCBackends is set to false, it will
10921196
// use NodeInternalIP of node.
1093-
// For services which don't have NodeBalancerBackendIPv4Range annotation,
1197+
// For services outside of VPC, it will use linode specific private IP address
10941198
// Backend IP can be overwritten to the one specified using AnnLinodeNodePrivateIP
10951199
// annotation over the NodeInternalIP.
10961200
func getNodePrivateIP(node *v1.Node, subnetID int) string {

0 commit comments

Comments
 (0)