Skip to content

Is this the best way to allow both ALB and TG updates in a CF stack?

0

In an existing CloudFormation stack, if you have a load balancer and a target group attached via a listener, there is no way to be able to update both with changes that require replacements. Amazon says that their Name properties should be left blank, but then you'll get an error when you try to update the ALB. You can set the TG Name to incorporate that ALB's automatic Name, but then you'll get an error when you try to update the TG.

I'll give a lot more details in a wall of text below the template, but in short I've created a sample template which works around this issue. My question is, do people here think this is the best way to deal with the problem?

The updates in this template are done entirely through the parameters; changing the template is not necessary.

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  SetTargetGroupName:
    Description: Configure the Target Group Name to be based on the Load Balancer Name. If false, then the Target Group Name is left unspecified.
    Type: String
    AllowedValues:
      - false
      - true
    Default: false

  LoadBalancerScheme:
    Description: The Scheme of the Load Balancer.
    Type: String
    AllowedValues:
      - internal
      - internet-facing
    Default: internal

  TargetGroupPort:
    Description: The Port of the Target Group.
    Type: Number
    Default: 80
    MinValue: 1
    MaxValue: 65535

Conditions:
  DoSetTargetGroupName: !Equals
    - !Ref SetTargetGroupName
    - true

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/23

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  Subnet0:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select
        - 0
        - !GetAZs

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select
        - 1
        - !GetAZs

  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: !Ref LoadBalancerScheme
      Subnets:
        - !Ref Subnet0
        - !Ref Subnet1

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !If
        - DoSetTargetGroupName
        - !Sub TG-${LoadBalancer.LoadBalancerName}
        - !Ref AWS::NoValue
      Port: !Ref TargetGroupPort
      Protocol: HTTP
      VpcId: !Ref VPC

  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP

Here is a wall of text with a bunch more details and reasoning. You can skip this if you want to:

= == = === = == = ==== = == = === = == =

Let's say you have an existing CloudFormation stack with an application load balancer and a target group, connected by a listener. And let's say you want to modify an property on that ALB or on that TG using a CloudFormation stack update, and let's say that modification requires replacing the ALB or the TG. Then you will have a problem. I've come up with a solution, and I'm wondering if it's the best way to do this.

In Amazon's documentation for AWS::ElasticLoadBalancingV2::LoadBalancer they say that the Name property needs to be left blank, or you need to modify your given name, if you want to perform an update that requires replacement of the resource. The documentation for AWS::ElasticLoadBalancingV2::TargetGroup doesn't say this, but in my testing I see that the same restriction applies.

When you've got a TG connected to an ALB and you leave both Names blank, you still can't replace the ALB. This is because Amazon will create a new ALB first, and then try to attach the existing TG to it (through the Listener) before it deletes anything. CloudFormation doesn't try to detach the TG first, and a TG can't be attached to more than one ALB at a time. The solution for this is to actually set the TG's Name property, but use the ALB's automatically-generated Name (or at least the random string part) as part of the name. This way, when CloudWatch creates the new ALB, this will result in the TG's Name changing, which causes a new TG to also be created. And that new TG can be attached to the new ALB.

But then, what if you want to modify a property of the TG which requires replacement? Now you've got a TG Name that you're setting (instead of leaving it blank so that CloudFormation generates a random Name). So when CloudFormation goes to create the replacement TG before deleting the old TG, it'll error out saying that you can't have two TG's with the same name.

So I've come up with an example template which allows for both kinds of updates, pasted below. It gives the user a parameter which sets the TG Name to be blank or to be based on the ALB name. And I think this example shows how this kind of parameter can be inserted into an existing stack if needed, so that someone can get out of a stuck situation without having to replace the whole stack now. The updates for this workaround are done entirely through the parameters; changing the template is not necessary.

If SetTargetGroupName is set to false, then the Load Balancer Scheme cannot be modified through a CloudFormation stack update. It will error out with the error below. But, if SetTargetGroupName gets set to true (it can be done at the same time as the Scheme modification), then the update will succeed.

