Friday, April 15, 2022

[SOLVED] Trying to use terraform to create multiple EC2 instances with separate route 53 records

Issue

So I have a project where I'm trying to do something simple like create a reusable project that could create the following:

EC2 SG - 1 per workload EC2 instances - could be 1 or more Route 53 records - 1 per EC2 instance created

This project works just fine without any issues with using just 1 instance, but when I increase the count to anything besides 1, I get the following:

Error: Invalid index
│ 
│   on .terraform/modules/ec2_servers_dns_name/main.tf line 18, in resource "aws_route53_record" "this":
│   18:   records = split(",", var.records[count.index])
│     ├────────────────
│     │ count.index is 1
│     │ var.records is list of string with 1 element
│ 
│ The given key does not identify an element in this collection value: the
│ given index is greater than or equal to the length of the collection.

In my core main.tf, the modules for EC2 and Route53 look like below:

# EC2 Instances
module "ec2_servers" {
   ami                         = data.aws_ami.server.id
   associate_public_ip_address = var.ec2_associate_public_ip_address
   disable_api_termination     = var.ec2_disable_api_termination
   ebs_optimized               = var.ec2_ebs_optimized
   instance_count              = var.ec2_servers_instance_count
   instance_dns_names          = var.ec2_servers_dns_name_for_tags
   instance_type               = var.ec2_servers_instance_type
   key_name                    = var.ec2_key_name
   monitoring                  = var.ec2_enhanced_monitoring
   name                        = format("ec2-%s-%s-server",local.tags["application"],local.tags["environment"])
   rbd_encrypted               = var.ec2_rbd_encrypted
   rbd_volume_size             = var.ec2_rbd_volume_size
   rbd_volume_type             = var.ec2_rbd_volume_type
   subnet_id                   = concat(data.terraform_remote_state.current-vpc.outputs.app_private_subnets)
   user_data                   = var.ec2_user_data
   vpc_security_group_ids      = [module.ec2_security_group_servers.this_security_group_id, var.baseline_sg]   

   tags = local.tags
}

# Create DNS entry for EC2 Instances
module "ec2_servers_dns_name" {
    domain          = var.domain_name
    instance_count  = var.ec2_servers_instance_count
    name            = var.ec2_servers_dns_name
    private_zone    = "true"
    records         = module.ec2_servers.private_ip
    ttl             = var.ttl
    type            = var.record_type

    providers = {
      aws = aws.network
    }
}

And the resources (EC2/Route53) in our core module repo are shown below:

EC2

locals {
  is_t_instance_type = replace(var.instance_type, "/^t[23]{1}\\..*$/", "1") == "1" ? "1" : "0"
}

resource "aws_instance" "this" {
  count = var.instance_count

  ami                                  = var.ami
  associate_public_ip_address          = var.associate_public_ip_address

  credit_specification {
    cpu_credits = local.is_t_instance_type ? var.cpu_credits : null
  }

  disable_api_termination              = var.disable_api_termination
  ebs_optimized                        = var.ebs_optimized
  iam_instance_profile                 = var.iam_instance_profile  
  instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior
  instance_type                        = var.instance_type
  ipv6_addresses                       = var.ipv6_addresses
  ipv6_address_count                   = var.ipv6_address_count
  key_name                             = var.key_name

  lifecycle {
    ignore_changes = [private_ip, root_block_device, ebs_block_device, volume_tags, user_data, ami]
  }

  monitoring      = var.monitoring
  placement_group = var.placement_group
  private_ip      = var.private_ip

  root_block_device {
    encrypted   = var.rbd_encrypted
    volume_size = var.rbd_volume_size
    volume_type = var.rbd_volume_type
  }

  secondary_private_ips  = var.secondary_private_ips
  source_dest_check      = var.source_dest_check
  subnet_id              = element(var.subnet_id, count.index)
  tags                   = merge(tomap({"Name"= var.name}), var.tags, var.instance_dns_names[count.index])
  tenancy                = var.tenancy  
  user_data              = var.user_data[count.index]
  volume_tags            = var.volume_tags
  vpc_security_group_ids = var.vpc_security_group_ids
}

Route53

data "aws_route53_zone" "this" {
  name         = var.domain
  private_zone = var.private_zone
}

terraform {
  required_providers {
    aws = {
        source  = "hashicorp/aws"
        version = ">= 2.7.0"
    }
  }
}

resource "aws_route53_record" "this" {
  count   = var.instance_count
  name    = var.name[count.index]
  records = split(",", var.records[count.index])
  type    = var.type
  ttl     = var.ttl
  zone_id = data.aws_route53_zone.this.zone_id
}

It seems like it's possibly with the output for the private IP for EC2, but I'm not sure. Here's the output for the private IP for the EC2 resource

output "private_ip" {
  description = "The private IP address assigned to the instance."
  value      = [aws_instance.this[0].private_ip]
}

And the records variable in the R53 resource is set to a list.

Any thoughts on how to pull the private IP for the EC2 instances (whether it's one or multiple) have the output for each private IP be called dynamically in the R53 module so the R53 record can be created without issue?


Solution

Try

output "private_ip" {
 description = "The private IP address assigned to the instance."
 value      = [aws_instance.this[*].private_ip]
}

The 0 returns only one element

EDIT: Add this tostring()



Answered By - Tolis Gerodimos
Answer Checked By - Pedro (WPSolving Volunteer)