AWS Attach Dynamic Instance Count to Target Group

I have a set of target groups that were created through a for_each condition iterating through a locals var in the situation below:

locals {
    target-ports-param = {
        "port_name" = {port = "80", health_path = "/status"}
        ...
    }
}

resource "aws_lb_target_group" "my-target-group" {
  for_each = local.target-ports-param
  name        = each.key
  target_type = "instance"
  port        = each.value.port
  protocol    = "TCP"
  vpc_id      = aws_vpc.my-vpc.id

  #outlines path, protocol, and port of healthcheck
  health_check {
    protocol = "HTTP"
    path     = each.value.health_path
    port     = 80
    matcher  = "200"
  }
}

I want to attach my instances to the target groups that were created using the above code block. The issue is that my instance count is dynamic and that the target_id parameter only accepts a string.

How would I attach all my instances to the target group?

#attach target group to instances
resource "aws_lb_target_group_attachment" "my-target-group-attach" {
  for_each         = local.target-ports.param
  target_group_arn = aws_lb_target_group.my-target-group[each.key].arn
  target_id        = aws_instance.my-servers.id
  port             = each.value.port
}

Hi @Belal.Khan,

To achieve this result you’ll need to construct a collection which contains one element for each unique pair of instance ID and port number.

You haven’t included the aws_instance.my-servers configuration in your example so I can’t show a specific example. If you can show how you’ve configured the servers in addition to how you’ve configured the target group and the ports then I may be able to show you an example configuration which will achieve the result you want.

Hi @apparentlymart ,

I was able to figure out how to do this, but I ran into separate issue.

To answer your question, my instances are setup using a count function which is set by the user at runtime.

To resolve “dynamic attachment issue”, I had to first extract the target group arns using a for loop.

I did this with the following code:

locals {
   target-group-arns = [for i in aws_lb_target_group.my-target-group : i.arn]
}

From there I needed to use the Cartesian product between the target group arns and instance ids to setup the “loop index”.

The Cartesian product provides all the combinations of target group arns and instance ids that are created when merging the two datasets.

This was accomplished using the setproduct() function.

resource "aws_lb_target_group_attachment" "my-target-group-attach" {

   for_each = {
     for pair in setproduct(tolist(local.target-group-arns), tolist(aws_instance.my-servers*.id)) : "${pair[0]} ${pair[1]}" => {
       target_group_arn = pair[0]
       target_id        = pair[1]
     }
   }

   target_group_arn = each.value.target_group_arn 
   target_id        = each.value.target_id
}

The issue I have now is that I’m getting the following error when running plan:

The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot     
│ predict how many instances will be created. To work around this, use the -target argument to first apply only the  
│ resources that the for_each depends on.

I thought I was getting this error as I was creating everything in the root module and that the target group arns could not be determined until an apply was completed. I split up the creation of the target groups and listeners into a separate module, but ended up receiving the same error.

Assistance on this would be much appreciated.

Thanks,

Belal

Hi @Belal.Khan,

This new error occurs because you are trying to use the dynamically-chosen target group ARNs and instance IDs as the unique identifiers for your instances of aws_lb_target_group_attachment.my-target-group-attach, and so Terraform can’t determine what the instance keys ought to be until the apply step.

To avoid this you’ll need to select different instance keys that only include information that’s specified statically in the configuration, without relying on attributes of resources that are being managed by other parts of this configuration.

Because you are using for_each for your target group, the target groups must already have a suitable instance key that you can use for that part of the attachment key. If you’re using count for the instances then they won’t have any special name other than their instance indices, so I think the best you could do here is generate instance keys by concatenating together the target group keys with the instance indices, making instance keys like "port_name:0", "port_name:1", etc.

Melding together for_each instances and count instances like this gets a bit awkward, but it is possible to do. Something like this:

resource "aws_lb_target_group_attachment" "my-target-group-attach" {
  for_each = {
    for pair in setproduct(keys(aws_lb_target_group.my-target-group), 
  range(length(aws_instance.my-servers))) :
    "${pair[0]}:${pair[1]}" => {
      target_group_arn = aws_lb_target_group.my-target-group[pair[0]].arn
      target_id        = aws_instance.my-servers[pair[1]].id
    }
  }

   target_group_arn = each.value.target_group_arn 
   target_id        = each.value.target_id
}

The range(length(aws_instance.my-servers)) part of this is the main trick here: it produces a list of integers matching all of the indices of aws_instance.my-servers, and so it’s effectively the same as the keys function but for a list, giving all of the values that would be valid to put in the index brackets [...] after a reference to that list.

This means that inside this for expression, pair[0] is always a key from aws_lb_target_group.my-target-group and pair[1] is always an index from aws_instance.my-servers, and so it’s valid to look up those elements from those collections to populate the target_group_arn and target_id attributes with the dynamic data from the upstream resource instances.

1 Like

Thanks @apparentlymart this worked. Apologies on the late reply, I was away and didn’t get a chance to work on this until now.

Really appreciate the assistance.

Hi @apparentlymart I am running both a ASG and LB modules at a time. ASG creating Instance. I am unable to provide the newly created instance id to the load balancer target group instance id dynamically .

my tfvars looks like this
name = “xxx”
load_balancer_type = "cc
lvpc_id = "vdd
subnets = [“ccx”, “sdd”]
security_groups = [“sg-0afed”]
internal = true
target_groups = [
{
name = “ded”
backend_protocol = “HTTP”
backend_port = 80
target_type = “instance”
targets = {
my_target = {
target_id = "i-xxxxx
port = 80
},

}

},
how to add target_id dynamically can you please help me with this