Attach multiple private subnet to route table for each terraform - amazon-web-services

I have public and private subnets established in a VPC created with for each. I am now trying to create route tables for the subnets and nat gateways specifically for access for private instances. My subnets, route tables, and public subnet associations are working properly. I am having trouble getting my private subnets to attach to the route table connecting it to the NAT gateway. I believe my logic correct. My NAT gateways are sitting in my public subnets. The only issue is private subnets being attached to the route table that connects to the NAT gateway. Below is my code, any advice is appreciated.
resource "aws_route_table" "public" {
for_each = var.pub_subnet
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = var.rt_tags
}
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
route_table_id = aws_route_table.public[each.key].id
subnet_id = each.value.id
}
resource "aws_route_table_association" "nat" {
for_each = aws_subnet.private
route_table_id = aws_route_table.nat[each.key].id
subnet_id = each.value.id
}
resource "aws_route_table" "nat" {
for_each = var.pub_subnet
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.main[each.key].id
}
tags = {
Name = var.rt_tags_private
}
}
resource "aws_subnet" "public" {
for_each = var.pub_subnet
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
map_public_ip_on_launch = true
tags = {
Name = each.key
}
}
resource "aws_subnet" "private" {
for_each = var.priv_subnet
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
map_public_ip_on_launch = false
tags = {
Name = each.key
}
}
Variables
variable "pub_subnet" {
type = map(object({
cidr_block = string
availability_zone = string
}))
default = {
"PubSub1" = {
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-1a"
}
}
}
variable "priv_subnet" {
type = map(object({
cidr_block = string
availability_zone = string
}))
default = {
"PrivSub1" = {
cidr_block = "10.0.2.0/24"
availability_zone = "us-west-1c"
}
}
}
Error
Error: Invalid index
on vpc.tf line 61, in resource "aws_route_table_association" "nat":
61: route_table_id = aws_route_table.nat[each.key].id
|----------------
| aws_route_table.nat is object with 1 attribute "PubSub1"
| each.key is "PrivSub1"
The given key does not identify an element in this collection value.
NAT Gateway
resource "aws_nat_gateway" "main" {
for_each = aws_subnet.public
subnet_id = each.value.id
allocation_id = aws_eip.main[each.key].id
}
EIP
resource "aws_eip" "main" {
for_each = aws_subnet.public
vpc = true
lifecycle {
create_before_destroy = true
}
}

You are defining your route table for nat using var.pub_subnet which has the form of:
"PubSub1" = {
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-1a"
}
Thus to refer to aws_route_table you have to use PubSub1 key.
However, in your aws_route_table_association you are iterating over aws_subnet.private which has key of PrivSub1.
update
The issue can be overcome by creating a local mapping for private=>public subnets names, e.g.:
locals {
private_public_mapping = zipmap(keys(var.priv_subnet), keys(var.pub_subnet))
}
resource "aws_route_table_association" "nat" {
for_each = aws_subnet.private
route_table_id = aws_route_table.nat[local.private_public_mapping[each.key]].id
subnet_id = each.value.id
}

Related

Terraform: Why AWS NAT Gateway conflicts with Egress Only Internet Gateway

