Skip to content

Commit c2d2f7c

Browse files
committed
feat(l4): Add support for enabling L4 NetLB Regional Backend Services by default
1 parent 1b510a7 commit c2d2f7c

File tree

7 files changed

+163
-11
lines changed

7 files changed

+163
-11
lines changed

cmd/glbc/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ func main() {
365365
EnableL4ILBZonalAffinity: flags.F.EnableL4ILBZonalAffinity,
366366
EnableL4NetLBForwardingRulesOptimizations: flags.F.EnableL4NetLBForwardingRulesOptimizations,
367367
ReadOnlyMode: flags.F.ReadOnlyMode,
368+
EnableL4NetLBRBSByDefault: flags.F.EnableL4NetLBRBSByDefault,
368369
}
369370
ctx, err := ingctx.NewControllerContext(kubeClient, backendConfigClient, frontendConfigClient, firewallCRClient, svcNegClient, svcAttachmentClient, networkClient, nodeTopologyClient, eventRecorderKubeClient, cloud, namer, kubeSystemUID, ctxConfig, rootLogger)
370371
if err != nil {

pkg/context/context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ type ControllerContextConfig struct {
138138
EnableL4ILBMixedProtocol bool
139139
EnableL4NetLBMixedProtocol bool
140140
EnableL4NetLBForwardingRulesOptimizations bool
141+
EnableL4NetLBRBSByDefault bool
141142
}
142143

143144
// NewControllerContext returns a new shared set of informers.

pkg/flags/flags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ var F = struct {
149149
EnableNEGsForIngress bool
150150
L4ILBLegacyHeadStartTime time.Duration
151151
EnableIPv6NodeNEGEndpoints bool
152+
EnableL4NetLBRBSByDefault bool
153+
L4NetLBLegacyHeadStartTime time.Duration
152154

153155
// ===============================
154156
// DEPRECATED FLAGS
@@ -362,6 +364,8 @@ L7 load balancing. CSV values accepted. Example: -node-port-ranges=80,8080,400-5
362364
flag.BoolVar(&F.EnableNEGsForIngress, "enable-negs-for-ingress", true, "Allow the NEG controller to create NEGs for Ingress services.")
363365
flag.DurationVar(&F.L4ILBLegacyHeadStartTime, "prevent-legacy-race-l4-ilb", 0*time.Second, "Delay before processing new L4 ILB services without existing finalizers. This gives the legacy controller a head start to claim the service, preventing a race condition upon service creation.")
364366
flag.BoolVar(&F.EnableIPv6NodeNEGEndpoints, "enable-ipv6-node-neg-endpoints", false, "Enable populating IPv6 addresses for Node IPs in GCE_VM_IP NEGs.")
367+
flag.BoolVar(&F.EnableL4NetLBRBSByDefault, "enable-l4-netlb-rbs-by-default", false, "Enable L4 NetLB Regional Backend Services by default for new L4 NetLB services.")
368+
flag.DurationVar(&F.L4NetLBLegacyHeadStartTime, "prevent-legacy-race-l4-netlb", 0*time.Second, "Delay before processing new L4 NetLB services without existing finalizers. This gives the legacy controller a head start to claim the service, preventing a race condition upon service creation.")
365369
}
366370

367371
func Validate() {

pkg/l4lb/l4netlbcontroller.go

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"k8s.io/ingress-gce/pkg/backends"
3737
"k8s.io/ingress-gce/pkg/common/operator"
3838
"k8s.io/ingress-gce/pkg/context"
39+
3940
"k8s.io/ingress-gce/pkg/flags"
4041
"k8s.io/ingress-gce/pkg/forwardingrules"
4142
"k8s.io/ingress-gce/pkg/instancegroups"
@@ -58,6 +59,8 @@ const (
5859

5960
instanceGroupLink backendLinkType = 0
6061
negLink backendLinkType = 1
62+
63+
delayDurationForLegacyOwnershipPriority = 5 * time.Second // Configurable via flag if needed
6164
)
6265

6366
type backendLinkType int64
@@ -86,6 +89,7 @@ type L4NetLBController struct {
8689
serviceVersions *serviceVersionsTracker
8790
enableNEGSupport bool
8891
enableNEGAsDefault bool
92+
enableRBSDefault bool
8993

9094
hasSynced func() bool
9195

@@ -122,6 +126,7 @@ func NewL4NetLBController(
122126
serviceVersions: NewServiceVersionsTracker(),
123127
logger: logger,
124128
hasSynced: ctx.HasSynced,
129+
enableRBSDefault: ctx.EnableL4NetLBRBSByDefault,
125130
}
126131
var networkLister cache.Indexer
127132
if ctx.NetworkInformer != nil {
@@ -359,6 +364,7 @@ func (lc *L4NetLBController) shouldProcessService(newSvc, oldSvc *v1.Service, sv
359364
warnL4FinalizerRemoved(lc.ctx, oldSvc, newSvc)
360365

361366
if !lc.isRBSBasedService(newSvc, svcLogger) && !lc.isRBSBasedService(oldSvc, svcLogger) {
367+
svcLogger.V(4).Info("Ignoring non RBS based NetLB service")
362368
return false, false
363369
}
364370
if lc.needsAddition(newSvc, oldSvc) || lc.needsUpdate(newSvc, oldSvc) || lc.needsDeletion(newSvc, svcLogger) {
@@ -384,34 +390,95 @@ func (lc *L4NetLBController) isRBSBasedService(svc *v1.Service, svcLogger klog.L
384390
// Check if the type=LoadBalancer, so we don't execute API calls o non-LB services
385391
// this call is nil-safe
386392
if !utils.IsLoadBalancerServiceType(svc) {
393+
svcLogger.V(4).Info("Service is not of type LoadBalancer")
387394
return false
388395
}
389396
if svc.Spec.LoadBalancerClass != nil {
397+
svcLogger.V(4).Info("Service has LoadBalancerClass annotation", "loadBalancerClass", *svc.Spec.LoadBalancerClass)
390398
return annotations.HasLoadBalancerClass(svc, annotations.RegionalExternalLoadBalancerClass)
391399
}
392-
return annotations.HasRBSAnnotation(svc) || utils.HasL4NetLBFinalizerV2(svc) || utils.HasL4NetLBFinalizerV3(svc) || lc.hasRBSForwardingRule(svc, svcLogger)
400+
if lc.enableRBSDefault {
401+
svcLogger.V(4).Info("RBS is enabled by default, treating service as RBS based")
402+
return true
403+
}
404+
if utils.HasL4NetLBFinalizerV2(svc) || utils.HasL4NetLBFinalizerV3(svc) {
405+
svcLogger.V(4).Info("Service has L4 NetLB RBS finalizer")
406+
return true
407+
}
408+
if annotations.HasRBSAnnotation(svc) {
409+
svcLogger.V(4).Info("Service has RBS annotation")
410+
return true
411+
}
412+
if utils.HasL4NetLBFinalizerV1(svc) {
413+
svcLogger.V(4).Info("Service has Legacy L4 NetLB finalizer")
414+
return false
415+
}
416+
if lc.hasRBSForwardingRule(svc, svcLogger) {
417+
svcLogger.V(4).Info("Service has RBS forwarding rule")
418+
return true
419+
}
420+
return false
421+
}
422+
423+
func (lc *L4NetLBController) hasLegacyControllerOwnership(svc *v1.Service, svcLogger klog.Logger) bool {
424+
// Check for legacy finalizer
425+
if utils.HasL4NetLBFinalizerV1(svc) {
426+
return true
427+
}
428+
429+
// Check if forwarding rule points to a target pool
430+
return lc.hasTargetPoolForwardingRule(svc, svcLogger)
431+
}
432+
433+
// refreshServiceFromK8s fetches the latest version of the service from Kubernetes
434+
// This is needed after adding a delay to ensure we have the most up-to-date version
435+
// which may include finalizers added by other controllers
436+
func (lc *L4NetLBController) refreshServiceFromK8s(service *v1.Service, svcLogger klog.Logger) *v1.Service {
437+
svcLogger.V(3).Info("Refreshing service from Kubernetes",
438+
"currentResourceVersion", service.ResourceVersion)
439+
440+
// Get the latest service from Kubernetes
441+
svcKey := utils.ServiceKeyFunc(service.Namespace, service.Name)
442+
refreshedService, exists, err := lc.ctx.Services().GetByKey(svcKey)
443+
if err != nil {
444+
svcLogger.Info("Could not get service from store, using existing one, error: ", err)
445+
} else if exists {
446+
service = refreshedService
447+
svcLogger.Info("finalizer Found service in informer store after wait, using it.")
448+
} else {
449+
// Service might have been deleted during the wait, return to avoid processing a non-existing service.
450+
service = nil
451+
svcLogger.Info("Service not found in informer store after wait, ignoring processing.")
452+
}
453+
return service
393454
}
394455

395456
func (lc *L4NetLBController) preventLegacyServiceHandling(service *v1.Service, key string, svcLogger klog.Logger) (bool, error) {
396-
if (annotations.HasRBSAnnotation(service) || annotations.HasLoadBalancerClass(service, annotations.RegionalExternalLoadBalancerClass)) && lc.hasTargetPoolForwardingRule(service, svcLogger) {
397-
if utils.HasL4NetLBFinalizerV2(service) || utils.HasL4NetLBFinalizerV3(service) {
457+
svcLogger.Info("Checking for legacy target pool service with RBS annotation or finalizers", "finalizers", service.ObjectMeta.Finalizers)
458+
if lc.hasLegacyControllerOwnership(service, svcLogger) {
459+
if utils.HasL4NetLBRBSFinalizers(service) {
398460
// If we found that RBS finalizer was attached to service, it means that RBS controller
399461
// had a race condition on Service creation with Legacy Controller.
400462
// It should only happen during service creation, and we should clean up RBS resources
463+
svcLogger.Info("Detected Target Pool on RBS service with RBS finalizer. Cleaning up RBS resources to prevent race condition.", "finalizers", service.ObjectMeta.Finalizers)
401464
return true, lc.preventTargetPoolRaceWithRBSOnCreation(service, key, svcLogger)
402-
} else {
465+
} else if annotations.HasRBSAnnotation(service) {
403466
// Target Pool to RBS migration is NOT yet supported and causes service to break (for now).
404467
// If we detect RBS annotation on legacy service, we remove RBS annotation,
405468
// so service stays with Legacy Target Pool implementation
469+
svcLogger.Info("Detected Target Pool on service with RBS annotation. Removing RBS annotation to prevent unsupported migration.", "finalizers", service.ObjectMeta.Finalizers)
406470
return true, lc.preventExistingTargetPoolToRBSMigration(service, svcLogger)
407471
}
472+
svcLogger.Info("Service is legacy Target Pool based service. No action needed.", "finalizers", service.ObjectMeta.Finalizers)
473+
return true, nil
408474
}
409475
return false, nil
410476
}
411477

412478
func (lc *L4NetLBController) hasTargetPoolForwardingRule(service *v1.Service, svcLogger klog.Logger) bool {
413479
frName := utils.LegacyForwardingRuleName(service)
414480
if lc.hasForwardingRuleAnnotation(service, frName) {
481+
svcLogger.V(4).Info("Service does not have Target Pool forwarding rule annotation", "forwardingRule", frName)
415482
return false
416483
}
417484

@@ -421,8 +488,10 @@ func (lc *L4NetLBController) hasTargetPoolForwardingRule(service *v1.Service, sv
421488
return false
422489
}
423490
if existingFR != nil && existingFR.Target != "" {
491+
svcLogger.V(4).Info("Service has Target Pool forwarding rule", "forwardingRule", frName, "targetPool", strings.Split(existingFR.Target, "/")[len(strings.Split(existingFR.Target, "/"))-1])
424492
return true
425493
}
494+
svcLogger.V(4).Info("Service does not have Target Pool forwarding rule", "forwardingRule", frName)
426495
return false
427496
}
428497

@@ -611,6 +680,12 @@ func (lc *L4NetLBController) syncInternal(service *v1.Service, svcLogger klog.Lo
611680
svcLogger.Info("Finished syncing L4 NetLB RBS service", "timeTaken", time.Since(startTime))
612681
}()
613682

683+
// Prevent race condition with legacy controller
684+
service = lc.handleCreationRace(service, svcLogger)
685+
if service == nil {
686+
return nil
687+
}
688+
614689
usesNegBackends := lc.shouldUseNEGBackends(service, svcLogger)
615690

616691
l4NetLBParams := &l4resources.L4NetLBParams{
@@ -976,3 +1051,36 @@ func (lc *L4NetLBController) publishSyncMetrics(result *l4resources.L4NetLBSyncR
9761051

9771052
metrics.PublishNetLBSyncMetrics(result.Error == nil, result.SyncType, result.GCEResourceInError, utils.GetErrorType(result.Error), result.StartTime, isResync, isWeightedLB, result.MetricsState.Protocol, backendType)
9781053
}
1054+
1055+
// handleCreationRace prevents a race condition between the legacy and new L4 NetLB controllers
1056+
// when a service is created, ensuring the L4 controller processes the most recent service
1057+
// state and avoids conflicting operations..
1058+
func (lc *L4NetLBController) handleCreationRace(service *v1.Service, svcLogger klog.Logger) *v1.Service {
1059+
l4NetLBLegacyHeadStartTime := flags.F.L4NetLBLegacyHeadStartTime
1060+
hasL4NetLBRBSFinalizers := utils.HasL4NetLBRBSFinalizers(service)
1061+
hasLegacyL4NetLBFinalizerV1 := utils.HasL4NetLBFinalizerV1(service)
1062+
1063+
if !hasLegacyL4NetLBFinalizerV1 && !hasL4NetLBRBSFinalizers && l4NetLBLegacyHeadStartTime > 0*time.Second {
1064+
// Add a delay to allow legacy controller to potentially claim the service first
1065+
svcLogger.Info("Service does not have RBS finalizer yet and RBS is default. Adding delay to allow legacy controller to act first",
1066+
"delay", delayDurationForLegacyOwnershipPriority)
1067+
time.Sleep(delayDurationForLegacyOwnershipPriority)
1068+
1069+
// Refresh the service from Kubernetes to get the latest version
1070+
// This is critical because the legacy controller may have added its finalizer (NetLBFinalizerV1)
1071+
// during the delay period
1072+
refreshedService := lc.refreshServiceFromK8s(service, svcLogger)
1073+
1074+
// Use the refreshed service for the rest of the sync
1075+
service = refreshedService
1076+
1077+
// Also check explicitly for legacy ownership to be extra safe
1078+
if lc.hasLegacyControllerOwnership(service, svcLogger) {
1079+
svcLogger.Info("After delay and refresh, detected legacy controller ownership. Skipping RBS sync.")
1080+
return nil
1081+
}
1082+
1083+
svcLogger.Info("After delay and refresh, proceeding with RBS sync", "resourceVersion", service.ResourceVersion)
1084+
}
1085+
return service
1086+
}

pkg/l4lb/l4netlbcontroller_test.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ func getFakeGCECloud(vals gce.TestClusterValues) *gce.Cloud {
311311
return fakeGCE
312312
}
313313

314-
func buildContext(vals gce.TestClusterValues, readOnlyMode bool) (*ingctx.ControllerContext, error) {
314+
func buildContext(vals gce.TestClusterValues, readOnlyMode bool, isRBSdefault bool) (*ingctx.ControllerContext, error) {
315315
fakeGCE := getFakeGCECloud(vals)
316316
kubeClient := fake.NewSimpleClientset()
317317
networkClient := netfake.NewSimpleClientset()
@@ -332,12 +332,20 @@ func buildContext(vals gce.TestClusterValues, readOnlyMode bool) (*ingctx.Contro
332332
}
333333

334334
func newL4NetLBServiceController() *L4NetLBController {
335-
return createL4NetLBServiceController(test.DefaultTestClusterValues(), false)
335+
return createL4NetLBServiceController(test.DefaultTestClusterValues(), false, false)
336336
}
337337

338-
func createL4NetLBServiceController(vals gce.TestClusterValues, readOnlyMode bool) *L4NetLBController {
338+
func newL4NetLBServiceControllerReadOnlyMode(readOnlyMode bool) *L4NetLBController {
339+
return createL4NetLBServiceController(test.DefaultTestClusterValues(), readOnlyMode, false)
340+
}
341+
342+
func newL4NetLBServiceControllerRBSDefault(isRBSdefault bool) *L4NetLBController {
343+
return createL4NetLBServiceController(test.DefaultTestClusterValues(), false, isRBSdefault)
344+
}
345+
346+
func createL4NetLBServiceController(vals gce.TestClusterValues, readOnlyMode bool, isRBSdefault bool) *L4NetLBController {
339347
stopCh := make(chan struct{})
340-
ctx, err := buildContext(vals, readOnlyMode)
348+
ctx, err := buildContext(vals, readOnlyMode, isRBSdefault)
341349
if err != nil {
342350
klog.Fatalf("Failed to build context: %v", err)
343351
}
@@ -1667,6 +1675,7 @@ func TestIsRBSBasedService(t *testing.T) {
16671675
finalizers []string
16681676
annotations map[string]string
16691677
frHook getForwardingRuleHook
1678+
isRBSDefault bool
16701679
expectRBSService bool
16711680
}{
16721681
{
@@ -1703,13 +1712,30 @@ func TestIsRBSBasedService(t *testing.T) {
17031712
frHook: test.GetLegacyForwardingRule,
17041713
expectRBSService: false,
17051714
},
1715+
{
1716+
desc: "Should detect RBS by default",
1717+
isRBSDefault: true,
1718+
expectRBSService: true,
1719+
},
1720+
{
1721+
desc: "Should not detect RBS by forwarding rule pointed to target pool, even if RBS is default",
1722+
frHook: test.GetLegacyForwardingRule,
1723+
isRBSDefault: true,
1724+
expectRBSService: false,
1725+
},
1726+
{
1727+
desc: "Legacy service should not be marked as RBS, even if RBS is default",
1728+
finalizers: []string{common.NetLBFinalizerV1},
1729+
isRBSDefault: true,
1730+
expectRBSService: false,
1731+
},
17061732
}
17071733

17081734
for _, testCase := range testCases {
17091735
t.Run(testCase.desc, func(t *testing.T) {
17101736
// Setup
17111737
svc := test.NewL4LegacyNetLBService(8080, 30234)
1712-
controller := newL4NetLBServiceController()
1738+
controller := newL4NetLBServiceControllerRBSDefault(testCase.isRBSDefault)
17131739
svc.Annotations = testCase.annotations
17141740
svc.ObjectMeta.Finalizers = testCase.finalizers
17151741
controller.ctx.Cloud.Compute().(*cloud.MockGCE).MockForwardingRules.GetHook = testCase.frHook
@@ -2318,7 +2344,7 @@ func TestEnsureExternalLoadBalancerClass(t *testing.T) {
23182344
},
23192345
} {
23202346
t.Run(tc.desc, func(t *testing.T) {
2321-
lc := createL4NetLBServiceController(test.DefaultTestClusterValues(), false)
2347+
lc := newL4NetLBServiceController()
23222348

23232349
svc := test.NewL4LBServiceWithLoadBalancerClass(tc.loadBalancerClass)
23242350
if tc.loadBalancerClass == "" {
@@ -2421,7 +2447,7 @@ func TestEnsureReadOnlyModeDoesNotProvision(t *testing.T) {
24212447
},
24222448
} {
24232449
t.Run(tc.desc, func(t *testing.T) {
2424-
lc := createL4NetLBServiceController(test.DefaultTestClusterValues(), tc.readOnlyModeEnabled)
2450+
lc := newL4NetLBServiceControllerReadOnlyMode(tc.readOnlyModeEnabled)
24252451

24262452
svc := test.NewL4LBServiceWithLoadBalancerClass(tc.loadBalancerClass)
24272453
if tc.loadBalancerClass == "" {

pkg/utils/common/finalizer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const (
4040
ILBFinalizerV2 = "gke.networking.io/l4-ilb-v2"
4141
// NegFinalizerKey is the finalizer used by neg controller to ensure NEG CRs are deleted after corresponding negs are deleted
4242
NegFinalizerKey = "networking.gke.io/neg-finalizer"
43+
// NetLBFinalizerV1 is the finalizer used by legacy cloud controller manager that manage L4 External LoadBalancer services.
44+
NetLBFinalizerV1 = "gke.networking.io/l4-netlb-v1"
4345
// NetLBFinalizerV2 is the finalizer used by newer controllers that manage L4 External LoadBalancer services.
4446
NetLBFinalizerV2 = "gke.networking.io/l4-netlb-v2"
4547
// NetLBFinalizerV3 is the finalizer used by the NEG backed variant of the L4 External LoadBalancer services.

pkg/utils/utils.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,16 @@ func HasL4ILBFinalizerV2(svc *api_v1.Service) bool {
761761
return slice.ContainsString(svc.ObjectMeta.Finalizers, common.ILBFinalizerV2, nil)
762762
}
763763

764+
// HasL4NetLBFinalizerV1 returns true if the given Service has NetLBFinalizerV1
765+
func HasL4NetLBFinalizerV1(svc *api_v1.Service) bool {
766+
return slice.ContainsString(svc.ObjectMeta.Finalizers, common.NetLBFinalizerV1, nil)
767+
}
768+
769+
// HasL4NetLBRBSFinalizers returns true if the given Service has any of the NetLBRBS finalizers v2 or v3
770+
func HasL4NetLBRBSFinalizers(svc *api_v1.Service) bool {
771+
return HasL4NetLBFinalizerV2(svc) || HasL4NetLBFinalizerV3(svc)
772+
}
773+
764774
// HasL4NetLBFinalizerV2 returns true if the given Service has NetLBFinalizerV2
765775
func HasL4NetLBFinalizerV2(svc *api_v1.Service) bool {
766776
return slice.ContainsString(svc.ObjectMeta.Finalizers, common.NetLBFinalizerV2, nil)

0 commit comments

Comments
 (0)