attaching different Security Groups to different EC2s - amazon-ec2

Requirement:-
I have multiple group(say 2 groups) of EC2s where each group contain 6 EC2. and I have to attach different SG to each group.
Example:-
Group1 contains :- Head1, child :EC2-1, EC2-2....6 and need to attach SG1
Group2 contains :- Head2, child :EC2-3, EC2-4 ...6 and need to attach SG2
I don't want to write separate resource "aws_instance"
Head-Module
resource "aws_security_group" "sg" {
count = var.ec2_instance_count
name = "${local.account}${count.index}"
vpc_id = local.vpc_id
}
resource "aws_instance" "ec2_instance" {
count = var.ec2_instance_count
security_groups = [element(aws_security_group.sg.*.id, count.index)]
}
Child-Module:
data "aws_security_groups" "data_security_group" {
filter {
name = "group-name"
values = ["${local.account}${count.index}"]
}
}
resource "aws_instance" "ec2_child" {
count = var.ec2_instance_count*var.numberofchild
security_groups = [element(aws_security_group.data_security_group.*.id, count.index)]
}
Error: Error launching source instance: InvalidGroup.NotFound: The security group 'terraform-2020082
4151444795600000001' does not exist in VPC 'vpc-ghhje85abcy'
status code: 400, request id: 9260fd88-a03a-4c46-b67c-3287594cdab5
on main.tf line 68, in resource "aws_instance" "ec2_instance":
68: resource "aws_instance" "ec2_instance" {
Note: I am using data "aws_security_groups" instead of data "aws_security_group". If I use the later one, I know I will be able to get only one SG in the data resource and it throws me an error :multiple Security Groups matched; from which I kind of moved ahead by using data "aws_security_groups" and this error get vanished. but the latest error I m facing is: InvalidGroup.NotFound as mentioned above.
Update: I am able to use data resource and able to attach the different SG to different EC2. the only issue is random Sequencing. for all 6 EC2 of group 1 I want them to assign first SG and so on.

Don't use the data, instead create your resource "aws_security_group" using a count like you do on your resource "aws_instance" that way you can reference them directly...
resource "aws_security_group" "sg" {
count = var.ec2_instance_count
name = "${local.account}${count.index}"
vpc_id = local.vpc_id
}
resource "aws_instance" "ec2_instance" {
count = var.ec2_instance_count
security_groups = [element(aws_security_group.sg.*.id, count.index)]
}

Thanks Helder, I created the resource with count. its not the huge infrastructure but a fairly complex one. 8 groups (each group has 1 Parent and 6 child EC2)
There is 1 External SG for all parents. and 8 internal SG 1 for each group).
I had to follow a sequence of provisioning because the requirement was to pass the "Parent Host name" to respective group of Childs in "Childs User data" so I had to keep them in a separate modules and used "data" resources for reuse.
ParentModule:
resource "aws_instance" "ec2_instance" {
count = tonumber(var.mycount)
vpc_security_group_ids = [data.aws_security_group.external_security_group.id, element(data.aws_security_group.internal_security_group.*.id, count.index)]
...
}
resource "aws_security_group" "internal_security_group" {
count = tonumber(var.mycount)
name = "${var.internalSGname}${count.index}"
}
resource "aws_security_group" "external_security_group" {
name = ${var.external_sg_name}"
}
ChildModule: with Data resource and used dynamic map to assignment of SG to Proper group of EC2.
data "aws_security_group" "internal_security_group" {
count = tonumber(var.mycount)
filter {
name = "group-name"
values = "${var.internalSGname}${count.index}"]
}
}
resource "aws_instance" "ec2_child" {
count = local.child_count * tonumber(var.mycount)
vpc_security_group_ids = ["${element(data.aws_security_group.internal_security_group.*.id, "${lookup(local.SG_lookup, count.index, 99)}")}"]
variable.tf
locals{
SG_lookup = {
for n in range(0, (local.child_count * tonumber(var.mycount))) :
n => "${floor(((n) / local.child_count))}"
}
}
}