I have both IPv4 and IPv6, I'm trying to manage a Routing for private subnet.
Once NAT Gateway is attached to Route Table, it does not allow me to attach Egress Gateway to the same route table, and giving me an error:
An interface that is part of a NAT gateway cannot be the next hop for an IPv6 destination CIDR block or IPv6 prefix list
However if I'm attaching manually thought AWS Console, there is no problem
Maybe I'm missing some info? I know that NAT only for IPv4 and Egress only for IPv6, can someone guide me on this? Why if NAT not compatible with Egress Only Gateway, it allows me to attach via aws console, but not with terraform?
Here is my simple terraform
resource "aws_eip" "neip" {
count = length(var.private_subnet)
vpc = true
}
resource "aws_nat_gateway" "nat" {
count = length(var.private_subnet)
subnet_id = element(var.public_subnet, count.index)
allocation_id = element(aws_eip.neip.*.id, count.index)
}
resource "aws_egress_only_internet_gateway" "egw" {
count = length(var.zones) > 0 ? 1 : 0
vpc_id = var.vpc_id
}
resource "aws_route_table" "route" {
count = length(var.private_subnet)
vpc_id = var.vpc_id
}
resource "aws_route" "ipv4" {
count = length(aws_route_table.route)
depends_on = [ aws_route_table.route ]
route_table_id = aws_route_table.route[count.index].id
nat_gateway_id = element(aws_nat_gateway.nat.*.id, count.index)
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route" "ipv6" {
count = length(aws_route_table.route)
depends_on = [ aws_route_table.route ]
route_table_id = aws_route_table.route[count.index].id
egress_only_gateway_id = element(aws_egress_only_internet_gateway.egw.*.id, count.index)
destination_ipv6_cidr_block = "::/0"
}
resource "aws_route_table_association" "route" {
count = length(aws_route_table.route)
subnet_id = var.private_subnet[count.index]
route_table_id = aws_route_table.route[count.index].id
}
No issue with terraform script
I tried to reproduce your issue, but for me it works as expected. Maybe you still have some "typos" in your code presented here, thus its difficult to see why it woudn't work for you.
Anyway, here is the code I used in order to mimic your setup, though large chunks I had to create myself, as they are not shown in your code (e.g. VPC setup all missing, internet gateway, public subnets).
The code below works and I couldn't replicate your issue. Route tables work as expected:
data "aws_availability_zones" "available" {}
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
assign_generated_ipv6_cidr_block = true
tags = {
Name = "testvpc"
}
}
variable "private_cidrs" {
default = ["10.0.2.0/24", "10.0.3.0/24"]
}
variable "public_cidrs" {
default = ["10.0.0.0/24", "10.0.1.0/24"]
}
resource "aws_subnet" "public_subnet" {
count = length(var.public_cidrs)
cidr_block = var.public_cidrs[count.index]
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "public${count.index}"
}
}
resource "aws_subnet" "private_subnet" {
count = length(var.private_cidrs)
cidr_block = var.private_cidrs[count.index]
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private${count.index}"
}
}
resource "aws_eip" "neip" {
count = length(var.private_cidrs)
vpc = true
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "main"
}
}
resource "aws_nat_gateway" "nat" {
count = length(var.private_cidrs)
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
allocation_id = element(aws_eip.neip.*.id, count.index)
depends_on = [aws_internet_gateway.igw]
}
resource "aws_egress_only_internet_gateway" "egw" {
#count = length(var.private_cidrs)
vpc_id = aws_vpc.vpc.id
}
# routes for public subnets
resource "aws_route_table" "public_route" {
count = length(var.public_cidrs)
vpc_id = aws_vpc.vpc.id
}
resource "aws_route" "public_ipv4" {
count = length(aws_route_table.public_route)
route_table_id = aws_route_table.public_route[count.index].id
gateway_id = aws_internet_gateway.igw.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route" "ipv6_public" {
count = length(aws_route_table.public_route)
route_table_id = aws_route_table.public_route[count.index].id
egress_only_gateway_id = aws_egress_only_internet_gateway.egw.id
destination_ipv6_cidr_block = "::/0"
}
resource "aws_route_table_association" "public_route" {
count = length(aws_route_table.public_route)
subnet_id = aws_subnet.public_subnet[count.index].id
route_table_id = aws_route_table.public_route[count.index].id
}
# routes for private subnets
resource "aws_route_table" "route" {
count = length(var.private_cidrs)
vpc_id = aws_vpc.vpc.id
}
resource "aws_route" "ipv4" {
count = length(aws_route_table.route)
route_table_id = aws_route_table.route[count.index].id
nat_gateway_id = aws_nat_gateway.nat[count.index].id
#nat_gateway_id = aws_nat_gateway.nat.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route" "ipv6" {
count = length(aws_route_table.route)
route_table_id = aws_route_table.route[count.index].id
egress_only_gateway_id = aws_egress_only_internet_gateway.egw.id
destination_ipv6_cidr_block = "::/0"
}
resource "aws_route_table_association" "route" {
count = length(aws_route_table.route)
subnet_id = aws_subnet.private_subnet[count.index].id
route_table_id = aws_route_table.route[count.index].id
}

