Skip to content

Commit 16a6004

Browse files
committed
Reconcile target groups and listeners separately
If a load balancer is created but the calls to create a target group or listener fails (for example, due to a rate limit), creating the group or listener is never retried. This results in a load balancer that's created, but there is nothing attached to it, making it useless. Signed-off-by: Nolan Brubaker <[email protected]>
1 parent 3a28a4d commit 16a6004

File tree

2 files changed

+457
-89
lines changed

2 files changed

+457
-89
lines changed

pkg/cloud/services/elb/loadbalancer.go

Lines changed: 115 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ func (s *Service) reconcileV2LB(lbSpec *infrav1.AWSLoadBalancerSpec) error {
112112
// set up the type for later processing
113113
lb.LoadBalancerType = lbSpec.LoadBalancerType
114114
if lb.IsManaged(s.scope.Name()) {
115+
// Reconcile the target groups and listeners from the spec and the ones currently attached to the load balancer.
116+
_, err := s.reconcileTargetGroupsAndListeners(lb, lbSpec)
117+
if err != nil {
118+
return errors.Wrapf(err, "failed to create target groups/listeners for load balancer %q", lb.Name)
119+
}
120+
115121
if !cmp.Equal(spec.ELBAttributes, lb.ELBAttributes) {
116122
if err := s.configureLBAttributes(lb.ARN, spec.ELBAttributes); err != nil {
117123
return err
@@ -147,6 +153,7 @@ func (s *Service) reconcileV2LB(lbSpec *infrav1.AWSLoadBalancerSpec) error {
147153
return errors.Wrapf(err, "failed to apply security groups to load balancer %q", lb.Name)
148154
}
149155
}
156+
150157
} else {
151158
s.scope.Trace("Unmanaged control plane load balancer, skipping load balancer configuration", "api-server-elb", lb)
152159
}
@@ -388,89 +395,7 @@ func (s *Service) createLB(spec *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBa
388395
return nil, errors.New("no new network load balancer was created; the returned list is empty")
389396
}
390397

391-
// TODO(Skarlso): Add options to set up SSL.
392-
// https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3899
393-
for _, ln := range spec.ELBListeners {
394-
// create the target group first
395-
targetGroupInput := &elbv2.CreateTargetGroupInput{
396-
Name: aws.String(ln.TargetGroup.Name),
397-
Port: aws.Int64(ln.TargetGroup.Port),
398-
Protocol: aws.String(ln.TargetGroup.Protocol.String()),
399-
VpcId: aws.String(ln.TargetGroup.VpcID),
400-
Tags: input.Tags,
401-
HealthCheckIntervalSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckIntervalSec),
402-
HealthCheckTimeoutSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckTimeoutSec),
403-
HealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerHealthThresholdCount),
404-
UnhealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerUnhealthThresholdCount),
405-
}
406-
if s.scope.VPC().IsIPv6Enabled() {
407-
targetGroupInput.IpAddressType = aws.String("ipv6")
408-
}
409-
if ln.TargetGroup.HealthCheck != nil {
410-
targetGroupInput.HealthCheckEnabled = aws.Bool(true)
411-
targetGroupInput.HealthCheckProtocol = ln.TargetGroup.HealthCheck.Protocol
412-
targetGroupInput.HealthCheckPort = ln.TargetGroup.HealthCheck.Port
413-
if ln.TargetGroup.HealthCheck.Path != nil {
414-
targetGroupInput.HealthCheckPath = ln.TargetGroup.HealthCheck.Path
415-
}
416-
if ln.TargetGroup.HealthCheck.IntervalSeconds != nil {
417-
targetGroupInput.HealthCheckIntervalSeconds = ln.TargetGroup.HealthCheck.IntervalSeconds
418-
}
419-
if ln.TargetGroup.HealthCheck.TimeoutSeconds != nil {
420-
targetGroupInput.HealthCheckTimeoutSeconds = ln.TargetGroup.HealthCheck.TimeoutSeconds
421-
}
422-
if ln.TargetGroup.HealthCheck.ThresholdCount != nil {
423-
targetGroupInput.HealthyThresholdCount = ln.TargetGroup.HealthCheck.ThresholdCount
424-
}
425-
if ln.TargetGroup.HealthCheck.UnhealthyThresholdCount != nil {
426-
targetGroupInput.UnhealthyThresholdCount = ln.TargetGroup.HealthCheck.UnhealthyThresholdCount
427-
}
428-
}
429-
s.scope.Debug("creating target group", "group", targetGroupInput, "listener", ln)
430-
group, err := s.ELBV2Client.CreateTargetGroup(targetGroupInput)
431-
if err != nil {
432-
return nil, errors.Wrapf(err, "failed to create target group for load balancer")
433-
}
434-
if len(group.TargetGroups) == 0 {
435-
return nil, errors.New("no target group was created; the returned list is empty")
436-
}
437-
438-
if !lbSpec.PreserveClientIP {
439-
targetGroupAttributeInput := &elbv2.ModifyTargetGroupAttributesInput{
440-
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
441-
Attributes: []*elbv2.TargetGroupAttribute{
442-
{
443-
Key: aws.String(infrav1.TargetGroupAttributeEnablePreserveClientIP),
444-
Value: aws.String("false"),
445-
},
446-
},
447-
}
448-
if _, err := s.ELBV2Client.ModifyTargetGroupAttributes(targetGroupAttributeInput); err != nil {
449-
return nil, errors.Wrapf(err, "failed to modify target group attribute")
450-
}
451-
}
452-
453-
listenerInput := &elbv2.CreateListenerInput{
454-
DefaultActions: []*elbv2.Action{
455-
{
456-
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
457-
Type: aws.String(elbv2.ActionTypeEnumForward),
458-
},
459-
},
460-
LoadBalancerArn: out.LoadBalancers[0].LoadBalancerArn,
461-
Port: aws.Int64(ln.Port),
462-
Protocol: aws.String(string(ln.Protocol)),
463-
Tags: converters.MapToV2Tags(spec.Tags),
464-
}
465-
// Create ClassicELBListeners
466-
listener, err := s.ELBV2Client.CreateListener(listenerInput)
467-
if err != nil {
468-
return nil, errors.Wrap(err, "failed to create listener")
469-
}
470-
if len(listener.Listeners) == 0 {
471-
return nil, errors.New("no listener was created; the returned list is empty")
472-
}
473-
}
398+
// Target Groups and listeners will be reconciled separately
474399

