@@ -67,60 +67,94 @@ const additionalTargetGroupPrefix = "additional-listener-"
6767// cantAttachSGToNLBRegions is a set of regions that do not support Security Groups in NLBs.
6868var cantAttachSGToNLBRegions = sets .New ("us-iso-east-1" , "us-iso-west-1" , "us-isob-east-1" )
6969
70+ type lbReconciler func () error
71+
7072// ReconcileLoadbalancers reconciles the load balancers for the given cluster.
7173func (s * Service ) ReconcileLoadbalancers () error {
7274 s .scope .Debug ("Reconciling load balancers" )
7375
7476 var errs []error
77+ var lbReconcilers []lbReconciler
78+
79+ // The following splits load balancer reconciliation into 2 phases:
80+ // 1. Get or create the load balancer
81+ // 2. Reconcile the load balancer
82+ // We ensure that we only wait for the load balancer to become available in
83+ // the reconcile phase. This is useful when creating multiple load
84+ // balancers, as they can take several minutes to become available.
7585
7686 for _ , lbSpec := range s .scope .ControlPlaneLoadBalancers () {
7787 if lbSpec == nil {
7888 continue
7989 }
8090 switch lbSpec .LoadBalancerType {
8191 case infrav1 .LoadBalancerTypeClassic :
82- errs = append (errs , s .reconcileClassicLoadBalancer ())
92+ reconciler , err := s .getOrCreateClassicLoadBalancer ()
93+ if err != nil {
94+ errs = append (errs , err )
95+ } else {
96+ lbReconcilers = append (lbReconcilers , reconciler )
97+ }
8398 case infrav1 .LoadBalancerTypeNLB , infrav1 .LoadBalancerTypeALB , infrav1 .LoadBalancerTypeELB :
84- errs = append (errs , s .reconcileV2LB (lbSpec ))
99+ reconciler , err := s .getOrCreateV2LB (lbSpec )
100+ if err != nil {
101+ errs = append (errs , err )
102+ } else {
103+ lbReconcilers = append (lbReconcilers , reconciler )
104+ }
85105 default :
86106 errs = append (errs , fmt .Errorf ("unknown or unsupported load balancer type on primary load balancer: %s" , lbSpec .LoadBalancerType ))
87107 }
88108 }
89109
110+ // Reconcile all load balancers
111+ for _ , reconciler := range lbReconcilers {
112+ if err := reconciler (); err != nil {
113+ errs = append (errs , err )
114+ }
115+ }
116+
90117 return kerrors .NewAggregate (errs )
91118}
92119
93- // reconcileV2LB creates a load balancer. It also takes care of generating unique names across
94- // namespaces by appending the namespace to the name.
95- func (s * Service ) reconcileV2LB (lbSpec * infrav1.AWSLoadBalancerSpec ) error {
120+ // getOrCreateV2LB gets an existing load balancer, or creates a new one if it does not exist.
121+ // It also takes care of generating unique names across namespaces by appending the namespace to the name.
122+ // It returns a function that reconciles the load balancer.
123+ func (s * Service ) getOrCreateV2LB (lbSpec * infrav1.AWSLoadBalancerSpec ) (lbReconciler , error ) {
96124 name , err := LBName (s .scope , lbSpec )
97125 if err != nil {
98- return errors .Wrap (err , "failed to get control plane load balancer name" )
126+ return nil , errors .Wrap (err , "failed to get control plane load balancer name" )
99127 }
100128
101129 // Get default api server spec.
102130 desiredLB , err := s .getAPIServerLBSpec (name , lbSpec )
103131 if err != nil {
104- return err
132+ return nil , err
105133 }
106134 lb , err := s .describeLB (name , lbSpec )
107135 switch {
108136 case IsNotFound (err ) && s .scope .ControlPlaneEndpoint ().IsValid ():
109137 // if elb is not found and owner cluster ControlPlaneEndpoint is already populated, then we should not recreate the elb.
110- return errors .Wrapf (err , "no loadbalancer exists for the AWSCluster %s, the cluster has become unrecoverable and should be deleted manually" , s .scope .InfraClusterName ())
138+ return nil , errors .Wrapf (err , "no loadbalancer exists for the AWSCluster %s, the cluster has become unrecoverable and should be deleted manually" , s .scope .InfraClusterName ())
111139 case IsNotFound (err ):
112140 lb , err = s .createLB (desiredLB , lbSpec )
113141 if err != nil {
114142 s .scope .Error (err , "failed to create LB" )
115- return err
143+ return nil , err
116144 }
117145
118146 s .scope .Debug ("Created new network load balancer for apiserver" , "api-server-lb-name" , lb .Name )
119147 case err != nil :
120148 // Failed to describe the classic ELB
121- return err
149+ return nil , err
122150 }
123151
152+ return func () error {
153+ return s .reconcileV2LB (lb , desiredLB , lbSpec )
154+ }, nil
155+ }
156+
157+ func (s * Service ) reconcileV2LB (lb * infrav1.LoadBalancer , desiredLB * infrav1.LoadBalancer , lbSpec * infrav1.AWSLoadBalancerSpec ) error {
124158 wReq := & elbv2.DescribeLoadBalancersInput {
125159 LoadBalancerArns : aws .StringSlice ([]string {lb .ARN }),
126160 }
@@ -442,12 +476,21 @@ func (s *Service) createLB(spec *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBa
442476
443477 // Target Groups and listeners will be reconciled separately
444478
445- s .scope .Info ("Created network load balancer" , "dns-name" , * out .LoadBalancers [0 ].DNSName )
479+ if out .LoadBalancers [0 ].DNSName == nil {
480+ return nil , fmt .Errorf ("CreateLoadBalancer did not return a DNS name for %s" , spec .Name )
481+ }
482+ dnsName := * out .LoadBalancers [0 ].DNSName
483+ if out .LoadBalancers [0 ].LoadBalancerArn == nil {
484+ return nil , fmt .Errorf ("CreateLoadBalancer did not return an ARN for %s" , spec .Name )
485+ }
486+ arn := * out .LoadBalancers [0 ].LoadBalancerArn
487+
488+ s .scope .Info ("Created network load balancer" , "dns-name" , dnsName )
446489
447490 res := spec .DeepCopy ()
448- s .scope .Debug ("applying load balancer DNS to result" , "dns" , * out . LoadBalancers [ 0 ]. DNSName )
449- res .DNSName = * out . LoadBalancers [ 0 ]. DNSName
450- res .ARN = * out . LoadBalancers [ 0 ]. LoadBalancerArn
491+ s .scope .Debug ("applying load balancer DNS to result" , "dns" , dnsName )
492+ res .DNSName = dnsName
493+ res .ARN = arn
451494 return res , nil
452495}
453496
@@ -507,37 +550,46 @@ func (s *Service) describeLB(name string, lbSpec *infrav1.AWSLoadBalancerSpec) (
507550 return fromSDKTypeToLB (out .LoadBalancers [0 ], outAtt .Attributes , tags ), nil
508551}
509552
510- func (s * Service ) reconcileClassicLoadBalancer () error {
553+ // getOrCreateClassicLoadBalancer gets an existing classic load balancer, or creates a new one if it does not exist.
554+ // It also takes care of generating unique names across namespaces by appending the namespace to the name.
555+ // It returns a function that reconciles the load balancer.
556+ func (s * Service ) getOrCreateClassicLoadBalancer () (lbReconciler , error ) {
511557 // Generate a default control plane load balancer name. The load balancer name cannot be
512558 // generated by the defaulting webhook, because it is derived from the cluster name, and that
513559 // name is undefined at defaulting time when generateName is used.
514560 name , err := ELBName (s .scope )
515561 if err != nil {
516- return errors .Wrap (err , "failed to get control plane load balancer name" )
562+ return nil , errors .Wrap (err , "failed to get control plane load balancer name" )
517563 }
518564
519565 // Get default api server spec.
520566 spec , err := s .getAPIServerClassicELBSpec (name )
521567 if err != nil {
522- return err
568+ return nil , err
523569 }
524570
525571 apiELB , err := s .describeClassicELB (spec .Name )
526572 switch {
527573 case IsNotFound (err ) && s .scope .ControlPlaneEndpoint ().IsValid ():
528574 // if elb is not found and owner cluster ControlPlaneEndpoint is already populated, then we should not recreate the elb.
529- return errors .Wrapf (err , "no loadbalancer exists for the AWSCluster %s, the cluster has become unrecoverable and should be deleted manually" , s .scope .InfraClusterName ())
575+ return nil , errors .Wrapf (err , "no loadbalancer exists for the AWSCluster %s, the cluster has become unrecoverable and should be deleted manually" , s .scope .InfraClusterName ())
530576 case IsNotFound (err ):
531577 apiELB , err = s .createClassicELB (spec )
532578 if err != nil {
533- return err
579+ return nil , err
534580 }
535581 s .scope .Debug ("Created new classic load balancer for apiserver" , "api-server-elb-name" , apiELB .Name )
536582 case err != nil :
537583 // Failed to describe the classic ELB
538- return err
584+ return nil , err
539585 }
540586
587+ return func () error {
588+ return s .reconcileClassicLoadBalancer (apiELB , spec )
589+ }, nil
590+ }
591+
592+ func (s * Service ) reconcileClassicLoadBalancer (apiELB * infrav1.LoadBalancer , spec * infrav1.LoadBalancer ) error {
541593 if apiELB .IsManaged (s .scope .Name ()) {
542594 if ! cmp .Equal (spec .ClassicElbAttributes , apiELB .ClassicElbAttributes ) {
543595 err := s .configureAttributes (apiELB .Name , spec .ClassicElbAttributes )
@@ -546,6 +598,9 @@ func (s *Service) reconcileClassicLoadBalancer() error {
546598 }
547599 }
548600
601+ // BUG: note that describeClassicELB doesn't set HealthCheck in its output,
602+ // so we're configuring the health check on every reconcile whether it's
603+ // needed or not.
549604 if ! cmp .Equal (spec .HealthCheck , apiELB .HealthCheck ) {
550605 s .scope .Debug ("Reconciling health check for apiserver load balancer" , "health-check" , spec .HealthCheck )
551606 err := s .configureHealthCheck (apiELB .Name , spec .HealthCheck )
@@ -597,7 +652,7 @@ func (s *Service) reconcileClassicLoadBalancer() error {
597652}
598653
599654func (s * Service ) configureHealthCheck (name string , healthCheck * infrav1.ClassicELBHealthCheck ) error {
600- if _ , err := s . ELBClient . ConfigureHealthCheck ( & elb.ConfigureHealthCheckInput {
655+ healthCheckInput := & elb.ConfigureHealthCheckInput {
601656 LoadBalancerName : aws .String (name ),
602657 HealthCheck : & elb.HealthCheck {
603658 Target : aws .String (healthCheck .Target ),
@@ -606,7 +661,14 @@ func (s *Service) configureHealthCheck(name string, healthCheck *infrav1.Classic
606661 HealthyThreshold : aws .Int64 (healthCheck .HealthyThreshold ),
607662 UnhealthyThreshold : aws .Int64 (healthCheck .UnhealthyThreshold ),
608663 },
609- }); err != nil {
664+ }
665+
666+ if err := wait .WaitForWithRetryable (wait .NewBackoff (), func () (bool , error ) {
667+ if _ , err := s .ELBClient .ConfigureHealthCheck (healthCheckInput ); err != nil {
668+ return false , err
669+ }
670+ return true , nil
671+ }, awserrors .LoadBalancerNotFound ); err != nil {
610672 return errors .Wrapf (err , "failed to configure health check for classic load balancer: %s" , name )
611673 }
612674 return nil
@@ -1193,30 +1255,15 @@ func (s *Service) createClassicELB(spec *infrav1.LoadBalancer) (*infrav1.LoadBal
11931255 return nil , errors .Wrapf (err , "failed to create classic load balancer: %v" , spec )
11941256 }
11951257
1196- if spec .HealthCheck != nil {
1197- if err := wait .WaitForWithRetryable (wait .NewBackoff (), func () (bool , error ) {
1198- if _ , err := s .ELBClient .ConfigureHealthCheck (& elb.ConfigureHealthCheckInput {
1199- LoadBalancerName : aws .String (spec .Name ),
1200- HealthCheck : & elb.HealthCheck {
1201- Target : aws .String (spec .HealthCheck .Target ),
1202- Interval : aws .Int64 (int64 (spec .HealthCheck .Interval .Seconds ())),
1203- Timeout : aws .Int64 (int64 (spec .HealthCheck .Timeout .Seconds ())),
1204- HealthyThreshold : aws .Int64 (spec .HealthCheck .HealthyThreshold ),
1205- UnhealthyThreshold : aws .Int64 (spec .HealthCheck .UnhealthyThreshold ),
1206- },
1207- }); err != nil {
1208- return false , err
1209- }
1210- return true , nil
1211- }, awserrors .LoadBalancerNotFound ); err != nil {
1212- return nil , errors .Wrapf (err , "failed to configure health check for classic load balancer: %v" , spec )
1213- }
1214- }
1215-
12161258 s .scope .Info ("Created classic load balancer" , "dns-name" , * out .DNSName )
12171259
12181260 res := spec .DeepCopy ()
12191261 res .DNSName = * out .DNSName
1262+
1263+ // We haven't configured any health check yet. Don't report it here so it
1264+ // will be set later during reconciliation.
1265+ res .HealthCheck = nil
1266+
12201267 return res , nil
12211268}
12221269
0 commit comments