restrict count in availability zones - amazon-web-services

I need to restrict the count az as per region available.
i have try by giving
data.aws_availability_zones.available.names[0]
but this create subnet in one AZ only.
sample code :
data "aws_availability_zones" "available" {}
//public subnet
resource "aws_subnet" "terraform_public_subnet" {
count = length(var.pub_subnet_cidr)
vpc_id = aws_vpc.terraform_vpc.id
cidr_block = var.pub_subnet_cidr[count.index]
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "terraform-public-subnet"
}
lifecycle {
create_before_destroy = true
}
}

You need to use the count.index attribute [1] that is made available when using the count meta-argument:
availability_zone = data.aws_availability_zones.available.names[count.index]
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/count#the-count-object

Related

In Terraform, how can I create an iterative list out of two aws_subnet objects?

New to Terraform. I have two aws_subnet objects which I want to associate with route tables. As I understand it, each AZ will need it's own route table. The easiest thing to do would be just declare two route tables, one for each subnet but would like to know if there is a better way to do it instead of just settling for things thrown together.
I have declared my subnets as a list in variables.tf:
variable "my_public_subnets" {
type = list
description = "public subnet within vpc cidr block"
default = ["10.1.2.0/24", "10.1.1.0/24"]
}
And have two public subnets in main.tf
resource "aws_subnet" "pub_1" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.my_public_subnets[0]
availability_zone = "us-east-1a"
}
resource "aws_subnet" "pub_2" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.my_public_subnets[1]
availability_zone = "us-east-1b"
}
Instead of:
resource "aws_route_table_association" "pub_ra_1" {
subnet_id = aws_subnet.pub_1.id
route_table_id = aws_route_table.bar.id
}
resource "aws_route_table_association" "pub2_ra_2" {
subnet_id = aws_subnet.pub_2.id
route_table_id = aws_route_table.bar.id
}
Is there way to do something like this? Create a list/array/map of those two subnets so I don't have to declare a aws_route_table_association for both of them? Maybe there's a better way to set this up in general?
locals {
my_pub_subnets = [aws_subnet.pub_1, aws_subnet.pub_2]
}
resource "aws_route_table_association" "pub_rt_a" {
for_each = locals.my_pub_subnets
subnet_id = each.value
route_table_id = aws_route_table.some_public_route_table.id
depends_on = [aws_subnet.pub_1]
}
Modules are how you create repeatable procedures in TF.
Something like:
locals{
subnets = {
public = "10.1.2.0/24",
private = "10.1.1.0/24"
}
module "subnets" {
source = "./modules/subnets"
for_each = subnets
name = each.key
cidr = each.value
}
for the AZ names, you could also use data.aws_availability_zones.available.names
I would guess that most of you want is really well done inside the VPC module.
You would have to import the VPC into your state to start, but this is how I do my subnets with it.
locals {
subnets = chunklist(cidrsubnets("10.2.8.0/24", 3, 3, 3, 3, 3, 3), 2)
public_subnets = local.subnets[1]
private_subnets = local.subnets[2]
}
data "aws_availability_zones" "available" {
}
resource "aws_eip" "nat" {
count = length(local.private_subnets)
vpc = true
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = "foo"
cidr = "10.2.8.0/24"
azs = data.aws_availability_zones.available.names
private_subnets = local.private_subnets
public_subnets = local.public_subnets
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
reuse_nat_ips = true # <= Skip creation of EIPs for the NAT Gateways
external_nat_ip_ids = aws_eip.nat.*.id
public_subnet_tags = {
"Tier" = "Public"
}
private_subnet_tags = {
"Tier" = "Private"
}
}
output "public_subnets" {
value = module.vpc.public_subnets
}
output "public_subnets_cidr" {
value = module.vpc.public_subnets_cidr_blocks
}
output "private_subnets" {
value = module.vpc.private_subnets
}
output "private_subnets_cidr" {
value = module.vpc.private_subnets_cidr_blocks
}

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.

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.

Terraform AWS subnet_id list is treated as single value string for ec2 instance

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)

Terraform: How to create multiple aws subnets from one resource block?

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]}"