@@ -121,37 +121,50 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
121121 return nil , fmt .Errorf ("load balancer with multiple protocols (%s) is not supported when AlphaFeatureMultiProtocolLB is disabled" , strings .Join (protocols , "," ))
122122 }
123123
124+ existingFRs , err := g .getExistingForwardingRules (loadBalancerName )
125+ if err != nil {
126+ return nil , fmt .Errorf ("error getting existing forwarding rules for %s: %v" , loadBalancerName , err )
127+ }
128+
124129 var fwdRuleIP string
125130 // We need to determine the IP address for all forwarding rules.
126131 // We check if any of the forwarding rules already exist and use their IP.
127- for protocol := range groupedPorts {
128- frName := g .getProtocolForwardingRuleName (loadBalancerName , protocol )
129- fwd , err := g .GetRegionForwardingRule (frName , g .region )
130- if err != nil && ! isNotFound (err ) {
131- return nil , err
132- }
133- if fwd != nil {
134- fwdRuleIP = fwd .IPAddress
135- break
136- }
137- }
138- // If no forwarding rule exists, check for the old one.
139- if fwdRuleIP == "" {
140- fwd , err := g .GetRegionForwardingRule (loadBalancerName , g .region )
141- if err != nil && ! isNotFound (err ) {
142- return nil , err
143- }
144- if fwd != nil {
145- fwdRuleIP = fwd .IPAddress
132+ for _ , fr := range existingFRs {
133+ if fr .IPAddress != "" {
134+ if fwdRuleIP == "" {
135+ fwdRuleIP = fr .IPAddress
136+ } else if fwdRuleIP != fr .IPAddress {
137+ return nil , fmt .Errorf ("found multiple forwarding rules with different IP addresses (%s and %s) for load balancer %s" , fwdRuleIP , fr .IPAddress , loadBalancerName )
138+ }
146139 }
147140 }
148141
149142 // Make sure we know which IP address will be used and have properly reserved
150143 // it as static before moving forward with the rest of our operations.
144+ //
145+ // We use static IP addresses when updating a load balancer to ensure that we
146+ // can replace the load balancer's other components without changing the
147+ // address its service is reachable on. We do it this way rather than always
148+ // keeping the static IP around even though this is more complicated because
149+ // it makes it less likely that we'll run into quota issues. Only 7 static
150+ // IP addresses are allowed per region by default.
151+ //
152+ // We could let an IP be allocated for us when the forwarding rule is created,
153+ // but we need the IP to set up the firewall rule, and we want to keep the
154+ // forwarding rule creation as the last thing that needs to be done in this
155+ // function in order to maintain the invariant that "if the forwarding rule
156+ // exists, the LB has been fully created".
151157 ipAddressToUse := ""
152- isUserOwnedIP := false
153- isSafeToReleaseIP := false
154158
159+ // Through this process we try to keep track of whether it is safe to
160+ // release the IP that was allocated. If the user specifically asked for
161+ // an IP, we assume they are managing it themselves. Otherwise, we will
162+ // release the IP in case of early-terminating failure or upon successful
163+ // creating of the LB.
164+ // TODO(#36535): boil this logic down into a set of component functions
165+ // and key the flag values off of errors returned.
166+ isUserOwnedIP := false // if this is set, we never release the IP
167+ isSafeToReleaseIP := false
155168 defer func () {
156169 if isUserOwnedIP {
157170 return
@@ -195,6 +208,12 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
195208 ipAddressToUse = ipAddr
196209 }
197210
211+ // Now we have the IP address we can build the desired forwarding rules.
212+ desiredFRs , err := g .buildDesiredForwardingRules (loadBalancerName , serviceName .String (), ipAddressToUse , g .targetPoolURL (loadBalancerName ), apiService , netTier , existingFRs )
213+ if err != nil {
214+ return nil , fmt .Errorf ("error building desired forwarding rules for %s: %v" , loadBalancerName , err )
215+ }
216+
198217 // Deal with the firewall next. The reason we do this here rather than last
199218 // is because the forwarding rule is used as the indicator that the load
200219 // balancer is fully created - it's what getLoadBalancer checks for.
@@ -257,33 +276,42 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
257276 } else {
258277 klog .V (4 ).Infof ("ensureExternalLoadBalancer(%s): Service needs nodes health checks." , lbRefStr )
259278 if hcLocalTrafficExisting != nil {
279+ // This logic exists to detect a transition from OnlyLocal to non-OnlyLocal service
280+ // and turn on the tpNeedsRecreation flag to delete/recreate fwdrule/tpool updating the
281+ // target pool to use nodes health check.
260282 klog .V (2 ).Infof ("ensureExternalLoadBalancer(%s): Updating from local traffic health checks to nodes health checks." , lbRefStr )
261283 hcToDelete = hcLocalTrafficExisting
262284 tpNeedsRecreation = true
263285 }
264286 hcToCreate = makeHTTPHealthCheck (MakeNodesHealthCheckName (clusterID ), GetNodesHealthCheckPath (), GetNodesHealthCheckPort ())
265287 }
266288
267- if err := g .ensureTargetPoolAndHealthCheck (tpExists , tpNeedsRecreation , apiService , loadBalancerName , clusterID , ipAddressToUse , hosts , hcToCreate , hcToDelete ); err != nil {
268- return nil , err
269- }
270-
271- // Forwarding rule logic
272- // We build the desired forwarding rules and then sync them with what exists.
273- existingFRs , err := g .getExistingForwardingRules (loadBalancerName )
274- if err != nil {
275- return nil , fmt .Errorf ("error getting existing forwarding rules for %s: %v" , loadBalancerName , err )
276- }
289+ // Now we get to some slightly more interesting logic.
290+ // First, neither target pools nor forwarding rules can be updated in place -
291+ // they have to be deleted and recreated.
292+ // Second, forwarding rules are layered on top of target pools in that you
293+ // can't delete a target pool that's currently in use by a forwarding rule.
294+ // Thus, we have to tear down a forwarding rule if either it or the target
295+ // pool needs to be updated.
296+ if len (existingFRs ) > 0 && tpNeedsRecreation {
297+ // Begin critical section. If we have to delete the forwarding rule,
298+ // and something should fail before we recreate it, don't release the
299+ // IP. That way we can come back to it later.
300+ isSafeToReleaseIP = false
301+
302+ for _ , fr := range existingFRs {
303+ if err := g .DeleteRegionForwardingRule (fr .Name , g .region ); err != nil && ! isNotFound (err ) {
304+ return nil , fmt .Errorf ("failed to delete existing forwarding rule for load balancer (%s) update: %v" , lbRefStr , err )
305+ }
306+ }
307+ klog .Infof ("ensureExternalLoadBalancer(%s): Deleted forwarding rule(s)." , lbRefStr )
277308
278- // Get the old forwarding rule for backward compatibility.
279- var oldFwdRule * compute.ForwardingRule
280- if fwd , ok := existingFRs [loadBalancerName ]; ok {
281- oldFwdRule = fwd
309+ // Clear the existing forwarding rules so we don't try to delete them again.
310+ existingFRs = nil
282311 }
283312
284- desiredFRs , err := g .buildDesiredForwardingRules (loadBalancerName , serviceName .String (), ipAddressToUse , g .targetPoolURL (loadBalancerName ), apiService , netTier , oldFwdRule )
285- if err != nil {
286- return nil , fmt .Errorf ("error building desired forwarding rules for %s: %v" , loadBalancerName , err )
313+ if err := g .ensureTargetPoolAndHealthCheck (tpExists , tpNeedsRecreation , apiService , loadBalancerName , clusterID , ipAddressToUse , hosts , hcToCreate , hcToDelete ); err != nil {
314+ return nil , err
287315 }
288316
289317 // Delete unwanted forwarding rules.
@@ -299,28 +327,25 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
299327 // Create or update forwarding rules.
300328 for _ , desiredFR := range desiredFRs {
301329 existingFR , exists := existingFRs [desiredFR .Name ]
302-
303- needsUpdate := false
304330 if exists {
305- if ! g .forwardingRulesEqual (existingFR , desiredFR ) {
306- needsUpdate = true
331+ if g .forwardingRulesEqual (existingFR , desiredFR ) {
332+ continue
307333 }
308334 }
309335
310- if ! exists || needsUpdate || tpNeedsRecreation {
311- if exists {
312- klog .Infof ("ensureExternalLoadBalancer(%s): Deleting forwarding rule %s to update." , lbRefStr , desiredFR .Name )
313- if err := g .DeleteRegionForwardingRule (desiredFR .Name , g .region ); err != nil && ! isNotFound (err ) {
314- return nil , err
315- }
316- }
317- klog .Infof ("ensureExternalLoadBalancer(%s): Creating forwarding rule %s for protocol %s, IP %s (tier: %s)." , lbRefStr , desiredFR .Name , desiredFR .IPProtocol , ipAddressToUse , netTier )
318- // The desiredFR is a complete spec, so we can just pass it to CreateRegionForwardingRule.
319- if err := g .CreateRegionForwardingRule (desiredFR , g .region ); err != nil && ! isHTTPErrorCode (err , http .StatusConflict ) {
320- return nil , fmt .Errorf ("failed to create forwarding rule for load balancer (%s): %v" , lbRefStr , err )
336+ // We can't update a forwarding rule in place, so we have to delete it and recreate it.
337+ if exists {
338+ klog .Infof ("ensureExternalLoadBalancer(%s): Deleting forwarding rule %s to update." , lbRefStr , desiredFR .Name )
339+ if err := g .DeleteRegionForwardingRule (desiredFR .Name , g .region ); err != nil && ! isNotFound (err ) {
340+ return nil , err
321341 }
322- klog .Infof ("ensureExternalLoadBalancer(%s): Created forwarding rule %s." , lbRefStr , desiredFR .Name )
323342 }
343+ klog .Infof ("ensureExternalLoadBalancer(%s): Creating forwarding rule %s for protocol %s, IP %s (tier: %s)." , lbRefStr , desiredFR .Name , desiredFR .IPProtocol , ipAddressToUse , netTier )
344+ // The desiredFR is a complete spec, so we can just pass it to CreateRegionForwardingRule.
345+ if err := g .CreateRegionForwardingRule (desiredFR , g .region ); err != nil && ! isHTTPErrorCode (err , http .StatusConflict ) {
346+ return nil , fmt .Errorf ("failed to create forwarding rule for load balancer (%s): %v" , lbRefStr , err )
347+ }
348+ klog .Infof ("ensureExternalLoadBalancer(%s): Created forwarding rule %s." , lbRefStr , desiredFR .Name )
324349 }
325350
326351 isSafeToReleaseIP = true
@@ -339,19 +364,32 @@ func (g *Cloud) getExistingForwardingRules(loadBalancerName string) (map[string]
339364
340365 existingFRs := make (map [string ]* compute.ForwardingRule )
341366 for _ , fr := range frs {
342- if strings .HasPrefix (fr .Name , loadBalancerName ) {
367+ isMatch := false
368+ if fr .Name == loadBalancerName {
369+ isMatch = true
370+ } else if fr .Name == g .getProtocolForwardingRuleName (loadBalancerName , v1 .Protocol (fr .IPProtocol )) {
371+ isMatch = true
372+ }
373+
374+ if isMatch {
343375 existingFRs [fr .Name ] = fr
344376 }
345377 }
346378 return existingFRs , nil
347379}
348380
349381// buildDesiredForwardingRules builds the desired forwarding rules for the given load balancer.
350- func (g * Cloud ) buildDesiredForwardingRules (loadBalancerName , serviceName , ipAddress , targetPoolURL string , apiService * v1.Service , netTier cloud.NetworkTier , oldFwdRule * compute.ForwardingRule ) (map [string ]* compute.ForwardingRule , error ) {
382+ func (g * Cloud ) buildDesiredForwardingRules (loadBalancerName , serviceName , ipAddress , targetPoolURL string , apiService * v1.Service , netTier cloud.NetworkTier , existingFRs map [ string ] * compute.ForwardingRule ) (map [string ]* compute.ForwardingRule , error ) {
351383 desiredFRs := make (map [string ]* compute.ForwardingRule )
352384 groupedPorts := groupPortsByProtocol (apiService .Spec .Ports )
353385 desc := makeServiceDescription (serviceName )
354386
387+ // Get the old forwarding rule for backward compatibility.
388+ var oldFwdRule * compute.ForwardingRule
389+ if fwd , ok := existingFRs [loadBalancerName ]; ok {
390+ oldFwdRule = fwd
391+ }
392+
355393 for protocol , protocolPorts := range groupedPorts {
356394 var frName string
357395 if g .AlphaFeatureGate .Enabled (AlphaFeatureMultiProtocolLB ) {
@@ -415,7 +453,6 @@ func (g *Cloud) forwardingRulesEqual(existing, desired *compute.ForwardingRule)
415453 return true
416454}
417455
418-
419456// updateExternalLoadBalancer is the external implementation of LoadBalancer.UpdateLoadBalancer.
420457func (g * Cloud ) updateExternalLoadBalancer (clusterName string , service * v1.Service , nodes []* v1.Node ) error {
421458 // Process services with LoadBalancerClass "networking.gke.io/l4-regional-external-legacy" used for this controller.
@@ -500,17 +537,16 @@ func (g *Cloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID string,
500537 }
501538
502539 // TODO: Always or just with alpha feature flag?
503- frs , err := g .ListRegionForwardingRules ( g . region )
540+ existingFRs , err := g .getExistingForwardingRules ( loadBalancerName )
504541 if err != nil {
505542 return err
506543 }
544+
507545 var deleteErrs []error
508- for _ , fr := range frs {
509- if strings .HasPrefix (fr .Name , loadBalancerName ) {
510- klog .Infof ("ensureExternalLoadBalancerDeleted(%s): Deleting forwarding rule %s." , lbRefStr , fr .Name )
511- if err := ignoreNotFound (g .DeleteRegionForwardingRule (fr .Name , g .region )); err != nil {
512- deleteErrs = append (deleteErrs , err )
513- }
546+ for _ , fr := range existingFRs {
547+ klog .Infof ("ensureExternalLoadBalancerDeleted(%s): Deleting forwarding rule %s." , lbRefStr , fr .Name )
548+ if err := ignoreNotFound (g .DeleteRegionForwardingRule (fr .Name , g .region )); err != nil {
549+ deleteErrs = append (deleteErrs , err )
514550 }
515551 }
516552 if len (deleteErrs ) > 0 {
@@ -908,8 +944,6 @@ func (g *Cloud) ensureHTTPHealthCheck(name, path string, port int32) (hc *comput
908944 return hc , nil
909945}
910946
911-
912-
913947// Doesn't check whether the hosts have changed, since host updating is handled
914948// separately.
915949func (g * Cloud ) targetPoolNeedsRecreation (name , region string , affinityType v1.ServiceAffinity ) (exists bool , needsRecreation bool , err error ) {
@@ -1145,8 +1179,6 @@ func (g *Cloud) ensureHTTPHealthCheckFirewall(svc *v1.Service, serviceName, ipAd
11451179 return nil
11461180}
11471181
1148-
1149-
11501182func (g * Cloud ) createFirewall (svc * v1.Service , name , desc , destinationIP string , sourceRanges utilnet.IPNetSet , ports []v1.ServicePort , hosts []* gceInstance ) error {
11511183 firewall , err := g .firewallObject (name , desc , destinationIP , sourceRanges , ports , hosts )
11521184 if err != nil {
0 commit comments