Skip to content

Commit 1f523ff

Browse files
committed
Create multiple control plane loadbalancers concurrently
1 parent 63b8bcf commit 1f523ff

File tree

2 files changed

+78
-36
lines changed

2 files changed

+78
-36
lines changed

pkg/cloud/services/elb/loadbalancer.go

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -67,60 +67,93 @@ const additionalTargetGroupPrefix = "additional-listener-"
6767
// cantAttachSGToNLBRegions is a set of regions that do not support Security Groups in NLBs.
6868
var 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.
7173
func (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
120+
// getOrCreateV2LB creates a load balancer. It also takes care of generating unique names across
94121
// namespaces by appending the namespace to the name.
95-
func (s *Service) reconcileV2LB(lbSpec *infrav1.AWSLoadBalancerSpec) error {
122+
func (s *Service) getOrCreateV2LB(lbSpec *infrav1.AWSLoadBalancerSpec) (lbReconciler, error) {
96123
name, err := LBName(s.scope, lbSpec)
97124
if err != nil {
98-
return errors.Wrap(err, "failed to get control plane load balancer name")
125+
return nil, errors.Wrap(err, "failed to get control plane load balancer name")
99126
}
100127

101128
// Get default api server spec.
102129
desiredLB, err := s.getAPIServerLBSpec(name, lbSpec)
103130
if err != nil {
104-
return err
131+
return nil, err
105132
}
106133
lb, err := s.describeLB(name, lbSpec)
107134
switch {
108135
case IsNotFound(err) && s.scope.ControlPlaneEndpoint().IsValid():
109136
// 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())
137+
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())
111138
case IsNotFound(err):
112139
lb, err = s.createLB(desiredLB, lbSpec)
113140
if err != nil {
114141
s.scope.Error(err, "failed to create LB")
115-
return err
142+
return nil, err
116143
}
117144

118145
s.scope.Debug("Created new network load balancer for apiserver", "api-server-lb-name", lb.Name)
119146
case err != nil:
120147
// Failed to describe the classic ELB
121-
return err
148+
return nil, err
122149
}
123150

151+
return func() error {
152+
return s.reconcileV2LB(lb, desiredLB, lbSpec)
153+
}, nil
154+
}
155+
156+
func (s *Service) reconcileV2LB(lb *infrav1.LoadBalancer, desiredLB *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBalancerSpec) error {
124157
wReq := &elbv2.DescribeLoadBalancersInput{
125158
LoadBalancerArns: aws.StringSlice([]string{lb.ARN}),
126159
}
@@ -507,35 +540,61 @@ func (s *Service) describeLB(name string, lbSpec *infrav1.AWSLoadBalancerSpec) (
507540
return fromSDKTypeToLB(out.LoadBalancers[0], outAtt.Attributes, tags), nil
508541
}
509542

510-
func (s *Service) reconcileClassicLoadBalancer() error {
543+
func (s *Service) getOrCreateClassicLoadBalancer() (lbReconciler, error) {
511544
// Generate a default control plane load balancer name. The load balancer name cannot be
512545
// generated by the defaulting webhook, because it is derived from the cluster name, and that
513546
// name is undefined at defaulting time when generateName is used.
514547
name, err := ELBName(s.scope)
515548
if err != nil {
516-
return errors.Wrap(err, "failed to get control plane load balancer name")
549+
return nil, errors.Wrap(err, "failed to get control plane load balancer name")
517550
}
518551

519552
// Get default api server spec.
520553
spec, err := s.getAPIServerClassicELBSpec(name)
521554
if err != nil {
522-
return err
555+
return nil, err
523556
}
524557

525558
apiELB, err := s.describeClassicELB(spec.Name)
526559
switch {
527560
case IsNotFound(err) && s.scope.ControlPlaneEndpoint().IsValid():
528561
// 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())
562+
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())
530563
case IsNotFound(err):
531564
apiELB, err = s.createClassicELB(spec)
532565
if err != nil {
533-
return err
566+
return nil, err
534567
}
535568
s.scope.Debug("Created new classic load balancer for apiserver", "api-server-elb-name", apiELB.Name)
536569
case err != nil:
537570
// Failed to describe the classic ELB
538-
return err
571+
return nil, err
572+
}
573+
574+
return func() error {
575+
return s.reconcileClassicLoadBalancer(apiELB, spec)
576+
}, nil
577+
}
578+
579+
func (s *Service) reconcileClassicLoadBalancer(apiELB *infrav1.LoadBalancer, spec *infrav1.LoadBalancer) error {
580+
if spec.HealthCheck != nil {
581+
if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
582+
if _, err := s.ELBClient.ConfigureHealthCheck(&elb.ConfigureHealthCheckInput{
583+
LoadBalancerName: aws.String(spec.Name),
584+
HealthCheck: &elb.HealthCheck{
585+
Target: aws.String(spec.HealthCheck.Target),
586+
Interval: aws.Int64(int64(spec.HealthCheck.Interval.Seconds())),
587+
Timeout: aws.Int64(int64(spec.HealthCheck.Timeout.Seconds())),
588+
HealthyThreshold: aws.Int64(spec.HealthCheck.HealthyThreshold),
589+
UnhealthyThreshold: aws.Int64(spec.HealthCheck.UnhealthyThreshold),
590+
},
591+
}); err != nil {
592+
return false, err
593+
}
594+
return true, nil
595+
}, awserrors.LoadBalancerNotFound); err != nil {
596+
return errors.Wrapf(err, "failed to configure health check for classic load balancer: %v", spec)
597+
}
539598
}
540599

541600
if apiELB.IsManaged(s.scope.Name()) {
@@ -1193,26 +1252,6 @@ func (s *Service) createClassicELB(spec *infrav1.LoadBalancer) (*infrav1.LoadBal
11931252
return nil, errors.Wrapf(err, "failed to create classic load balancer: %v", spec)
11941253
}
11951254

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-
12161255
s.scope.Info("Created classic load balancer", "dns-name", *out.DNSName)
12171256

12181257
res := spec.DeepCopy()

pkg/cloud/services/elb/loadbalancer_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,7 +2546,10 @@ func TestReconcileV2LB(t *testing.T) {
25462546
scope: clusterScope,
25472547
ELBV2Client: elbV2APIMocks,
25482548
}
2549-
err = s.reconcileV2LB(clusterScope.ControlPlaneLoadBalancer())
2549+
reconciler, err := s.getOrCreateV2LB(clusterScope.ControlPlaneLoadBalancer())
2550+
if err == nil {
2551+
err = reconciler()
2552+
}
25502553
lb := s.scope.Network().APIServerELB
25512554

25522555
tc.check(t, &lb, err)

0 commit comments

Comments
 (0)