terraform aws ec2 instance ip address assignment - amazon-web-services

I have a typical problem with terraform and aws. I have to deploy 26 instances via terraform but they all should have ip address in an increment order.
for example
instance 1: 0.0.0.1
instance 2: 0.0.0.2
instance 3: 0.0.0.3
Is it possible somehow to achieve in terraform?

Below, you can find an example of how to do it. It just creates for instances with ip range from 172.31.64.100 to 172.31.64.104 (you can't use first few numbers as they are reserved by AWS).
You will have to adjust subnet id and initial IP range which I used in my example to your subnets. You also must ensure that these IP addresses are not used. AWS could already use them for its load balances in your VPC, existing instances or other services. If any IP address in this range is already taken, it will fail.
locals {
ip_range = [for val in range(100, 104): "172.31.64.${val}"]
}
resource "aws_network_interface" "foo" {
for_each = toset(local.ip_range)
subnet_id = "subnet-b64b8988"
private_ips = [each.key]
tags = {
Name = "primary_network_interface"
}
}
resource "aws_instance" "web" {
for_each = toset(local.ip_range)
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
network_interface {
network_interface_id = aws_network_interface.foo[each.key].id
device_index = 0
}
tags = {
Name = "HelloWorld"
}
}

Related

terraform iterate to associate aws resources

I try to create nat gateway on terraform, on each public subnets that I created.
I create the public subntes like that:
resource "aws_subnet" "public_subnet" {
count = length(var.vpc.public_subnets)
vpc_id = aws_vpc.vpc.id
availability_zone = var.vpc.public_subnets[count.index].availability_zone
cidr_block = var.vpc.public_subnets[count.index].cidr_block
tags = var.vpc.public_subnets[count.index].tags
}
I create all elastic ip like that:
resource "aws_eip" "eip" {
for_each = { for eip in var.vpc.eip : eip.name => eip }
vpc = true
tags = each.value.tags
}
And finally I have a resource block to create 3 nat gateways. Each nat gateway have to use a subnet and an eip:
resource "aws_nat_gateway" "ngw" {
count = length(var.vpc.public_subnets)
allocation_id = element(aws_eip.eip.*.allocation_id, count.index)
subnet_id = element(aws_subnet.public_subnet.*.id, count.index)
}
results ==> This object does not have an attribute named "allocation_id"
How should I iterate over 2 resources to create the nat gateay for each pair of subnet/eip ?
thanks.
Since you are using for_each for eip it will be a map, not a list. Thus to access its values you can use values:
allocation_id = element(values(aws_eip.eip)[*].allocation_id, count.index)

How to get subnet_id of ec2 instances in AWS using Terraform?

Suppose we have some ec2 instances in AWS. How can we get subnet_ids of these ec2s via Terraform?
You should use a data source to fetch the existing EC2 instance and then just reference the data source and the attribute subnet_id where needed. Check the documentation: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/instance#subnet_id
Get the data related ec2 instances you want to get subnets of:
data "aws_instances" "ec2s" {
filter {
name = "tag:Name"
values = ["value"]
}
}
Get instance_id of each ec2 using count:
data "aws_instance" "ec2_subnets" {
count = length(data.aws_instances.ec2s.ids)
instance_id = data.aws_instances.ec2s.ids[count.index]
}
Now, subnets could be referred:
resource "aws_***" "***" {
count = length(data.aws_instance.ec2_subnets)
subnet_id = data.aws_instance.ec2_subnets[count.index].subnet_id
###
# Other Terraform attributes
###
}

Automatically create a subnet for each AWS availability zone in Terraform

Is there a better way to optime the code below so I don't have to ask for availability zone again and again instead can do it in once. as the region is variable so I cant define hardcoded availability zone. can you guys please I want my public subnets to be /24
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
}
}
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]
}
An important hazard to consider with the aws_availability_zones data source is that the set of available zones can change over time, and so it's important to write your configuration so that you don't find yourself trapped in a situation where Terraform thinks you intend to replace a subnet that you are currently using and therefore cannot destroy.
A key part of that is ensuring that Terraform understands that each of the subnets belongs to a specific availability zone, so that when the set of availability zones changes Terraform can either add a new subnet for a new availability zone or remove an existing subnet for a now-removed availability zone, without affecting the others that haven't changed. The easiest way to achieve that is to use resource for_each with the set of availability zones:
resource "aws_subnet" "public" {
for_each = aws_avaiability_zones.available.names
# ...
}
The above will declare subnet instances with addresses that each include the availability zone name, like this:
aws_subnet.public["eu-west-1a"]
aws_subnet.public["eu-west-1b"]
aws_subnet.public["eu-west-1e"]
Because they are identified by the availability zone name, Terraform can see that each subnet belongs to a particular availability zone.
For subnets in particular there is an additional challenge: we must assign each subnet its own CIDR block, which means we need a systematic way of allocating IP address space to availability zones so that the networks won't get renumbered by future changes to the set of availability zones.
The documentation for the aws_availability_zone data source includes an example of declaring a mapping table that assigns each region and each availability zone a number between 1 and 14 which is then used to populate one of the octets of the IP address to create a separate prefix per (region, AZ) pair. That example creates only a single VPC and a single subnet, but we can expand on that by using for_each to do it for each of the availability zones, as long as we update the mapping tables whenever we use a new region or a new availability zone suffix letter is assigned (up to 14 of each):
variable "region_number" {
# Arbitrary mapping of region name to number to use in
# a VPC's CIDR prefix.
default = {
us-east-1 = 1
us-west-1 = 2
us-west-2 = 3
eu-central-1 = 4
ap-northeast-1 = 5
}
}
variable "az_number" {
# Assign a number to each AZ letter used in our configuration
default = {
a = 1
b = 2
c = 3
d = 4
e = 5
f = 6
# and so on, up to n = 14 if that many letters are assigned
}
}
data "aws_region" "current" {}
# Determine all of the available availability zones in the
# current AWS region.
data "aws_availability_zones" "available" {
state = "available"
}
# This additional data source determines some additional
# details about each VPC, including its suffix letter.
data "aws_availability_zone" "all" {
for_each = aws_avaiability_zones.available.names
name = each.key
}
# A single VPC for the region
resource "aws_vpc" "example" {
cidr_block = cidrsubnet("10.1.0.0/16", 4, var.region_number[data.aws_region.current.name])
}
# A subnet for each availability zone in the region.
resource "aws_subnet" "example" {
for_each = aws_availability_zone.all
vpc_id = aws_vpc.example.id
availability_zone = each.key
cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 4, var.az_number[each.value.name_suffix])
}
For example, if we were working in us-west-2 and there were availability zones us-west-2a and us-west-2c, the above would declare:
A single aws_vpc.example with CIDR block 10.1.48.0/20, where 48 is the decimal representation of hex 0x30, where 3 is the number for us-west-2.
A subnet aws_subnet.example["us-west-2a"] in us-west-2a with CIDR block 10.1.49.0/24, where 49 is the decimal representation of hex 0x31.
A subnet aws_subnet.example["us-west-2c"] in us-west-2c with CIDR block 10.1.51.0/24, where 51 is the decimal representation of hex 0x33.
Notice that there is no subnet for 10.1.50.0/24, because 50 (hex 0x32) is reserved for a hypothetical us-west-2b. By allocating these addresses statically by subnet letter we can ensure that they will not change over time as availability zones are added and removed.
You can automate creation of subnets using count and cidrsubnets.
An example would be:
resource "aws_subnet" "public_subnet" {
count = length(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.app_vpc.id
cidr_block = cidrsubnet(aws_vpc.app_vpc.cidr_block, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "public_subnet_${count.index}"
}
depends_on = [aws_vpc_dhcp_options_association.dns_resolver]
}
The above will automatically create subnet in each AZ as well as assing
cidr block (/24, assuming that vpc is /16) to it.

Terraform get IPs of vpc endpoint subnets

I am trying to setup AWS SFTP transfer in vpc endpoint mode but there is one think I can't manage with.
The problem I have is how to get target IPs for NLB target group.
The only output I found:
output "vpc_endpoint_transferserver_network_interface_ids" {
description = "One or more network interfaces for the VPC Endpoint for transferserver"
value = flatten(aws_vpc_endpoint.transfer_server.*.network_interface_ids)
}
gives network interface ids which cannot be used as targets:
Outputs:
api_url = https://12345.execute-api.eu-west-1.amazonaws.com/prod
vpc_endpoint_transferserver_network_interface_ids = [
"eni-12345",
"eni-67890",
"eni-abcde",
]
I went through:
terraform get subnet integration ips from vpc endpoint subnets tab
and
Terraform how to get IP address of aws_lb
but none of them seems to be working. The latter says:
on modules/sftp/main.tf line 134, in data "aws_network_interface" "ifs":
134: count = "${length(local.nlb_interface_ids)}"
The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.
You can create an Elastic IP
resource "aws_eip" "lb" {
instance = "${aws_instance.web.id}"
vpc = true
}
Then specify the Elastic IPs while creating Network LB
resource "aws_lb" "example" {
name = "example"
load_balancer_type = "network"
subnet_mapping {
subnet_id = "${aws_subnet.example1.id}"
allocation_id = "${aws_eip.example1.id}"
}
subnet_mapping {
subnet_id = "${aws_subnet.example2.id}"
allocation_id = "${aws_eip.example2.id}"
}
}

How to apply security group rules in different regions?

I need help in writing a terraform script fro AWS as follows:
I have a list of security groups in multiple regions, for example,
- us-east-2
- us-west-1
- etc.
Now when I add a new instance in any of the region, I am applying an EIP.
I need to add that EIP all traffic in every region's security group.
So far what I tried:
Saving the EIP in a file called node_ips.txt
Read that file
Apply it to security group
Here is the script sample:
variable "list_eips" { type=list" }
resource "aws_eip_association" "eip_assoc" {
count = "${local.number_of_instances}"
instance_id = "${element(aws_instance.ec2_instance.*.id, count.index)}"
allocation_id = "${element(data.aws_eip.db_ip.*.id, count.index)}"
provisioner "local-exec" {
command = "echo ${self.public_ip} >> node_ips.txt"
}
}
data "template_file" "read_node_ips" {
template = "${file("${path.cwd}/node_ips.txt")}"
}
resource "aws_security_group_rule" "allow_db_communication" {
type = "ingress"
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["${split(",", "${join("/32,", concat(compact(split("\n",data.template_file.read_node_ips.rendered)),var.list_eips) )}/32")}"]
security_group_id = "${data.aws_security_group.cassandra_sg.id}"
}
This is not working for me. It is adding rules only for list_eips.
Again, when I add a new instance in different region, the secruity group is different. So, I am not able to know what was my security group in previous region.
Please advise any idea.
Thanks.
Terraform has the idea of "Multiple Provider Instances", You can create providers for each region that need to access and manipulate resources.
# West coast region
provider "aws" {
alias = "west"
region = "us-west-2"
}
resource "aws_instance" "foo" {
provider = "aws.west"
# ...
}
https://www.terraform.io/docs/configuration/providers.html