I want to get host IPs out of two subnets in AWS using Terraform/Terragrunt and the cidrhost function.
For some reason, the subnets are being injected into cidrhost in the wrong order.
The remote state it's checking looks like this:
"firewall_subnets_cidr_blocks": {["10.0.0.96/28", "10.0.0.112/28"]}
The first subnet is in 1a and the second in 1b
And this is my code:
resource "aws_network_interface" "private" {
count = length(var.firewall_subnets) > 0 ? length(var.firewall_subnets) : 0
subnet_id = element(var.firewall_subnets, count.index)
private_ip = cidrhost(tolist(var.firewall_subnets_cidr_blocks)[count.index], 10)
description = "Private Interface"
security_groups = [aws_security_group.sg_firewall_private.id]
source_dest_check = false
}
Whenever I run it, it puts the first subnet into the second - the 1b - interface and vice versa.
# aws_network_interface.private[0] will be created
+ resource "aws_network_interface" "private" {
+ description = "Private Interface"
+ private_ip = "10.0.0.122"
# aws_network_interface.private[1] will be created
+ resource "aws_network_interface" "private" {
+ description = "Private Interface"
+ private_ip = "10.0.0.106"
It only happens on this interface script; management and public are identical and behaving as expected.
TF 1.1.9
TG 0.37.3
Digging into the documentation I found this statement:
For a new network interface, the same primary IP address is consistently selected from a given set of addresses, regardless of the order provided.
This sorting happens before the list is passed to my functions resulting in the wrong IPs going to the wrong subnet/AZ.
To override this behaviour I've set private_ip_list_enabled to true.
Here's my working code:
resource "aws_network_interface" "private" {
count = length(var.private_subnets) > 0 ? length(var.private_subnets) : 0
subnet_id = element(var.private_subnets, count.index)
private_ip_list_enabled = true
private_ip_list = tolist([cidrhost(element(var.private_subnets_cidr_blocks, count.index), 10)])
description = "Private Interface"
source_dest_check = false
}
Victory is mine!
Related
I'm working on a terraform module which creates a load balancer amongst other resources. I would like to attach a security group to the load balancer, but only if a a certain variable is equal to "all" OR "gw".
I currently have a count argument in place for the security group itself:
resource "aws_security_group" "akamai_sg" {
count = var.domain_name_suffix == "all" || var.domain_name_suffix == "gw" ? 1 : 0
name = "akamai-pl-sg"
description = "Manage access from Akamai to ${var.environment} alb"
vpc_id = var.vpc_id
tags = merge(var.common_tags, tomap({ "Name" = "akamai-pl-sg" }))
revoke_rules_on_delete = true
}
This means that the security group will only be created if the domain_name_suffix is set to "all" or "gw".
I would like this same functionality for attaching this security group to the ALB. However, I still want to create the ALB regardless of this variable, it is just the security group attachment which I want to be dependent.
Currently I have this configuration for my ALB:
resource "aws_lb" "internal" {
name = "${var.environment}-${var.domain_name_suffix}"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id]
subnets = var.alb_subnets
idle_timeout = var.alb_idle_timeout
tags = var.common_tags
drop_invalid_header_fields = true
dynamic "access_logs" {
for_each = var.alb_access_logging_enabled == true ? [var.alb_access_logging_enabled] : []
content {
bucket = var.alb_access_logging_bucket
enabled = true
prefix = "internal"
}
}
}
However, this results in an error:
"Error: Missing resource instance key
on .terraform/modules/comm_common/alb.tf line 5, in resource "aws_lb" "internal":
5: security_groups = [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id]
Because aws_security_group.alb_sg has "count" set, its attributes must be
accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_security_group.alb_sg[count.index]"
When I add in this [count.index] to the ALB security group reference, I still get the same error.
Could you try the same condition in security_groups field as well? but the value would be a list with two items if condition is true and with one item if condition is false?
resource "aws_lb" "internal" {
name = "${var.environment}-${var.domain_name_suffix}"
internal = true
load_balancer_type = "application"
security_groups = var.domain_name_suffix == "all" || var.domain_name_suffix == "gw" ? [aws_security_group.alb_sg.id, aws_security_group.akamai_sg.id] : [aws_security_group.alb_sg.id]
subnets = var.alb_subnets
idle_timeout = var.alb_idle_timeout
...
}
Trying to create GCP subnetwork for 2 regions using terraform module. use case will be we may create either of the region subnet one time or both regions subnets along with secondary ranges (not all the time and can be more than one secondary range within same subnet). Like,
one region (from 2 regions) subnet at a time without secondary range.
both regions subnet at a time without secondary range.
one region (from 2 regions) subnet at a time with one or more secondary range.
both regions subnet at a time with one or more secondary ranges.
below code hasrestriction to use count and for_each on same resource. planning to add another resource block for central region as well in same main.tf
**main.tf file**
resource "google_compute_subnetwork" "subnet_east4" {
count = "${var.npe_subnet_east4_enable ? 1 : 0}"
name = "${var.npe_subnet_name_east4}"
ip_cidr_range = "${var.npe_cidr_range_east4}"
network = "${var.npe_vpc_name_east4}"
region = "${var.npe_region_east4}"
private_ip_google_access = "true"
for_each = var.npe_secondary_subnets_east4
secondary_ip_range {
range_name = each.value["npe_east4_subnet_pod_range_name"]
ip_cidr_range = each.value["npe_east4_subnet_pod_ip_cidr_range"]
}
secondary_ip_range {
range_name = each.value["npe_east4_subnet_service_range_name"]
ip_cidr_range = each.value["npe_east4_subnet_service_ip_cidr_range"]
}
**variable.tf file**
variable "npe_secondary_subnets_east4" {
type = map(object({
npe_east4_subnet_pod_range_name = string
npe_east4_subnet_pod_ip_cidr_range = string
npe_east4_subnet_service_range_name = string
npe_east4_subnet_service_ip_cidr_range = string
}))
description = "Secondary subnets"
}
Please suggest some alternate logic here
count is not necessary, you can use a conditional expression in your for_each:
for_each = var.npe_subnet_east4_enable ? var.npe_secondary_subnets_east4 : {}
In terraform, I wish to create 3 servers while I'm having 2 subnets.
Creating 2 servers according to the below code will route both server and subnet ID according to the count - But what if I want 3 servers? I don't mind on which of the subnet the third server will be located.
resource "aws_instance" "consul_server" {
count = 2
ami = "ami-00ddb0e5626798373"
instance_type = t2.micro
subnet_id = var.private_subnet_id[count.index]
vpc_security_group_ids = [aws_security_group.consul_server.id]
tags = {
Name = "consul-server-${count.index + 1}-${var.project_name}"
tag_enviroment= var.tag_enviroment
project_name = var.project_name
consul_server = "true"
role = "consul-server"
}
}
Normally you would use element to wrap-around indexing:
subnet_id = element(var.private_subnet_id, count.index)
i have an requirement of creating multiple network interface and each containing 5 private ip depends on AWS_EIP_INSTANCE_COUNT variable . and then i need to assign 5 elastic ip to 5 private ip for each instance . how we can use assign these elastic ip to each network interface if my instance count and AWS_EIP_INSTANCE_COUNT variable has diff values ?
resource "aws_network_interface" "broker" {
count = var.AWS_INSTANCE_COUNT --> no of instances
subnet_id = data.aws_subnet.test1.id
security_groups = data.aws_security_groups.test1.ids
private_ips_count = tonumber(var.AWS_EIP_INSTANCE_COUNT) - 1 --> no of elastic ip
private_ip_list_enabled = "true"
}
resource "aws_eip" "broker-ip" {
vpc = true
count = var.AWS_EIP_INSTANCE_COUNT
network_interface = "${element(aws_network_interface.broker.*.id)}"
associate_with_private_ip = "${join(" ",aws_network_interface.broker[*].private_ip_list[count.index])}"
}
I am new to terraform. I want to create single public subnet and three private subnet per availability zone in particular region in AWS using terraform. I am able to create one private and public subnet per availability zone by referring the following link https://medium.com/#maneetkum/create-subnet-per-availability-zone-in-aws-through-terraform-ea81d1ec1883. However I need to split the one private subet created into another 2. Is that possible in terraform?
data "aws_availability_zones" "available" {}resource "aws_vpc" "myVpc" {
cidr_block = "10.20.0.0/16"
enable_dns_hostnames = true
tags {
Name = "myVpc"
}
}
resource "aws_subnet" "public_subnet" {
count = "${length(data.aws_availability_zones.available.names)}"
vpc_id = "${aws_vpc.myVpc.id}"
cidr_block = "10.20.${10+count.index}.0/24"
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = true
tags {
Name = "PublicSubnet"
}
}
resource "aws_subnet" "private_subnet" {
count = "${length(data.aws_availability_zones.available.names)}"
vpc_id = "${aws_vpc.myVpc.id}"
cidr_block = "10.20.${20+count.index}.0/24"
availability_zone= "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
tags {
Name = "PrivateSubnet"
}
}
The above code is used to create the one private and public subnet per availability zone.
It is possible to create multiple subnets, and automatically place them into an Availability Zone, without duplicating code. Let's keep things DRY. To avoid duplicating the code, use the magic of Terraform meta-arguments and built-in functions. Specifically, use "count" and "cidrsubnet". The "count", will generate as many copies of your subnet as you want.
If you want to provide each subnet with unique values, such as the subnet's tag Name, you can give each subnet a unique, and mnemonic name, by creating a data dictionary with the names that you want assigned to each of the subnets. Then assign them as you create the subnets, by using "count.index". IF that is too much work, you could also just embed the count.index in the name as well.
Different regions have a different number of Availability Zones. To make sure that you are assigning your subnets to Availability Zones that actually exit, you should generate the list of the Availability Zones dynamically. This way, you know that all of the Availability Zones, that are in the list, are actually available in the region that you are working.
What happens if you have more subnets then the region has Availability Zones? Use modulo arithmetic to wrap your working index. Rather than using the index.count directly, do a modulo on the index.count, using the length of the list. This will wrap the index around, so your working index never overflows the length of the list of Availability Zones.
But, the real magic is "cidrsubnet" command. The example below will take the size of the base CIDR block that is passed (which happens to be a /16), add the 2nd parameter (4), and generate a /20 CIDR block. The third parameter indexes through the available CIDR block, thus ensuring that each subnet gets a different sub-CIDR block.
Note: The related cidrsubnets command is quite a bit different. So, be careful, and don't get the two functions confused.
resource "aws_subnet" "area_subnets" {
count = 4 # creates four subnets
vpc_id = var.area_vpc_id
map_public_ip_on_launch = var.map_public_ip_on_launch
cidr_block = cidrsubnet(var.area_subnet_cidr, 4, count.index)
availability_zone_id = data.aws_availability_zones.available.zone_ids[count.index % length(data.aws_availability_zones.available.zone_ids)]
tags = tomap({ "Name" = "${var.subnet_names[count.index]}" })
}
variable "subnet_names" {
type = list(string)
default = [
"Primary NAT Gateway Subnet",
"Secondary NAT Gateway Subnet",
"Channel A Subnet",
"Channel B Subnet"
]
}
variable "map_public_ip_on_launch" {
type = bool
default = true
}
variable "area_vpc_id"
documentation = "The Terraform ID of the containing VPC"
type = string
default = "vpc-abcdefghijklmno"
}
variable "area_subnet_cidr"
documentation = "The base CIDR that you are working with"
type = string
default = "10.0.0.0/16"
}
data "aws_availability_zones" "available" {
state = "available"
filter { # Only fetch Availability Zones (no Local Zones)
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
You can simply duplicate the private_subnet resource element to create the two new subnets in each AZ:
...
resource "aws_subnet" "private_subnet" {
count = "${length(data.aws_availability_zones.available.names)}"
vpc_id = "${aws_vpc.myVpc.id}"
cidr_block = "10.20.${20+count.index}.0/24"
availability_zone= "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
tags {
Name = "PrivateSubnet"
}
}
resource "aws_subnet" "private_subnet_2" {
count = "${length(data.aws_availability_zones.available.names)}"
vpc_id = "${aws_vpc.myVpc.id}"
cidr_block = "10.30.${20+count.index}.0/24"
availability_zone= "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = false
tags {
Name = "PrivateSubnet2"
}
}
You will need to modify the CIDR blocks for each subnet to make sure they don't overlap with each other.