Terraform AWS subnet_id list is treated as single value string for ec2 instance - amazon-web-services

I have code to create VPC, with 2 private subnets, 2xec2 instances in private and bastion in public.
ec2 code uses outputs.tf of VPC module subnet_ids. as there are 2 private subnets there are 2 subnet_ids being generated. when these generated subnet_ids are fed into ec2 instances instead of one subnet_id, it is feeding 2 subnet_ids at once as a single value.
As a result terraform couldn't find that subnet_ids value, creation is being failed.
error:
The subnet ID 'subnet-0***********,subnet-0*************' does not exist
editing subnets*
vpc.tf
private_subnets = "10.10.20.#/#,10.10.20.#/#"
instanceec2.tf
subnet_id = "${module.vpc.private_subnets}"
below are modules:
vpc_main.tf
// Private subnet/s
resource "aws_subnet" "private" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${element(split(",", var.private_subnets), count.index)}"
availability_zone = "${element(split(",", var.azs), count.index)}"
count = "${length(split(",", var.private_subnets))}"
tags {
Name = "${var.name}-private-${element(split(",", var.azs), count.index)}"
Team = "${var.team}"
Environment = "${var.environment}"
Service = "${var.service}"
Product = "${var.product}"
Owner = "${var.owner}"
Description = "${var.description}"
managed_by = "terraform"
}
}
resource "aws_route_table" "private" {
vpc_id = "${aws_vpc.vpc.id}"
count = "${length(split(",", var.private_subnets))}"
tags {
Name = "${var.name}-private-${element(split(",", var.azs), count.index)}"
Team = "${var.team}"
Environment = "${var.environment}"
Service = "${var.service}"
Product = "${var.product}"
Owner = "${var.owner}"
Description = "${var.description}"
managed_by = "terraform"
}
}
resource "aws_route_table_association" "private" {
subnet_id = "${element(aws_subnet.private.*.id, count.index)}"
route_table_id = "${element(aws_route_table.private.*.id, count.index)}"
count = "${length(split(",", var.private_subnets))}"
}
``````
vpc_outputs.tf
```````
output "private_subnets" {
value = "${join(",", aws_subnet.private.*.id)}"
}
Expected value is only one subnet ID as value:
Error: supply 2 Subnet IDs as one value.
aws_instance.ec2-instance[0]: 1 error(s) occurred:
aws_instance.ec2-instance.0: Error launching source instance: InvalidSubnetID.NotFound: The subnet ID 'subnet-0**********,subnet-0***********' does not exist

you are joining the subnet IDs in your output variable:
output "private_subnets" {
value = "${join(",", aws_subnet.private.*.id)}"
}
When you access this output value from your instanceec2.tf you will only receive this joined string of IDs.
So, you again have to slipt the received value as you've done before and access the respective individual ID with your count index of the ec2 resource:
resource "aws_instance" "default" {
count = "${length(split(",", module.vpc.private_subnets))}"
subnet_id = "${element(split(",", module.vpc.private_subnets), count.index)}"
....
}
That should solve you're issue.
Alternatively, you can also output the subnet IDs directly as a list:
output "private_subnets" {
description = "The IDs of the private subnets as list"
value = ["${aws_subnet.private.*.id}"]
}
and then access them with:
subnet_id = "${element(module.vpc.private_subnets, count.index)}"

Since you have 'join'ed the result, you would have to split again if you require just one subnet value.
Something like:
element(split(",", var.private_subnets), 0)

Related

Terraform Multiple instance creation error

Hello I'm trying to create multiple instance based on a count variable. I currently have 1 private subnet with 2 cidr blocks however when i try to create the instance i get
"Error: Error launching source instance: InvalidSubnetID.NotFound: The subnet ID '10.7.90.96/27' does not exist
status code: 400, request id: d7ef5147-ac30-4d31-815a-ad6a46bfe456
on .terraform\modules\vpc\AWS-VPC-Module\main.tf line 1427, in resource "aws_instance" "FID":
1427: resource "aws_instance" "FID" {"
when clearly the TF does create the subnets are present
resource "aws_instance" "FID" {
depends_on = [aws_kms_key.aws-wm-wmad-prod]
count = var.How_many_FID
ami = var.windows_dc_ami_2016
availability_zone = element(var.availability_zones, count.index)
ebs_optimized = var.windows_dc_ebs_optimized
instance_type = var.windows_dc_instance_type_FID
key_name = var.key_name
monitoring = true
subnet_id = element(var.private_subnet_cidr_blocks, count.index)
associate_public_ip_address = false
here is my subnet creation code:
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidr_blocks) # count = 2
vpc_id = aws_vpc.default.id #id34odfjdf
cidr_block = var.private_subnet_cidr_blocks[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
{
Name_TF = "dctr-ad-sbn-use1-az1A-prod-lan-0${count.index+1}",
Project = var.project,
Environment = var.environment
},
var.tags
)
}
subnet_id must be actual subnet id, not its CIDR:
subnet_id = element(aws_subnet.private, count.index).id

Retrieve Subnet ids for map variables

I have created subnet as map variable for availability zone and CIDR block and
variable "public_subnets_list" {
type = map(any)
description = "Public Subnets"
default = {
"ap-south-1a" = "10.0.1.0/24"
"ap-south-1b" = "10.0.2.0/24"
}
}
This works fine for creating subnets under my custom VPC with below code
resource "aws_subnet" "public_subnet" {
depends_on = [
aws_vpc.terraform_vpc
]
for_each = tomap(var.public_subnets_list)
availability_zone = each.key
cidr_block = each.value
vpc_id = aws_vpc.terraform_vpc.id
tags = {
Name = "Public_Subnet_${each.key}"
}
}
How do I retrieve and display the subnet id created for respective AZs from output which i get from aws_subnet.public_subnet[*]
From the Terraform documentation, splat expressions do not work with resources that use the for_each argument.
To retrieve a list of subnet IDs using your Terraform configuration, you can do the following:
output "subnet_ids" {
value = [for subnet in aws_subnet.public_subnet : subnet.id]
}

Terraform - Multi Loops or Maps

I am creating few public subnets, private subnets, igw, nat, route_tables, and route_table entry in AWS using terraform. Below is the number of resources I am creating.
terraform.tfvars
vpc_cidr = "10.0.0.0/16"
public_subnet_count = 6
public_subnets_cidr = ["10.0.1.0/24","10.0.2.0/24","10.0.3.0/24","10.0.4.0/24","10.0.5.0/24", "10.0.6.0/24"]
availability_zones = ["us-east-2a", "us-east-2b","us-east-2c","us-east-2d"]
resources.tf
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public_subnet" {
vpc_id = "${aws_vpc.vpc.id}"
count = var.public_subnet_count
cidr_block = "${element(var.public_subnets_cidr, count.index)}"
availability_zone = length(var.availability_zones) > 1 ? var.availability_zones[count.index % length(var.availability_zones)] : var.availability_zones[0]
map_public_ip_on_launch = false
tags = {
Name = "${var.environment}-${element(var.availability_zones, count.index)}-public-subnet"
Environment = "${var.environment}"
}
}
resource "aws_internet_gateway" "ig" {
count = 1
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.environment}-igw"
Environment = "${var.environment}"
}
}
resource "aws_route_table" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.environment}-public-route-table"
Environment = "${var.environment}"
}
}
resource "aws_route" "public_internet_gateway" {
count = length(aws_route_table.public.*.id)
route_table_id = element(aws_route_table.public.*.id, count.index)
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.ig[0].id
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
route_table_id = element(aws_route_table.public.*.id, count.index)
}
Query in aws_route_table_association.public section.
How do I attach private subnet of a particular az to a route table id. For example if i use 6 private subnets in tfvars, it creates atleast 2 private subnets in one az (ex: us-east-2a). How do i loop and attach 2 subnet from us-east-2a to one route table created for us-east-2a. Kind of map between multiple subnets in one az to route table in that az.
This is to attach each az nat gateway to that az subnet for routing.
You don't need to create a route table per AZ, the route tables are linked to subnets. So, you can just create a single route table and then link all your public subnets to it:
resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.ig.id
}
}
resource "aws_route_table_association" "public" {
count = var.public_subnet_count
subnet_id = aws_subnet.public_subnet[count.index].id
route_table_id = aws_route_table.public.id
}
I've included the single route into the route_table to reduce the amount of code, so you can delete the aws_route resource if you use this.
[Edit]
Sorry I just noticed your mention of private subnets, but attaching route tables follows the same principle, and you don't need a NAT gateway per AZ either (that's expensive!) so you could route all private subnets through a single NAT gateway with one route table.
If you really want 4 NAT gateways, then you could do something like:
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat[count.index].id
}
}
resource "aws_route_table_association" "private" {
count = var.private_subnet_count
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[
count.index % length(var.availability_zones)
].id
}
Since you already use the modulus (%) operator to distribute the subnets, I think you'll get the same result doing it this way.

Creating EC2 instances in multiple subnets with Terraform using for each

I currently have multiple subnets created within a VPC for each availability zone within a region. I am now trying to create one EC2 instance per subnet within each availability zone, and am running into an issue. Below is my code, I am having some trouble getting the subnet ID to attach to EC2 instances. Any advice would be appreciated.
Instance
resource "aws_instance" "public" {
for_each = aws_subnet.public
ami = data.aws_ami.ec2.id
instance_type = var.tableau_instance
key_name = aws_key_pair.main.key_name
subnet_id = [for subnet in aws_subnet.public : subnet.id]
}
Subnet
locals {
az_names = data.aws_availability_zones.azs.names
}
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
Error: Incorrect attribute value type
on vpc.tf line 18, in resource "aws_instance" "public":
18: subnet_id = [for subnet in aws_subnet.public : subnet.id]
|----------------
| aws_subnet.public is object with 3 attributes
Inappropriate value for attribute "subnet_id": string required.
The instance code should be as follows:
resource "aws_instance" "public" {
for_each = aws_subnet.public
ami = data.aws_ami.ec2.id
instance_type = var.tableau_instance
key_name = aws_key_pair.main.key_name
subnet_id = each.value.id
}
This will place 1 instance in each subnet.

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.