Terraform - cannot launch AWS EC2 instance with Launch Template - amazon-web-services

My EC2 instance resource code
resource "aws_instance" "my-sample-webapp-ec2" {
availability_zone = var.availability_zone
subnet_id = var.subnet_id
key_name = var.ec2_instance_name
instance_initiated_shutdown_behavior = "stop"
disable_api_termination = false
# vpc_security_group_ids = var.vpc_security_group_ids
launch_template {
id = var.launch_template_id
version = "$Latest"
}
tags = {
"Name" = var.ec2_instance_name
}
root_block_device {
delete_on_termination = true
}
}
My launch template already exists in AWS region - checked
My Module import for the above EC2 resource
module "aws_ec2_machines" {
source = "./modules/ec2_machines"
count = length(local.availability_zones)
launch_template_id = var.launch_template_id
launch_template_ver = var.launch_template_ver
ec2_instance_name = "${var.ec2_instance_name}-${count.index + 1}"
availability_zone = local.availability_zones[count.index]
}
what I want to do is below
Specify my launch template and launch EC2 instance(s)
Subnet association should happen based on availability_zone
Currently, I have only 3 subnets (1 per availability zone), but they are not default. Also, the VPC under which the subnets are created is also not the default VPC.
The error I am getting
│ Error: Error launching source instance: InvalidParameterValue: Value (us-east-2b) for parameter availabilityZone is invalid. Subnet 'subnet-xxxxxx' is in the availability zone us-east-2a
│ status code: 400, request id: 75a126cb-59eb-40fe-9fa5-579ed908edbd
│
│ with module.aws_ec2_machines[1].aws_instance.my-sample-webapp-ec2,
│ on modules\ec2_machines\main.tf line 7, in resource "aws_instance" "my-sample-webapp-ec2":
│ 7: resource "aws_instance" "my-sample-webapp-ec2" {
│
╵
What am I doing wrong?

OK - I figured out to problem (pen and pencil writing)
Launch Template ID does not need to have any subnet defined
But any VPC should have subnets created and (good practice) assigned to a different AZ
a. e.g. subnet-2a should be with AZ 1, subnet-2b should be with AZ 2 etc.
Now, with the below resource/module invocation - it all worked well
resource "aws_instance" "my-sample-webapp-ec2" {
subnet_id = var.subnet_id
instance_initiated_shutdown_behavior = "stop"
disable_api_termination = false
launch_template {
id = var.launch_template_id
version = "$Latest"
}
tags = {
"Name" = var.ec2_instance_name
}
root_block_device {
delete_on_termination = true
}
}
And the subsequent module call in main.tf
module "aws_ec2_machines" {
source = "./modules/ec2_machines"
count = length(local.availability_zones)
launch_template_id = var.launch_template_id
launch_template_ver = var.launch_template_ver
ec2_instance_name = "${var.ec2_instance_name}-${count.index + 1}"
subnet_id = local.subnets[count.index % local.available_subnet_count]
}

Related

Can't access attributes on a primitive-typed value (string) - trying to create an instance in every subnet

I'm using a VPC module that specifies a list of public subnets (3 to be exact) and I want to deploy an instance in each subnet. Here's my VPC module:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.12.0"
name = "${var.name}-vpc"
cidr = "10.1.0.0/16"
azs = [var.azs]
private_subnets = ["10.1.0.0/19", "10.1.32.0/19", "10.1.64.0/19"]
public_subnets = ["10.1.128.0/20", "10.1.144.0/20", "10.1.160.0/20"]
enable_nat_gateway = true
single_nat_gateway = false
one_nat_gateway_per_az = false
enable_dns_hostnames = true
tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
}
public_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = "1"
}
}
Here's my instance resource block
resource "aws_instance" "bastion" {
count = length(var.azs)
ami = var.instance_ami
key_name = aws_key_pair.bastion_auth.id
instance_type = var.instance_type
security_groups = [aws_security_group.bastion-sg.id]
associate_public_ip_address = true
subnet_id = module.vpc.public_subnets[count.index].id
user_data = file("userdata.tpl")
root_block_device {
volume_size = var.main_vol_size
}
tags = {
Name = "${var.name}-bastion-host-${count.index + 1}"
}
}
How can I get it to deploy an instance in each subnet? I tried using count.index but I'm getting this error
│ on bastion.tf line 9, in resource "aws_instance" "bastion":
│ 9: subnet_id = module.vpc.public_subnets[count.index].id
│ ├────────────────
│ │ count.index is 0
│ │ module.vpc.public_subnets is tuple with 3 elements
│
│ Can't access attributes on a primitive-typed value (string).
public_subnets is already a list of IDs. So it should be:
subnet_id = module.vpc.public_subnets[count.index]

