@@ -67,60 +67,94 @@ const additionalTargetGroupPrefix = "additional-listener-"
67
67
// cantAttachSGToNLBRegions is a set of regions that do not support Security Groups in NLBs.
68
68
var cantAttachSGToNLBRegions = sets .New ("us-iso-east-1" , "us-iso-west-1" , "us-isob-east-1" )
69
69
70
+ type lbReconciler func () error
71
+
70
72
// ReconcileLoadbalancers reconciles the load balancers for the given cluster.
71
73
func (s * Service ) ReconcileLoadbalancers () error {
72
74
s .scope .Debug ("Reconciling load balancers" )
73
75
74
76
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.
75
85
76
86
for _ , lbSpec := range s .scope .ControlPlaneLoadBalancers () {
77
87
if lbSpec == nil {
78
88
continue
79
89
}
80
90
switch lbSpec .LoadBalancerType {
81
91
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
+ }
83
98
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
+ }
85
105
default :
86
106
errs = append (errs , fmt .Errorf ("unknown or unsupported load balancer type on primary load balancer: %s" , lbSpec .LoadBalancerType ))
87
107
}
88
108
}
89
109
110
+ // Reconcile all load balancers
111
+ for _ , reconciler := range lbReconcilers {
112
+ if err := reconciler (); err != nil {
113
+ errs = append (errs , err )
114
+ }
115
+ }
116
+
90
117
return kerrors .NewAggregate (errs )
91
118
}
92
119
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 ) {
96
124
name , err := LBName (s .scope , lbSpec )
97
125
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" )
99
127
}
100
128
101
129
// Get default api server spec.
102
130
desiredLB , err := s .getAPIServerLBSpec (name , lbSpec )
103
131
if err != nil {
104
- return err
132
+ return nil , err
105
133
}
106
134
lb , err := s .describeLB (name , lbSpec )
107
135
switch {
108
136
case IsNotFound (err ) && s .scope .ControlPlaneEndpoint ().IsValid ():
109
137
// 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 ())
111
139
case IsNotFound (err ):
112
140
lb , err = s .createLB (desiredLB , lbSpec )
113
141
if err != nil {
114
142
s .scope .Error (err , "failed to create LB" )
115
- return err
143
+ return nil , err
116
144
}
117
145
118
146
s .scope .Debug ("Created new network load balancer for apiserver" , "api-server-lb-name" , lb .Name )
119
147
case err != nil :
120
148
// Failed to describe the classic ELB
121
- return err
149
+ return nil , err
122
150
}
123
151
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 {
124
158
wReq := & elbv2.DescribeLoadBalancersInput {
125
159
LoadBalancerArns : aws .StringSlice ([]string {lb .ARN }),
126
160
}
@@ -507,37 +541,46 @@ func (s *Service) describeLB(name string, lbSpec *infrav1.AWSLoadBalancerSpec) (
507
541
return fromSDKTypeToLB (out .LoadBalancers [0 ], outAtt .Attributes , tags ), nil
508
542
}
509
543
510
- func (s * Service ) reconcileClassicLoadBalancer () error {
544
+ // getOrCreateClassicLoadBalancer gets an existing classic load balancer, or creates a new one if it does not exist.
545
+ // It also takes care of generating unique names across namespaces by appending the namespace to the name.
546
+ // It returns a function that reconciles the load balancer.
547
+ func (s * Service ) getOrCreateClassicLoadBalancer () (lbReconciler , error ) {
511
548
// Generate a default control plane load balancer name. The load balancer name cannot be
512
549
// generated by the defaulting webhook, because it is derived from the cluster name, and that
513
550
// name is undefined at defaulting time when generateName is used.
514
551
name , err := ELBName (s .scope )
515
552
if err != nil {
516
- return errors .Wrap (err , "failed to get control plane load balancer name" )
553
+ return nil , errors .Wrap (err , "failed to get control plane load balancer name" )
517
554
}
518
555
519
556
// Get default api server spec.
520
557
spec , err := s .getAPIServerClassicELBSpec (name )
521
558
if err != nil {
522
- return err
559
+ return nil , err
523
560
}
524
561
525
562
apiELB , err := s .describeClassicELB (spec .Name )
526
563
switch {
527
564
case IsNotFound (err ) && s .scope .ControlPlaneEndpoint ().IsValid ():
528
565
// 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 ())
566
+ 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 ())
530
567
case IsNotFound (err ):
531
568
apiELB , err = s .createClassicELB (spec )
532
569
if err != nil {
533
- return err
570
+ return nil , err
534
571
}
535
572
s .scope .Debug ("Created new classic load balancer for apiserver" , "api-server-elb-name" , apiELB .Name )
536
573
case err != nil :
537
574
// Failed to describe the classic ELB
538
- return err
575
+ return nil , err
539
576
}
540
577
578
+ return func () error {
579
+ return s .reconcileClassicLoadBalancer (apiELB , spec )
580
+ }, nil
581
+ }
582
+
583
+ func (s * Service ) reconcileClassicLoadBalancer (apiELB * infrav1.LoadBalancer , spec * infrav1.LoadBalancer ) error {
541
584
if apiELB .IsManaged (s .scope .Name ()) {
542
585
if ! cmp .Equal (spec .ClassicElbAttributes , apiELB .ClassicElbAttributes ) {
543
586
err := s .configureAttributes (apiELB .Name , spec .ClassicElbAttributes )
@@ -546,6 +589,9 @@ func (s *Service) reconcileClassicLoadBalancer() error {
546
589
}
547
590
}
548
591
592
+ // BUG: note that describeClassicELB doesn't set HealthCheck in its output,
593
+ // so we're configuring the health check on every reconcile whether it's
594
+ // needed or not.
549
595
if ! cmp .Equal (spec .HealthCheck , apiELB .HealthCheck ) {
550
596
s .scope .Debug ("Reconciling health check for apiserver load balancer" , "health-check" , spec .HealthCheck )
551
597
err := s .configureHealthCheck (apiELB .Name , spec .HealthCheck )
@@ -597,7 +643,7 @@ func (s *Service) reconcileClassicLoadBalancer() error {
597
643
}
598
644
599
645
func (s * Service ) configureHealthCheck (name string , healthCheck * infrav1.ClassicELBHealthCheck ) error {
600
- if _ , err := s . ELBClient . ConfigureHealthCheck ( & elb.ConfigureHealthCheckInput {
646
+ healthCheckInput := & elb.ConfigureHealthCheckInput {
601
647
LoadBalancerName : aws .String (name ),
602
648
HealthCheck : & elb.HealthCheck {
603
649
Target : aws .String (healthCheck .Target ),
@@ -606,7 +652,14 @@ func (s *Service) configureHealthCheck(name string, healthCheck *infrav1.Classic
606
652
HealthyThreshold : aws .Int64 (healthCheck .HealthyThreshold ),
607
653
UnhealthyThreshold : aws .Int64 (healthCheck .UnhealthyThreshold ),
608
654
},
609
- }); err != nil {
655
+ }
656
+
657
+ if err := wait .WaitForWithRetryable (wait .NewBackoff (), func () (bool , error ) {
658
+ if _ , err := s .ELBClient .ConfigureHealthCheck (healthCheckInput ); err != nil {
659
+ return false , err
660
+ }
661
+ return true , nil
662
+ }, awserrors .LoadBalancerNotFound ); err != nil {
610
663
return errors .Wrapf (err , "failed to configure health check for classic load balancer: %s" , name )
611
664
}
612
665
return nil
@@ -1193,30 +1246,15 @@ func (s *Service) createClassicELB(spec *infrav1.LoadBalancer) (*infrav1.LoadBal
1193
1246
return nil , errors .Wrapf (err , "failed to create classic load balancer: %v" , spec )
1194
1247
}
1195
1248
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
-
1216
1249
s .scope .Info ("Created classic load balancer" , "dns-name" , * out .DNSName )
1217
1250
1218
1251
res := spec .DeepCopy ()
1219
1252
res .DNSName = * out .DNSName
1253
+
1254
+ // We haven't configured any health check yet. Don't report it here so it
1255
+ // will be set later during reconciliation.
1256
+ res .HealthCheck = nil
1257
+
1220
1258
return res , nil
1221
1259
}
1222
1260
0 commit comments