AWS::ElasticLoadBalancingV2::Listener - Logical ID: Listener - Resource handler returned message: "The following target groups cannot be associated with more than one load balancer: arn:aws:elasticloadbalancing:us-east-2:692973256363:targetgroup/pgn-te-Targe-3I1DX4LDYYMZ/d6b64fadeabe2c6b (Service: ElasticLoadBalancingV2, Status Code: 400, Request ID: 759a63d7-a850-4066-8fb2-8758eb768a2e) (SDK Attempt Count: 1)" (RequestToken: 012f9faf-6156-ca7f-1ce0-f7a4925a30fd, HandlerErrorCode: ServiceLimitExceeded)

If SetTargetGroupName is set to true, then the Target Group Port cannot be modified through a CloudFormation stack update. It will error out with the error below. But, if SetTargetGroupName gets set to false (it can be done at the same time as the Port modification), then the update will succeed.

AWS::ElasticLoadBalancingV2::TargetGroup - Logical ID: TargetGroup - Resource handler returned message: "Resource of type 'AWS::ElasticLoadBalancingV2::TargetGroup' with identifier 'TG-pgn-te-LoadB-7uQlvsjubujb' already exists." (RequestToken: bca35472-3cbf-dbe9-7766-5ab1a8fa3d07, HandlerErrorCode: AlreadyExists)

The SetTargetGroupName parameter would probably need a pretty explanatory description, since this is an awkward workaround. Something like this:

Defaults to false. If an existing stack needs to be updated to modify a Target Group property, then this parameter should be set to false. If a update needs to modify a Load Balancer, then it should be set to true. Changing this parameter can be done in the same update as the TG or LB modification. If both a TG and an LB need to be modified, then you must first do one in one update, and then the other. If you get the error message "The following target groups cannot be associated with more than one load balancer" or "Resource of type 'AWS::ElasticLoadBalancingV2::TargetGroup' with identifier '***' already exists", then try flipping this parameter and see if it helps.

In my testing, I don't think there's any real downside to this workaround in the way that changes are rolled out or the speed of them or anything. CloudFormation takes care of retaining as much uptime as is possible while replacing the load balancer or the target group. And the ALB and TG should always be given a Name tag, so that the resource Name never needs to be anything that's readable by any human.

1 Answer
1

Your approach to allowing both ALB and TG updates in a CloudFormation stack is a clever workaround for the limitations imposed by AWS. It addresses the issue of updating properties that require replacements for both the Application Load Balancer (ALB) and Target Group (TG) when they are connected via a listener.

The use of a parameter (SetTargetGroupName) to toggle between setting the TG name based on the ALB's name or leaving it unspecified is an effective solution. This allows for flexibility in updates, enabling you to modify either the ALB or the TG as needed.

However, while this approach works, it does have some drawbacks:

  1. Complexity: It adds an extra layer of complexity to your stack management, requiring users to understand when and how to toggle the SetTargetGroupName parameter.

  2. Manual intervention: Users need to manually switch the parameter value depending on whether they want to update the ALB or the TG, which could be prone to human error.

  3. Two-step updates: If both ALB and TG need to be updated, it requires two separate update operations, which might not be ideal in all scenarios.

  4. Potential for confusion: The need for a detailed explanation of the parameter's purpose and usage might be confusing for team members who are not familiar with this workaround.

While your solution is functional, there might be alternative approaches to consider:

  1. Use separate stacks for ALB and TG: This would allow independent updates but requires managing cross-stack references.

  2. Use nested stacks: This could provide more flexibility in resource management while keeping everything within a single main stack.

  3. Implement custom resources: A Lambda-backed custom resource could potentially manage the ALB and TG updates more seamlessly, although this adds significant complexity.

  4. Use AWS CDK: The CDK might offer more programmatic control over the update process, potentially allowing for more elegant solutions.

In conclusion, while your approach is a valid workaround for the current limitations of CloudFormation with ALB and TG updates, it's not without drawbacks. It may be the best solution in some scenarios, particularly for existing stacks that need this flexibility. However, for new deployments, exploring alternative architectures or tools might lead to more maintainable long-term solutions.
Sources
Update your stack template - AWS CloudFormation
Prevent updates to stack resources - AWS CloudFormation
Creating stacks using Direct Change mode - AMS Advanced User Guide

answered a year ago

EXPERT

reviewed a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.