Attach each EIP to each Nat Gatway in Terraform

I'm creating two public subnets that will each contain a nat gateay. My code, attempts to create these nats per subnet, and then allocate the eip to each. However, since my for each starts the code block, it looks like the allocation id became us-east-* instead of the id of the eip.
Variables.tf:
variable "public_subnet_numbers" {
type = map(number)
description = "Map of AZ to a number that should be used for public subnets"
default = {
"us-east-1a" = 1
"us-east-1b" = 2
#"us-east-1c" = 3
}
}
variable "private_subnet_numbers" {
type = map(number)
description = "Map of AZ to a number that should be used for private subnets"
default = {
"us-east-1a" = 4
"us-east-1b" = 5
#"us-east-1c" = 6
}
}
variable "vpc_cidr" {
type = string
description = "The IP range to use for the VPC"
default = "192.168.0.0/16"
}
Main.tf:
resource "aws_eip" "nat" {
count = 2
vpc = true
lifecycle {
# prevent_destroy = true
}
tags = {
Name = "cf-${var.infra_env}-eip"
Project = "cf.io"
Environment = var.infra_env
VPC = aws_vpc.vpc.id
ManagedBy = "terraform"
Role = "private"
}
}
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = each.value.id #aws_subnet.public[each.key].id
allocation_id = aws_eip.nat[each.key].id
tags = {
Name = "cf-${var.infra_env}-ngw"
Project = "cf.io"
VPC = aws_vpc.vpc.id
Environment = var.infra_env
ManagedBy = "terraform"
Role = "private"
}
}
Error:
Error: Invalid index
│
│ on ../terraform/modules/networking/gateways.tf line 42, in resource "aws_nat_gateway" "ngw":
│ 42: allocation_id = aws_eip.nat[each.key].id
│ ├────────────────
│ │ aws_eip.nat is tuple with 2 elements
│ │ each.key is "us-east-1a"
│
│ The given key does not identify an element in this collection value: a number is required.
╵
╷
│ Error: Invalid index
│
│ on ../terraform/modules/networking/gateways.tf line 42, in resource "aws_nat_gateway" "ngw":
│ 42: allocation_id = aws_eip.nat[each.key].id
│ ├────────────────
│ │ aws_eip.nat is tuple with 2 elements
│ │ each.key is "us-east-1b"
│
│ The given key does not identify an element in this collection value: a number is required.
You're mixing count and for_each. The easiest way to solve this would be to use for_each in your EIP creation as well, which makes sense because you are creating an EIP for each NAT. That would also make your code work better if you decided to add another subnet later, you wouldn't need to go in and change the count from 2 to 3.
Otherwise, you need to use the index function to convert the each value to an index number.
As Mark B mentioned mixing the count and for_each is not recommended. In your current setup using exclusively for_each is the way to go based on the private_subnet_numbers variable.
In your aws_eip.nat resource change count to for_each
resource "aws_eip" "nat" {
for_each = var.private_subnet_numbers
vpc = true
}
Next in your resource aws_nat_gateway.ngw you should refer to subnet ids using each
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = aws_subnet.public[each.key].id
....
}
And the code as a whole for clarity
resource "aws_vpc" "vpc" {
... vpc configurations ...
}
resource "aws_subnet" "public" {
for_each = var.private_subnet_numbers
vpc_id = aws_vpc.vpc.id
... subnet configurations ...
}
resource "aws_eip" "nat" {
for_each = var.private_subnet_numbers
vpc = true
lifecycle {
# prevent_destroy = true
}
tags = {
Name = "cf-${var.infra_env}-eip"
Project = "cf.io"
Environment = var.infra_env
VPC = aws_vpc.vpc.id
ManagedBy = "terraform"
Role = "private"
}
}
resource "aws_nat_gateway" "ngw" {
for_each = var.private_subnet_numbers
subnet_id = aws_subnet.public[each.key].id
allocation_id = aws_eip.nat[each.key].id
tags = {
Name = "cf-${var.infra_env}-ngw"
Project = "cf.io"
VPC = aws_vpc.vpc.id
Environment = var.infra_env
ManagedBy = "terraform"
Role = "private"
}
}

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