Terraform AWS - route table association - add multiple subnet

I have created 4 subnets in my vpc, 2 of them public and 2 of them private.
I need to associate 2 public subnets to a one route table and 2 private subnets to another route table.
Looking at the docs, aws_route_table_association seems like accepts only one subnet_id.
How do I add multiple subnets as show in this pic?
Associate route table to subnets
resource "aws_route_table_association" "public-test" {
subnet_id = -> I need to add 2 public subnets here
route_table_id = aws_route_table.public-test.id
}
resource "aws_route_table_association" "private-test" {
subnet_id = -> I need to add 2 private subnets here
route_table_id = aws_route_table.private-test.id
}
Here are the subnets and routes:
Create Subnet
resource "aws_subnet" "public-test-a" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.0/28"
map_public_ip_on_launch = true
availability_zone = var.AZ[0]
tags = {
Name = "public-test-a"
}
}
resource "aws_subnet" "public-test-b" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.16/28"
map_public_ip_on_launch = true
availability_zone = var.AZ[1]
tags = {
Name = "public-test-b"
}
}
resource "aws_subnet" "private-test-a" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.32/28"
availability_zone = var.AZ[0]
tags = {
Name = "private-test-a"
}
}
resource "aws_subnet" "private-test-b" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.48/28"
availability_zone = var.AZ[1]
tags = {
Name = "private-test-b"
}
}
Create route table
resource "aws_route_table" "public-test" {
vpc_id = aws_vpc.vpc-test-02.id
route {
cidr_block = "10.0.0.0/26"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id =aws_internet_gateway.myIG-test-02.id
}
tags = {
Name = "public-test"
}
}
resource "aws_route_table" "private-test" {
vpc_id = aws_vpc.vpc-test-02.id
route {
cidr_block = "10.0.0.0/26"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.myNat-test-02.id
}
}
You can simple declare two route table association resources.
resource "aws_subnet" "public_test_a" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.0/28"
map_public_ip_on_launch = true
availability_zone = var.AZ[0]
tags = {
Name = "public-test-a"
}
}
resource "aws_subnet" "public-test-b" {
vpc_id = aws_vpc.vpc-test-02.id
cidr_block = "10.0.0.16/28"
map_public_ip_on_launch = true
availability_zone = var.AZ[1]
tags = {
Name = "public-test-b"
}
}
resource "aws_route_table_association" "public-test-a" {
subnet_id = aws_subnet.public-test-a.id # first subnet
route_table_id = aws_route_table.public-test.id
}
resource "aws_route_table_association" "public-test-b" {
subnet_id = aws_subnet.public-test-b.id # second subnet
route_table_id = aws_route_table.public-test.id
}
resource "aws_route_table" "public-test" {
vpc_id = aws_vpc.vpc-test-02.id
route {
cidr_block = "10.0.0.0/26"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id =aws_internet_gateway.myIG-test-02.id
}
tags = {
Name = "public-test"
}
}
Also it is considered good practice to follow naming conventions. Quoting the docs
Use _ (underscore) instead of - (dash) in all: resource names, data source names, variable names, outputs.
Beware that actual cloud resources have many hidden restrictions in their naming conventions. Some cannot contain dashes, some must be camel cased. These conventions refer to Terraform names themselves.
Only use lowercase letters and numbers.

assign multiple subnets to route table aws

