skip to Main Content

I have 3 private and 3 public subnets, and I want to create either 1 or 2 NAT Gateways depending on the environment.

So, for dev and staging there should be 1 NAT Gateway, and for production two.

Since I can’t use for_each with subnets as it is one more than I need, I decided to create the Elastic IPs with count.

resource "aws_eip" "elastic_ip" {
  count = var.environment == "stg" ? 1 : 2
  vpc = true
    
  tags = merge(var.tags, {
    Name        = "eip-${var.name_suffix}-${count.index}"
    Description = "Terraform Managed Elastic IP"
    Project     = var.project
    Environment = var.environment
  })
}

Now I want to create the NAT Gateways depending on the number of Elastic IPs created, like this:

resource "aws_nat_gateway" "nat_gw" {
  for_each      = aws_eip.elastic_ip
  allocation_id = each.value.id
  subnet_id     = CHALLENGE 2
    
  tags = merge(var.tags, {
    Name        = "nat-gw-${var.name_suffix}"
    Description = "Terraform Managed NAT Gateway"
    Project     = var.project
    Environment = var.environment
  })
}

but here I have two challenges.

  1. it complaints about aws_eip.elastic_ip being a tuple. I have tried using toset(), but it didn’t work
  2. I need to dynamically pull the IDs of two out of three subnets.

Is this actually possible without extra locals or variables?

UPDATE

Here the code for subnets. I have two of this; one for private subnets and this one for public.

resource "aws_subnet" "public_subnet" {
  for_each = var.public_subnet
     
  availability_zone = each.value["az"]
  cidr_block        = each.value["cidr"]
  vpc_id            = aws_vpc.vpc.id
    
  tags = merge(var.tags, {
    Name        = "public-subnet-${var.name_suffix}"
    Description = "Terraform Managed Subnet"
    Project     = var.project
    Environment = var.environment
    AZ          = each.value["az"]
  })
}

And I’m passing a variable with the region and cidr blocks:

public_subnet = {
  subnet_a = {
    az = "eu-west-1a"
    cidr = "10.10.0.0/24"
  }
  subnet_b = {
    az = "eu-west-1b"
    cidr = "10.10.1.0/24"
  }
  subnet_c = {
    az = "eu-west-1c"
    cidr = "10.10.2.0/24"
  }
}

2