Terraform code for creating AWS EC2 instances with volumes

I'm trying to create two EC2 instances on AWS with the following features:
Instance: Ubuntu Server 18.04 LTS (HVM), SSD Volume Type
Type: ami for 64-bit x86 us-east-1 region ami-0747bdcabd34c712a (64-bit x86)
Type: 2 processors, 8 GB Memory, Up to 10 Gigabit Network, m5a type m5a.large
Number of instances: 2
Storage: 20 GB General Purpose SSD, Delete storage on termination
Tags: Name=lfs258_class
Allow all traffic from everywhere
Use the existing SSH Keypair I have on my laptop
This is the tree file structure
.
├── README.md
├── ec2.tf
├── outputs.tf
├── provider.tf
├── variables.tf
└── versions.tf
file ec2.tf
locals {
availability_zone = "${local.region}a"
name = "kubernetes-lfs258-course"
region = "us-east-1"
tags = {
Owner = "pss-cli-user1 "
Environment = "kubernetes-lfs258-course"
}
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 3.0"
name = local.name
azs = ["${local.region}a", "${local.region}b", "${local.region}c"]
public_subnets = lookup(var.init,"public-subnet")
tags = local.tags
}
module "security_group" {
source = "terraform-aws-modules/security-group/aws"
version = "~> 4.0"
name = local.name
description = "Security group for example usage with EC2 instance"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["0.0.0.0/0"]
ingress_rules = ["all-all"]
egress_rules = ["all-all"]
tags = local.tags
}
################################################################################
# Supporting Resources for the EC2 module
################################################################################
module "ec2" {
source = "../../"
name = local.name
ami = lookup(var.init,"ami")
#instance_type = "c5.large"
instance_type = lookup(element(var.instances,0),"instance_type")
availability_zone = local.availability_zone
subnet_id = element(module.vpc.private_subnets, 0)
vpc_security_group_ids = [module.security_group.security_group_id]
associate_public_ip_address = true
tags = local.tags
}
resource "aws_volume_attachment" "this" {
device_name = "/dev/sdh"
volume_id = aws_ebs_volume.this.id
instance_id = module.ec2.id
}
resource "aws_ebs_volume" "this" {
availability_zone = local.availability_zone
size = 20
tags = local.tags
}
file outputs.tf
# EC2
output "ec2_id" {
description = "The ID of the instance"
value = module.ec2.id
}
output "ec2_arn" {
description = "The ARN of the instance"
value = module.ec2.arn
}
output "ec2_capacity_reservation_specification" {
description = "Capacity reservation specification of the instance"
value = module.ec2.capacity_reservation_specification
}
output "ec2_instance_state" {
description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`"
value = module.ec2.instance_state
}
output "ec2_primary_network_interface_id" {
description = "The ID of the instance's primary network interface"
value = module.ec2.primary_network_interface_id
}
output "ec2_private_dns" {
description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC"
value = module.ec2.private_dns
}
output "ec2_public_dns" {
description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC"
value = module.ec2.public_dns
}
output "ec2_public_ip" {
description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
value = module.ec2.public_ip
}
output "ec2_tags_all" {
description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block"
value = module.ec2.tags_all
}
file provider. tf
provider "aws" {
region = local.region
profile = "pss-cli-user1"
shared_credentials_file = "~/.aws/credentials"
}
file variables.tf
# This file defines variables types and their initial hardcoded values
variable "zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
variable "instances" {
type = list(object({
instance_type = string
count = number
tags = map(string)
}))
# If instances is not defined in terraforms.tfvars use this value
default = [
{
instance_type = "m5a.large"
count = 2
tags = { "UsedFor" = "kubernetes lfs258 course"}
}
]
}
variable "init" {
type = object({
vpc-id=list(string),
public-subnet=list(string),
aws_region=string,
ami=string
vpc-sec-group= list(string)
})
# if not defined in terraform.tfvars takes this default
default = {
vpc-id = ["vpc-02938578"]
public-subnet = ["subnet-94e25d9a"]
aws_region = "us-east-1"
ami = "ami-0747bdcabd34c712a"
vpc-sec-group = ["sg-d60bf3f5"]
}
}
file versions.tf
terraform {
required_version = ">= 0.13.1"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 3.51"
}
}
}
The command terraform init works without errors
However terraform plan is giving me the following complains
╷
│ Error: Unsupported argument
│
│ on ec2.tf line 41, in module "ec2":
│ 41: name = local.name
│
│ An argument named "name" is not expected here.
╵
╷
│ Error: Unsupported argument
│
│ on ec2.tf line 43, in module "ec2":
│ 43: ami = lookup(var.init,"ami")
│
│ An argument named "ami" is not expected here.
..... more errors like this removed
Questions are :
What am I doing wrong and how to fix it ?
How to create a better IaC Terraform deployment?
BR
David

Terraform one EC2 instance with two subnets

I need to create one EC2 and associate 2 subnets to it.
variables.tf
variable "aws_subnet_id_this" {
description = "Subnet ID"
default = ["subnet-09df122a4faee8882", "subnet-2fcc756f02ddb4b62"]
}
main.tf
resource "aws_instance" "test" {
ami = var.ami_id
instance_type = var.ec2_instance_type
subnet_id = var.aws_subnet_id_this
key_name = var.pki_name
vpc_security_group_ids = [aws_security_group.Allow_SSH_in.id]
}
Error:
Error: Incorrect attribute value type
on main_count_data.tf line 57, in resource "aws_instance" "test":
57: subnet_id = var.aws_subnet_id_this
|----------------
| var.aws_subnet_id_eks is tuple with 2 elements
Inappropriate value for attribute "subnet_id": string required.
So I tried this:
main.tf
resource "aws_instance" "prueba" {
ami = var.ami_id
instance_type = var.ec2_instance_type
#subnet_id = var.aws_subnet_id_this
count = 2
subnet_id = "${element(var.aws_subnet_id_this, count.index)}"
key_name = var.pki_name
vpc_security_group_ids = [aws_security_group.Allow_SSH_in.id]
}
}
but this last portion of code tries to create a new EC2 instance with the second subnet and this is not what I expect tough.
To sum up: I need 1 EC2 containing 2 subnets defined in the variables.tf file.
How can I do this?
Below is an example of how you can create one instance with two NICs in different subnets. The NICs must be in same AZ. So instance can have two NICs in different subnets as long as they are in same AZ:
variable "aws_subnet_id_this" {
description = "Subnet ID"
default = ["subnet-09df122a4faee8882", "subnet-2fcc756f02ddb4b62"]
}
resource "aws_network_interface" "nic1" {
subnet_id = var.aws_subnet_id_this[0]
}
resource "aws_network_interface" "nic2" {
subnet_id = var.aws_subnet_id_this[1]
}
resource "aws_instance" "prueba" {
ami = var.ami_id
instance_type = var.ec2_instance_type
key_name = var.pki_name
network_interface {
device_index = 0
network_interface_id = aws_network_interface.nic1.id
}
network_interface {
device_index = 1
network_interface_id = aws_network_interface.nic2.id
}
}