Related

How to get newly created instance id using Terraform

I am creating AWS ec2 instance(s) using auto scaling group and launch template. I would like to get instance ids of the newly launched instances. Is this possible?
For brevity purpose I have removed some code
resource "aws_launch_template" "service_launch_template" {
name_prefix = "${var.name_prefix}-lt"
image_id = var.ami_image_id
iam_instance_profile {
name = var.instance_profile
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_lb_target_group" "service_target_group" {
name = "${var.name_prefix}-tg"
target_type = "instance"
vpc_id = var.vpc_id
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "service_autoscaling_group" {
name = "${var.name_prefix}-asg"
max_size = var.max_instances
min_size = var.min_instances
desired_capacity = var.desired_instances
target_group_arns = [aws_lb_target_group.service_target_group.arn]
health_check_type = "ELB"
launch_template {
id = aws_launch_template.service_launch_template.id
version = aws_launch_template.service_launch_template.latest_version
}
depends_on = [aws_alb_listener.service_frontend_https]
lifecycle {
create_before_destroy = true
}
}
resource "aws_alb" "service_frontend" {
name = "${var.name_prefix}-alb"
load_balancer_type = "application"
lifecycle {
create_before_destroy = true
}
}
resource "aws_alb_listener" "service_frontend_https" {
load_balancer_arn = aws_alb.service_frontend.arn
protocol = "HTTPS"
port = "443"
}
This is working. But I would like to output the instance ids of the newly launched instances. From terraform documentation looks like the aws_launch_template or aws_autoscaling_group does not export the instance ids. What are my options here?
Terraform is probably completing, and exiting, before the auto-scaling group has even triggered a scale-up event and created the instances. There's no way for Terraform to know about the individual instances, since Terraform isn't managing those instances, the auto-scaling group is managing them. You would need to use another tool, like the AWS CLI, to get the instance IDs.

Iterating network interfaces in vsphere provider with Terraform

Question: How can I iterate through a nested map to assign string values for a data resource block?
Context:
Working on a requirement to deploy multiple VMs via OVA template using the vsphere provider 2.0 on terraform.
As the network interfaces will vary according to the environment, the OVA template will only include the "global" network interface common to all VMs in any environment.
I am using the vsphere_network data resource to retrieve the distributed virtual port group ID for each network interface being assigned to the VMs.
Currently stuck on a variable interpolation to iterate through this info to assign to each vm resource in terraform.
1 vsphere network data block to iterate all DVPG ids, and 1 vm resource block to deploy all vms with those DVPGs using dynamic network interface block
VM Configuration Variable:
variable "vmconfig" {
description = "Map of VM name => Configs "
type = map(object({
name = string
cpus = number
memory = number
folder = string
remote_ovf = string
netint = map(string)
}))
default = {}
}
.tfvars:
vmconfig = {
"vm1" = {
name = "vm1"
cpus = 4
memory = 16384
folder = "foo/bary"
remote_ovf = "foo.bar.ova"
netint = {
nic1 = "segment1",
nic2 = "segment2",
nic3 = "segment3",
nic4 = "segment4"
}
},
"vm2" = {...}, etc.
Calling the variable above into a local var:
locals {
vm_values = { for name, config in var.vmconfig : name => {
vm_name = config.name
num_cpus = config.cpus
memory = config.memory
folder = config.folder
remote_ovf_url = config.remote_ovf
netint = config.netint
}
}
}
Trying to iterate through each value for netint inside the data resource block using for_each instead of count(listed as best practices for the vm type being deployed):
data "vsphere_network" "nicint" {
for_each = local.vm_values
name = each.value.netint
datacenter_id = data.vsphere_datacenter.dc.id
}
This data resource block is then called inside the VM resource block using dynamic:
resource "vsphere_virtual_machine" "vm" {
.
.
.
dynamic "network_interface" {
for_each = data.vsphere_network.nicint
content {
network_id = network_interface.value.id
}
}
}
The issue I'm having is iterating through each value inside netint, I get the inkling that I might be missing something trivial here, would appreciate your support in defining that for_each iteration accurately so that multiple vsphere_network data sources are available programmatically using that one data block.
I have tried the following variations for iterating in the data block:
data "vsphere_network" "nicint" {
for_each = {for k,v in local.vm_values : k => v.netint}
name = each.value
datacenter_id = data.vsphere_datacenter.dc.id
}
Error I get is:
Inappropriate value for attribute "name": string required
each.value is a map of string with 4 elements
I tried using merge, it works! BUT it ended up creating duplicates for each VM and wouldn't modify an existing resource, but destroy and create another.
Another local variable created to map the network interface segments:
netint_map = merge([
for vmtype, values in var.vmconfig:
{
for netint in values.netint:
"${vmtype}-${netint}" => {vmtype = vmtype, netint = netint}
}
]...)
data "vsphere_network" "nicint" {
for_each = local.netint_map
name = each.value
datacenter_id = data.vsphere_datacenter.dc.id
}
Dear Hivemind, please guide me to optimize this effectively - thank you!!
Your merge is correct. So I just post it here for reference:
locals {
netint_map = merge([
for vmtype, values in var.vmconfig:
{
for netint in values.netint:
"${vmtype}-${netint}" => {vmtype = vmtype, netint = netint}
}
]...)
}
data "vsphere_network" "nicint" {
for_each = local.netint_map
name = each.value
datacenter_id = data.vsphere_datacenter.dc.id
}
I think the issue is with your dynamic block. namely, instead of for_each = data.vsphere_network.nicint you should iterate over nicint from your variable, not data source.
resource "vsphere_virtual_machine" "vm" {
for_each = var.vmconfig
#...
dynamic "network_interface" {
for_each = toset(each.value.netint)
content {
network_id = data.vsphere_network.nicint["${each.key}-${network_interface.key}"].id
}
}
}

Terraform create multiple ec2 instances in multiple subnets

I am trying to create multiple ec2 instances with access to multiple subnets.
I've found questions and answers on doing these things individually, but not combined.
First, I create a private and a public subnet, then I setup a local to store the IDs once they are created:
locals {
subnets = [ aws_subnet.public_subnet.id, aws_subnet.private_subnet.id ]
}
Next, I can create a variable number of servers in the private_subnet using for_each and the below:
servers = [ "s1", "s2" ]
resource "aws_instance" "system" {
for_each = var.servers
ami = var.aws_ami
instance_type = var.instance_type
#subnet_id = aws_subnet.private_subnet.id
count = 2
subnet_id = element(local.subs, count.index)
}
What I want to have, is that the server can access both subnets (it doesn't exist as far as I can tell, but the equivalent of subnet_ids = [aws_subnet.public_subnet.id, aws_subnet.private_subnet.id]).
I found a nice answer which works for a specific instance by creating two NICs (Terraform one EC2 instance with two subnets), however I need to do this var.servers times so it's difficult to hardcode the var.servers * 2 NICs with my current aws_instance setup (and I trip up when combining for_each and count).
Can someone please point me in the right direction?
Two create multiple servers (in your case 4 in total) in private (two servers) and public (two servers) subnets you can use count:
resource "aws_instance" "system" {
count = length(var.servers) * length(local.subnets)
ami = var.aws_ami
instance_type = var.instance_type
subnet_id = element(local.subnets, count.index)
}
For those looking to have a similar setup there are a few steps (assuming subnets and route tables already exist):
Create a machine on a single subnet
Create an additional network interface
Attach the network interface to the 'other' subnet (for the existing machine)
Create a variable for machines to create:
domains = [
"asd.com",
"asd2.com"
]
Create the machines on a single subnet:
resource "aws_instance" "domain" {
for_each = var.domains
ami = var.aws_ami
subnet_id = aws_subnet.public_subnet.id
associate_public_ip_address = true
tags = {
Name = "Instance - ${each.key}"
}
}
Create the additional interfaces for the 'other' subnet:
resource "aws_network_interface" "nics" {
for_each = var.domains
subnet_id = aws_subnet.private_subnet.id
tags = {
Name = "NIC - ${each.key}"
}
}
Attach the network interfaces to the 'other' subnet (for the existing machine):
resource "aws_network_interface_attachment" "attach_nics" {
for_each = var.domains
instance_id = aws_instance.domain[each.key].id
network_interface_id = aws_network_interface.nics[each.key].id
device_index = 1 # public_subnet = 0
}
The 'trick' here (that I didn't know) is understanding that you can access data from created resources based on their names in the existing script (which is used in the aws_network_interface_attachment component).

Terraform - Create ec2 instances in each availability zone

I am trying to create multiple ec2 instance with this script
resource "aws_instance" "my-instance" {
count = 3
ami = ...
instance_type = ...
key_name = ...
security_groups = ...
tags = {
Name = "my-instance - ${count.index + 1}"
}
}
This creates 3 instances. But all three are in same availability zones. I want to create one instance in each availability zone or one in each of the availability zone that I provide. How can I do it?
I read that I can use
subnet_id = ...
option to specify the availability zone where the instance should be created. But I am not able to figure out how to loop through instance creation (which is currently being handled by count parameter) and specifiy different subnet_id
Can someone help please.
There are several ways of accomplishing this. What I would recommend is to create a VPC with 3 subnets and place an instance in each subnet:
# Specify the region in which we would want to deploy our stack
variable "region" {
default = "us-east-1"
}
# Specify 3 availability zones from the region
variable "availability_zones" {
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = var.region
}
# Create a VPC
resource "aws_vpc" "my_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "my_vpc"
}
}
# Create a subnet in each availability zone in the VPC. Keep in mind that at this point these subnets are private without internet access. They would need other networking resources for making them accesible
resource "aws_subnet" "my_subnet" {
count = length(var.availability_zones)
vpc_id = aws_vpc.my_vpc.id
cidr_block = cidrsubnet("10.0.0.0/16", 8, count.index)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "my-subnet-${count.index}"
}
}
# Put an instance in each subnet
resource "aws_instance" "foo" {
count = length(var.availability_zones)
ami = ...
instance_type = "t2.micro"
subnet_id = aws_subnet.my_subnet[count.index].id
tags = {
Name = "my-instance-${count.index}"
}
}

How can I pass aws ec2 private ips to template file Using terraform

I am fairly new to terraform. I am trying to create number of ec2 instances and want to run bootstrap script once instance is ready, and in bootstrap script I need to pass private ips of instance created ( part of terraform script).
After so much of googling I came to know that I have to use terraform_file but, not able to use it correctly.
Terraform version: 0.11.13
/* Security group for the instances */
resource "aws_security_group" "test-reporting-sg"
{
name = "${var.environment}-test-reporting-sg"
vpc_id = "${var.vpc_id}"
}
data "template_file" "init"
{
count = "${var.instance_count}"
template = "${file("${path.module}/wrapperscript.sh")}"
vars = {
ip_addresses= "${aws_instance.dev-testreporting.*.private_ip}"
}
}
resource "aws_instance" "dev-testreporting"
{
count = "${var.instance_count}"
ami="${var.ami_id}"
instance_type ="${var.instance_type}"
key_name ="${var.key_name}"
security_groups = [ "${aws_security_group.test-reporting-sg.id}" ]
subnet_id = "${var.subnet_ids}"
user_data = "${data.template_file.init.*.rendered.[count.index]}"
}
Thanks
In resource module you can add private_ip like below
private_ip = "${lookup(var.private_ips,count.index)}"
and add private_ip values to variable file

Resources