I think multiple people have asked the same question but my condition is different. I am taking input from the user for the vpc region, cidr value even the public subnet segment too. I have to attach all my public subnet to the default route table and private subnets to the diff route table . can you help me in how to attach them .
provider "aws" {
region = var.region
}
resource "aws_vpc" "app_vpc" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = var.vpc_name
}
}
# create igw
resource "aws_internet_gateway" "app_igw" {
vpc_id = aws_vpc.app_vpc.id
}
data "aws_availability_zones" "available" {
state = "available"
}
#provision public subnet
resource "aws_subnet" "public_subnet_01" {
vpc_id = aws_vpc.app_vpc.id
cidr_block = var.public_subnet_01
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "public_subnet_01"
}
depends_on = [aws_vpc_dhcp_options_association.dns_resolver]
}
resource "aws_subnet" "public_subnet_02" {
vpc_id = aws_vpc.app_vpc.id
cidr_block = var.public_subnet_02
availability_zone = data.aws_availability_zones.available.names[1]
tags = {
Name = "public_subnet_02"
}
depends_on = [aws_vpc_dhcp_options_association.dns_resolver]
}
resource "aws_subnet" "public_subnet_03" {
vpc_id = aws_vpc.app_vpc.id
cidr_block = var.public_subnet_03
availability_zone = data.aws_availability_zones.available.names[2]
tags = {
Name = "public_subnet_03"
}
depends_on = [aws_vpc_dhcp_options_association.dns_resolver]
}
#default route table
resource "aws_default_route_table" "default" {
default_route_table_id = aws_vpc.app_vpc.default_route_table_id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.app_igw.id
}
}
resource "aws_route_table_association" "default_association_01" {
subnet_id = [aws_subnet.public_subnet_01.id, aws_subnet.public_subnet_02.id, aws_subnet.public_subnet_03.id]
route_table_id = aws_vpc.app_vpc.default_route_table_id
}
I am getting error in adding multiple subnet so can u please help here :)
aws_route_table_association takes only one subnet as an input, not a list of subnets.
If you want to create the associations using your list, you can use for_each:
resource "aws_route_table_association" "default_association_01" {
for_each = toset([aws_subnet.public_subnet_01.id, aws_subnet.public_subnet_02.id, aws_subnet.public_subnet_03.id])
subnet_id = each.key
route_table_id = aws_vpc.app_vpc.default_route_table_id
}
The above assumes that everything else is correct. There could be still some errors in your code which aren't apparent yet.

Assigning multiple public subnet to route table using for each

I have multiple subnets created with for each that I am trying to get associated with a route table. Below is the code I have, error, and what I have tried.
locals {
az_names = data.aws_availability_zones.azs.names
pub_sub_ids = aws_subnet.public.*.id
}
resource "aws_route_table_association" "main" {
for_each = var.public_sub_cidr
subnet_id = local.pub_sub_ids[each.key]
route_table_id = aws_route_table.main.id
}
resource "aws_subnet" "public" {
for_each = { for index, az_name in local.az_names : index => az_name }
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.key + 1)
availability_zone = local.az_names[each.key]
map_public_ip_on_launch = true
tags = {
Name = "${var.vpc_tags}-PubSubnet"
}
}
Error: Unsupported attribute
on vpc.tf line 3, in locals:
3: pub_sub_ids = aws_subnet.public.*.id
This object does not have an attribute named "id".
I believe this should be working. Any advice on this error and getting these public subnets to attach to the route table would be helpful.
UPDATE
I made some changes and removed the local variable 'pub_sub_ids' and also changed 'aws_route_table_association" "main" to
resource "aws_route_table_association" "main" {
for_each = var.public_sub_cidr
subnet_id = each.key
route_table_id = aws_route_table.main.id
}
Now I am getting an error
Error: Error creating route table association: InvalidSubnetID.NotFound: The `subnet ID '' does not exist`
It says the subnet does not exist even though I see it in the Console. Any advice would be appreciated in associating these public subnets to the route table.
subnet_id in aws_route_table_association should be subnet id, not subnet CIDR.
Since aws_route_table is not given, I made my own to verify the setup. Thus you could do the following:
resource "aws_route_table_association" "main" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.main.id
}
And below is the full code I used for verification:
provider "aws" {
# your data
}
data "aws_availability_zones" "azs" {
state = "available"
}
locals {
az_names = data.aws_availability_zones.azs.names
}
variable "vpc_cidr" {
default = "10.0.0.0/16"
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
resource "aws_subnet" "public" {
for_each = {for index, az_name in local.az_names: index => az_name}
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.key + 1)
availability_zone = local.az_names[each.key]
map_public_ip_on_launch = true
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main"
}
}
resource "aws_route_table" "main" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
}
resource "aws_route_table_association" "main" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.main.id
}