Answers


  1. You can use count in aws_nat_gateway.nat_gw too and access the aws_eip.elastic_ip resource with an index like this: aws_eip.elastic_ip[count.index].

    This would look something like this:

    resource "aws_eip" "elastic_ip" {
      count = var.environment == "stg" ? 1 : 2
      vpc = true
    
      tags = merge(var.tags, {
        Name        = "eip-${var.name_suffix}-${count.index}"
        Description = "Terraform Managed Elastic IP"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_nat_gateway" "nat_gw" {
      count = var.environment == "stg" ? 1 : 2
      allocation_id = aws_eip.elastic_ip[count.index].id
      subnet_id     = aws_subnet.public_subnet[count.index].id
    
      tags = merge(var.tags, {
        Name        = "nat-gw-${var.name_suffix}"
        Description = "Terraform Managed NAT Gateway"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_subnet" "public_subnet" {
      for_each= var.public_subnet
         
      availability_zone = each.value["az"]
      cidr_block        = each.value["cidr"]
      vpc_id            = aws_vpc.vpc.id
        
      tags = merge(var.tags, {
        Name        = "public-subnet-${var.name_suffix}"
        Description = "Terraform Managed Subnet"
        Project     = var.project
        Environment = var.environment
        AZ          = each.value["az"]
      })
    }
    

    Another option might be to use the splat expression combined with toset. But I think terraform will complain because it cannot determine the values of aws_eip.elastic_ip until after the apply.
    But I still wanted to mention it and it would look like this:

    resource "aws_eip" "elastic_ip" {
      count = var.environment == "stg" ? 1 : 2
      vpc = true
    
      tags = merge(var.tags, {
        Name        = "eip-${var.name_suffix}-${count.index}"
        Description = "Terraform Managed Elastic IP"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_nat_gateway" "nat_gw" {
      for_each = toset(aws_eip.elastic_ip[*].id)
    
      allocation_id = each.value
      subnet_id     = CHALLENGE 2
    
      tags = merge(var.tags, {
        Name        = "nat-gw-${var.name_suffix}"
        Description = "Terraform Managed NAT Gateway"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    Login or Signup to reply.
  2. After making some changes and playing around with the code, this example should work:

    locals {
      non_prod_nat_gateway  = { for k, v in aws_subnet.public_subnet : k => v.availability_zone if k != "subnet_a" && k != "subnet_b" }
      prod_nat_gateways = { for k, v in aws_subnet.public_subnet : k => v.availability_zone if k != "subnet_c" }
    }
    
    resource "aws_eip" "elastic_ip" {
      for_each = var.environment != "prod" ? local.non_prod_nat_gateway : local.prod_nat_gateways
    
      tags = merge(var.tags, {
        Name        = "eip-${var.name_suffix}-${each.key}"
        Description = "Terraform Managed Elastic IP"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_nat_gateway" "nat_gw" {
      for_each      = aws_eip.elastic_ip
      allocation_id = each.value.id
      subnet_id     = aws_subnet.private_subnet[each.key].id
    
      tags = merge(var.tags, {
        Name        = "nat-gw-${var.name_suffix}"
        Description = "Terraform Managed NAT Gateway"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_subnet" "public_subnet" {
      for_each = var.public_subnet
    
      availability_zone = each.value["az"]
      cidr_block        = each.value["cidr"]
      vpc_id            = aws_vpc.vpc.id
    
      tags = merge(var.tags, {
        Name        = "public-subnet-${var.name_suffix}"
        Description = "Terraform Managed Subnet"
        Project     = var.project
        Environment = var.environment
        AZ          = each.value["az"]
      })
    }
    

    It does not provide much of the dynamic functionality you are probably aiming for, but the rest should work.

    A bit more dynamic approach:

    locals {
      non_prod_gateway  = { for k, v in aws_subnet.public_subnet : k => v.availability_zone if k != data.aws_availability_zones.all.names[0] && k != data.aws_availability_zones.all.names[1] }
      prod_nat_gateways = { for k, v in aws_subnet.public_subnet : k => v.availability_zone if k != data.aws_availability_zones.all.names[0] }
    }
    
    data "aws_availability_zones" "all" {
      filter {
        name   = "opt-in-status"
        values = ["opt-in-not-required"]
      }
    }
    
    resource "aws_eip" "elastic_ip" {
      for_each = var.environment != "prod" ? local.non_prod_gateway : local.prod_nat_gateways
    
      tags = merge(var.tags, {
        Name        = "eip-${var.name_suffix}-${each.key}"
        Description = "Terraform Managed Elastic IP"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_nat_gateway" "nat_gw" {
      for_each      = aws_eip.elastic_ip
      allocation_id = each.value.id
      subnet_id     = aws_subnet.public_subnet[each.key].id
    
      tags = merge(var.tags, {
        Name        = "nat-gw-${var.name_suffix}"
        Description = "Terraform Managed NAT Gateway"
        Project     = var.project
        Environment = var.environment
      })
    }
    
    resource "aws_subnet" "public_subnet" {
      for_each = var.public_subnet
    
      availability_zone = each.key
      cidr_block        = each.value
      vpc_id            = aws_vpc.vpc.id
    
      tags = merge(var.tags, {
        Name        = "public-subnet-${var.name_suffix}"
        Description = "Terraform Managed Subnet"
        Project     = var.project
        Environment = var.environment
        AZ          = each.key
      })
    }
    

    where the public_subnet would have to change to have a value:

    public_subnet = {
      "eu-west-1a" = "10.10.0.0/24"
      "eu-west-1b" = "10.10.1.0/24"
      "eu-west-1c" = "10.10.2.0/24"
    }
    

    NOTE: the NAT Gateway should probably be in the public subnet(s), not private ones.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search