I am trying to create a subnet within the availability zones within a region using Terraform. I have the below code and am having some trouble getting my subnet to read into the availability zone for the region. Below is also my error.
data "aws_availability_zones" "azs" {
state = "available"
}
locals {
az_names = data.aws_availability_zones.azs.names
}
resource "aws_vpc" "main" {
for_each = var.environment
cidr_block = var.vpc_cidr
tags = {
Name = var.vpc_tags
}
}
resource "aws_subnet" "public" {
for_each = var.public_sub_cidr
vpc_id = aws_vpc.main[each.key].id
cidr_block = cidrsubnet(var.vpc_cidr, 0, each.value.public_subnet)
availability_zone = local.az_names[each.key]
map_public_ip_on_launch = true
tags = {
Name = "${var.vpc_tags}-PubSubnet"
}
}
Errors
Error: Unsupported attribute
on vpc.tf line 17, in resource "aws_subnet" "public":
17: cidr_block = cidrsubnet(var.vpc_cidr, 0, each.value.public_subnet)
|----------------
| each.value is ""
This value does not have any attributes.
Error: Invalid index
on vpc.tf line 18, in resource "aws_subnet" "public":
18: availability_zone = local.az_names[each.key]
|----------------
| each.key is ""
| local.az_names is list of string with 3 elements
The given key does not identify an element in this collection value: a number
is required.
Any advice on reading from availability zones and also assigning these public subnets would be appreciated.
UPDATE
I made some progress, updated my code to use for each with the "length" of my data resource. See code below and error:
resource "aws_subnet" "public" {
for_each = length(local.az_names)
vpc_id = aws_vpc.tableau[each.key].id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.value)
availability_zone = local.az_names[each.key]
map_public_ip_on_launch = true
tags = {
Name = "${var.vpc_tags}-PubSubnet"
}
}
Error: Invalid for_each argument
on vpc.tf line 15, in resource "aws_subnet" "public":
15: for_each = length(local.az_names)
The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type
number.
make: *** [apply] Error 1
Not sure what are you trying to achieve, but this is incorrect:
for_each = length(local.az_names)
it should be:
for_each = toset(local.az_names)
Since local.az_names is set now, you can only use each.key (each.value will be same as each.key). For example:
availability_zone = each.key
The following code creates a subnet in each AZ:
provider "aws" {
# your details
}
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 idx, az_name in local.az_names: idx => az_name}
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, each.key)
availability_zone = local.az_names[each.key]
map_public_ip_on_launch = true
}
Related
Subnets are created from the below code:
resource "aws_subnet" "Private" {
for_each = var.subnet_cidrs_private
vpc_id = aws_vpc.vpc.id
cidr_block = each.value
availability_zone = each.key
tags = {
Name = format("%s-%s-%s", var.environment, "Pri-Subnet", substr(each.key, -2, -1))
}
}
Trying to create 1 instance with count parameter.
resource "aws_instance" "visualapp" {
instance_type = "t2.micro"
ami = var.visualapp-ami
key_name = var.key_pair
vpc_security_group_ids = [aws_security_group.visualapp.id]
subnet_id = element(aws_subnet.Private[*].id, count.index)
count = 1
tags = {
Name = format("%s-%s", var.environment, "mage-varnish-${count.index + 1}")
}
}
Getting the below error:
subnet_id = element(aws_subnet.Private[*].id, count.index)
This object does not have an attribute named "id".
Variables:
variable "subnet_cidrs_private" {
default = {
eu-west-1a = "172.26.3.0/24",
eu-west-1b = "172.26.4.0/24"
}
}
Can anyone help me?.
element would be used if you had used count. Since you've used for_each you need to use values first:
subnet_id = element(values(aws_subnet.Private)[*].id, count.index)
I just started with Terraform infrastructure. Trying to create a vpc module that will contain code for vpc, subnets, internet gateway, rout table. Also creating a separate tf file for rds , which will refer to the vpc module and utilize the private subnets declared in vpc module.
Created a vpc module that has vpc.tf with following
provider "aws" {
region = var.region
}
terraform {
backend "s3" {}
}
resource "aws_vpc" "production-vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
tags = {
Name = "Dev-VPC"
}
}
// Private Subnets
resource "aws_subnet" "private-subnet-1" {
cidr_block = var.private_subnet_1_cidr
vpc_id = aws_vpc.production-vpc.id
availability_zone = "us-east-1a"
tags = {
Name = "Private-Subnet-1"
}
}
resource "aws_subnet" "private-subnet-2" {
cidr_block = var.private_subnet_2_cidr
vpc_id = aws_vpc.production-vpc.id
availability_zone = "us-east-1b"
tags = {
Name = "Private-Subnet-2"
}
}
The output.tf has following
output "private-subnet1-id" {
description = "Private Subnet1 Id"
value = aws_subnet.private-subnet-1.*.id
}
output "private-subnet2-id" {
description = "Private Subnet2 Id"
value = aws_subnet.private-subnet-2.*.id
}
The file is saved in folder \module\vpc folder
Created rds.tf as follows in folder \rds
provider "aws" {
region = var.region
}
terraform {
backend "s3" {}
}
module "vpc" {
source = "../module/vpc"
}
resource "aws_db_subnet_group" "subnetgrp" {
name = "dbsubnetgrp"
subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
}
When I run terraform plan , I get following error
Error: Unsupported attribute
on rds.tf line 16, in resource "aws_db_subnet_group" "subnetgrp":
16: subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
|----------------
| module.vpc.private-subnet1-id is tuple with 1 element
This value does not have any attributes.
Error: Unsupported attribute
on rds.tf line 16, in resource "aws_db_subnet_group" "subnetgrp":
16: subnet_ids = [module.vpc.private-subnet1-id.id, module.vpc.private-subnet2-id.id]
|----------------
| module.vpc.private-subnet2-id is tuple with 1 element
This value does not have any attributes.
You don't need the splat expression in the output.tf. Try the following,
output "private-subnet1-id" {
description = "Private Subnet1 Id"
value = aws_subnet.private-subnet-1.id
}
output "private-subnet2-id" {
description = "Private Subnet2 Id"
value = aws_subnet.private-subnet-2.id
}
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
}
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
}
I'm trying to create multiple subnets from one resource block and I get the following error
Error: aws_subnet.private: cidr_block must be a single value, not a list
main.tf
resource "aws_subnet" "private" {
vpc_id = "${aws_vpc.vpcname.id}"
cidr_block = "${var.private_subnet}"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
tags {
Name = "${var.private}"
Environment = "${terraform.workspace}"
}
}
variable.tf
variable "private_subnet" {
type = "list"
default = []
}
dev.tfvars
private_subnet = ["10.0.2.0/24", "10.0.3.0/24"]
You have to create multiple aws_subnet resources by utilitizing the count argument to create one resource for each entry in your var.private_subnet list:
resource "aws_subnet" "private" {
count = "${length(var.private_subnet)}"
vpc_id = "${aws_vpc.vpcname.id}"
cidr_block = "${var.private_subnet[count.index]}"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
}
This expands the single aws_subnet resource into two, each with slightly different values based on the enumeration of count when each resource block is evaluated by terraform.
private_subnet is a list, so you should pick a single element, e.g.
cidr_block = "${element(var.private_subnet,count.index)}"
also add data module to get the availability zones for a region
data "aws_availability_zones" "available" {}
e.g.
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"