Create AWS RDS instance in non default VPC using terraform

I am using Terraform v0.10.2. I have created VPC in modules/vpc/main.tf and modules/acl/main.tf. I am accessing it using it's output.
I can successfully create ec2 instance in public subnet in above vpc like so:
subnet_id = "${element(module.vpc.public_subnet_ids, count.index)}"
I want to add the RDS instance to private subnet. I tried what the terraform doc said:
vpc_security_group_ids = [
"${aws_security_group.db_access_sg.id}"
]
db_subnet_group_name = "${module.vpc.aws_db_subnet_group_database}"
But, it is adding to the default VPC. If i put the subnet outside the module and access the resource, it gives the variable not found error.
I have referred many GitHub examples, but without success. Am i missing something ?
And this is one of the link i referred: https://github.com/hashicorp/terraform/issues/13739
Contents of modules/vpc/main.tf
resource "aws_vpc" "mod" {
cidr_block = "${var.cidr}"
tags {
Name = "${var.name}"
}
}
resource "aws_internet_gateway" "mod" {
vpc_id = "${aws_vpc.mod.id}"
}
resource "aws_route_table" "public" {
vpc_id = "${aws_vpc.mod.id}"
propagating_vgws = ["${compact(split(",", var.public_propagating_vgws))}"]
tags {
Name = "${var.name}-public"
}
}
resource "aws_route" "public_internet_gateway" {
route_table_id = "${aws_route_table.public.id}"
destination_cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.mod.id}"
}
resource "aws_route_table" "private" {
vpc_id = "${aws_vpc.mod.id}"
propagating_vgws = ["${compact(split(",", var.private_propagating_vgws))}"]
tags {
Name = "${var.name}-private"
}
}
resource "aws_subnet" "private" {
vpc_id = "${aws_vpc.mod.id}"
cidr_block = "${element(split(",", var.private_subnets), count.index)}"
availability_zone = "${element(split(",", var.azs), count.index)}"
count = "${length(compact(split(",", var.private_subnets)))}"
tags {
Name = "${var.name}-private"
}
}
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.mod.id}"
cidr_block = "${element(split(",", var.public_subnets), count.index)}"
availability_zone = "${element(split(",", var.azs), count.index)}"
count = "${length(compact(split(",", var.public_subnets)))}"
tags {
Name = "${var.name}-public"
}
map_public_ip_on_launch = true
}
resource "aws_db_subnet_group" "database" {
name = "${var.name}-rds-subnet-group-${count.index}"
description = "Database subnet groups for ${var.name}"
subnet_ids = ["${aws_subnet.private.*.id}"]
#tags = "${merge(var.tags, map("Name", format("%s-database-subnet-group", var.name)))}"
count = "${length(compact(split(",", var.private_subnets)))}"
}
resource "aws_route_table_association" "private" {
count = "${length(compact(split(",", var.private_subnets)))}"
subnet_id = "${element(aws_subnet.private.*.id, count.index)}"
route_table_id = "${aws_route_table.private.id}"
}
resource "aws_route_table_association" "public" {
count = "${length(compact(split(",", var.public_subnets)))}"
subnet_id = "${element(aws_subnet.public.*.id, count.index)}"
route_table_id = "${aws_route_table.public.id}"
}
Contents of modules/vpc/outputs.tf
output "vpc_id" {
value = "${aws_vpc.mod.id}"
}
output "public_subnet_ids" {
value = ["${aws_subnet.public.*.id}"]
}
output "private_subnet_ids" {
value = ["${aws_subnet.private.*.id}"]
}
output "aws_db_subnet_group_database" {
value = "${aws_db_subnet_group.database.name}"
}
Contents of modules/acl/main.tf
resource "aws_network_acl" "private_app_subnets" {
vpc_id = "${var.vpc_id}"
subnet_ids = ["${var.private_subnet_ids}"]
}
The issue was, i had enabled the "Publicly Accessible" to true, while trying to add the RDS instance to private subnet. I had to remove the count from aws_db_subnet_group like ydaetskcoR told me to, of course.