475400
s.scope.Info("Created network load balancer", "dns-name", *out.LoadBalancers[0].DNSName)
476401

@@ -1604,6 +1529,113 @@ func (s *Service) reconcileV2LBTags(lb *infrav1.LoadBalancer, desiredTags map[st
16041529
return nil
16051530
}
16061531

1532+
func (s *Service) reconcileTargetGroupsAndListeners(spec *infrav1.LoadBalancer, lbSpec *infrav1.AWSLoadBalancerSpec) ([]*elbv2.TargetGroup, error) {
1533+
1534+
describeInput := &elbv2.DescribeTargetGroupsInput{
1535+
LoadBalancerArn: aws.String(spec.ARN),
1536+
}
1537+
targetGroups, err := s.ELBV2Client.DescribeTargetGroups(describeInput)
1538+
if err != nil {
1539+
s.scope.Error(err, "could not describe target groups for load balancer", "arn", spec.ARN)
1540+
return nil, err
1541+
}
1542+
1543+
// Target Groups already exist, no need to do additional work.
1544+
// TODO(nrb): This will need to match the infrav1.Listener type
1545+
if len(targetGroups.TargetGroups) > 0 {
1546+
return targetGroups.TargetGroups, nil
1547+
}
1548+
1549+
groups := make([]*elbv2.TargetGroup, len(spec.ELBListeners))
1550+
1551+
// TODO(Skarlso): Add options to set up SSL.
1552+
// https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3899
1553+
for _, ln := range spec.ELBListeners {
1554+
// create the target group first
1555+
targetGroupInput := &elbv2.CreateTargetGroupInput{
1556+
Name: aws.String(ln.TargetGroup.Name),
1557+
Port: aws.Int64(ln.TargetGroup.Port),
1558+
Protocol: aws.String(ln.TargetGroup.Protocol.String()),
1559+
VpcId: aws.String(ln.TargetGroup.VpcID),
1560+
Tags: converters.MapToV2Tags(spec.Tags),
1561+
HealthCheckIntervalSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckIntervalSec),
1562+
HealthCheckTimeoutSeconds: aws.Int64(infrav1.DefaultAPIServerHealthCheckTimeoutSec),
1563+
HealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerHealthThresholdCount),
1564+
UnhealthyThresholdCount: aws.Int64(infrav1.DefaultAPIServerUnhealthThresholdCount),
1565+
}
1566+
if s.scope.VPC().IsIPv6Enabled() {
1567+
targetGroupInput.IpAddressType = aws.String("ipv6")
1568+
}
1569+
if ln.TargetGroup.HealthCheck != nil {
1570+
targetGroupInput.HealthCheckEnabled = aws.Bool(true)
1571+
targetGroupInput.HealthCheckProtocol = ln.TargetGroup.HealthCheck.Protocol
1572+
targetGroupInput.HealthCheckPort = ln.TargetGroup.HealthCheck.Port
1573+
if ln.TargetGroup.HealthCheck.Path != nil {
1574+
targetGroupInput.HealthCheckPath = ln.TargetGroup.HealthCheck.Path
1575+
}
1576+
if ln.TargetGroup.HealthCheck.IntervalSeconds != nil {
1577+
targetGroupInput.HealthCheckIntervalSeconds = ln.TargetGroup.HealthCheck.IntervalSeconds
1578+
}
1579+
if ln.TargetGroup.HealthCheck.TimeoutSeconds != nil {
1580+
targetGroupInput.HealthCheckTimeoutSeconds = ln.TargetGroup.HealthCheck.TimeoutSeconds
1581+
}
1582+
if ln.TargetGroup.HealthCheck.ThresholdCount != nil {
1583+
targetGroupInput.HealthyThresholdCount = ln.TargetGroup.HealthCheck.ThresholdCount
1584+
}
1585+
if ln.TargetGroup.HealthCheck.UnhealthyThresholdCount != nil {
1586+
targetGroupInput.UnhealthyThresholdCount = ln.TargetGroup.HealthCheck.UnhealthyThresholdCount
1587+
}
1588+
}
1589+
s.scope.Debug("creating target group", "group", targetGroupInput, "listener", ln)
1590+
group, err := s.ELBV2Client.CreateTargetGroup(targetGroupInput)
1591+
if err != nil {
1592+
return nil, errors.Wrapf(err, "failed to create target group for load balancer")
1593+
}
1594+
if len(group.TargetGroups) == 0 {
1595+
return nil, errors.New("no target group was created; the returned list is empty")
1596+
}
1597+
groups = append(groups, group.TargetGroups...)
1598+
1599+
if !lbSpec.PreserveClientIP {
1600+
targetGroupAttributeInput := &elbv2.ModifyTargetGroupAttributesInput{
1601+
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
1602+
Attributes: []*elbv2.TargetGroupAttribute{
1603+
{
1604+
Key: aws.String(infrav1.TargetGroupAttributeEnablePreserveClientIP),
1605+
Value: aws.String("false"),
1606+
},
1607+
},
1608+
}
1609+
if _, err := s.ELBV2Client.ModifyTargetGroupAttributes(targetGroupAttributeInput); err != nil {
1610+
return nil, errors.Wrapf(err, "failed to modify target group attribute")
1611+
}
1612+
}
1613+
1614+
listenerInput := &elbv2.CreateListenerInput{
1615+
DefaultActions: []*elbv2.Action{
1616+
{
1617+
TargetGroupArn: group.TargetGroups[0].TargetGroupArn,
1618+
Type: aws.String(elbv2.ActionTypeEnumForward),
1619+
},
1620+
},
1621+
LoadBalancerArn: aws.String(spec.ARN),
1622+
Port: aws.Int64(ln.Port),
1623+
Protocol: aws.String(string(ln.Protocol)),
1624+
Tags: converters.MapToV2Tags(spec.Tags),
1625+
}
1626+
// Create ClassicELBListeners
1627+
listener, err := s.ELBV2Client.CreateListener(listenerInput)
1628+
if err != nil {
1629+
return nil, errors.Wrap(err, "failed to create listener")
1630+
}
1631+
if len(listener.Listeners) == 0 {
1632+
return nil, errors.New("no listener was created; the returned list is empty")
1633+
}
1634+
}
1635+
1636+
return groups, nil
1637+
}
1638+
16071639
func (s *Service) getHealthCheckTarget() string {
16081640
controlPlaneELB := s.scope.ControlPlaneLoadBalancer()
16091641
protocol := &infrav1.ELBProtocolSSL

0 commit comments

Comments
 (0)