I have created a AWS instance list using Terraform:
resource "aws_instance" "masters" {
count = 2
ami = "${var.aws_centos_ami}"
instance_type = "t2.micro"
availability_zone = "eu-west-1b"
tags {
Name = "master-${count.index}"
}
}
How can I assign volumes to that instances like in a loop?
I just trying using the next:
data "aws_ebs_volume" "masters_ebs_volume" {
most_recent = true
filter {
name = "attachment.instance-id"
values = ["${aws_instance.masters.*.id}"]
}
}
But I don't thing it is working fine, because when I try to write the AWS volumes in a file, it writes only one volume name.
provisioner "local-exec" {
command = "echo \"${join("\n", data.aws_ebs_volume.masters_ebs_volume.*.id)}\" >> volumes"
}
I have tried defining the volume like this:
data "aws_ebs_volume" "masters_ebs_volume" {
count = 2
# most_recent = true
filter {
name = "attachment.instance-id"
values = ["${aws_instance.masters.*.id}"]
}
}
But it throws the next error:
data.aws_ebs_volume.masters_ebs_volume.0: Your query returned more than one result. Please try a more specific search criteria, or set `most_recent` attribute to true.
You'll need to tell it which instance specifically maps to which volume. You can do that with element():
data "aws_ebs_volume" "masters_ebs_volume" {
count = 2
filter {
name = "attachment.instance-id"
values = ["${element(aws_instance.masters.*.id, count.index)}"]
}
}
Related
Trying to lunch 1 or 2 instances based on a condition ( "single-target" )
ERROR im getting :
132: target_id = var.ec2-2
var.ec2-2 is empty tuple
Inappropriate value for attribute "target_id": string required.
the use of that variable :
resource "aws_lb_target_group_attachment" "b" {
target_id = var.ec2-2
}
outputs :
output "ec2-2_ot" {
value = aws_instance.ec2V2[*].id
description = "the value of the network module ec2-2 id"
}
variable :
variable "single-target" {
type=bool
default=false
}
main.tf :
module "network_module" {
ec2-2 = module.compute_module.ec2-2_ot
}
ec2 launch :
resource "aws_instance" "ec2V2" {
count = var.single-target ? 0 : 1
ami = var.ec2_ami
instance_type = var.ec2_type
associate_public_ip_address = true
key_name = var.key_pair
vpc_security_group_ids = [ var.vpc-sg ]
subnet_id = var.subnet-2
user_data = file("PATH/user-data.sh")
tags = {
Name = var.ec2-2_name
}
}
As the count has been already set to the resource you are trying to create, the returning result list would need to be accessed via Splat Expression mentioned in this terraform documentation.
If you need to choose specific index from the list of the result , then you can use the element function to do so
So in your case, if you need to output EC2 id it would be as follows.
output "thisisoutput" {
value = aws_instance.ec2V2[*].arn
}
I relay stack with that simple question.
Assume i need create few instance resources so how can i iterate from from tf variables to gather all private ips and pass it to ansible inventory file.
As i found i have to use * like here:
k8s_master_name = "${join("\n", azurerm_virtual_machine.k8s-master.*.name)}"
But i as think for me it will look like:
inst_ip = "${join("\n", ${aws_instance.*.private_ip})}"
But i got error:
Error: Invalid reference
on crc.cloud.connect.tf line 72, in resource "local_file" "servers1":
72: inst_ip = "${join("\n", aws_instance.*.private_ip)}"
A reference to a resource type must be followed by at least one attribute
access, specifying the resource name.
Full tf file:
resource "aws_instance" "sp-1" {
ami = "cmi-993E674A"
instance_type = "c5.large"
monitoring = true
source_dest_check = false
user_data = file("user_data.sh")
subnet_id = "subnet-F6C45280"
private_ip = "172.31.16.18"
vpc_security_group_ids = ["sg-230C7615"]
key_name = "mmk-key"
#network_interface {
# network_interface_id = "${aws_network_interface.ni-sp-1.id}"
# device_index = 0
#}
tags = {
desc = "sp-1"
group_name = "sp"
}
}
resource "aws_instance" "sp-2" {
ami = "cmi-993E674A"
instance_type = "c5.large"
monitoring = true
source_dest_check = false
user_data = file("user_data.sh")
subnet_id = "subnet-F6C45280"
private_ip = "172.31.16.19"
vpc_security_group_ids = ["sg-230C7615"]
key_name = "mmk-key"
tags = {
desc = "sp-2"
group_name = "sp"
}
}
resource "local_file" "servers1" {
content = templatefile("${path.module}/templates/servers1.tpl",
{
inst_ip = "${join("\n", ${aws_instance.*.private_ip})}"
}
)
filename = "../ansible/inventory/servers1"
}
Per the Terraform documentation, you need to reference the resource type and its associated name.
In your configuration file, you have an aws_instance resource with the name sp-1. If you wish to access the private_ip attribute of the resource, you need to do it like so: aws_instance.sp-1[*].private_ip.
You are creating a single instance aws_instance.sp-1, not multiple instances. To create multiple instance you would need to use count or for_each, or provision instances through aws_autoscaling_group.
Therefore, to access private_ip you don't really need splat * and join in your case (but still can use them if you want) as you have only one instance and will have only one private_ip. The following should be enough instead:
inst_ip = aws_instance.sp-1.private_ip
I would like to create auto scaling group in Terraform and get the spot price through a data and create the launch template with the updated spot price, for example:
resource "aws_launch_template" "launch_cfg_spot" {
count = length(var.pricing)
name_prefix = "launch_cfg_spot_${count.index}"
instance_type = var.pricing[count.index].InstanceType
image_id = "ami-0ff8a91507f77f867"
instance_market_options {
market_type = "spot"
spot_options {
max_price = var.pricing[count.index].price
}
}
network_interfaces{
subnet_id = var.subnets[var.pricing[count.index].az]
}
}
I have implemented it with an external script for now using the describe_spot_price_history command in boto3 but I know for sure that there is a way to get the price through Terraform
Since terraform aws provider 3.1.0 got released, there is a data source called "aws_ec2_spot_price". I use construction based on desired subnet (spot prices are different from one availability zone to another), but you certainly can adjust it up to your needs. I also add two more percent to prevent an instance from termination due to price volatility:
data "aws_subnet" "selected" {
id = var.subnet_id
}
data "aws_ec2_spot_price" "current" {
instance_type = var.instance_type
availability_zone = data.aws_subnet.selected.availability_zone
filter {
name = "product-description"
values = ["Linux/UNIX"]
}
}
locals {
spot_price = data.aws_ec2_spot_price.current.spot_price + data.aws_ec2_spot_price.current.spot_price * 0.02
common_tags = {
ManagedBy = "terraform"
}
}
So the following data source will fetch the latest AMI with component:web tagged. Let's say I have a handful of components with their own AMI. Instead of creating this same block 5 times, is there a way to make this more dynamic where I can pass in the value of component? I can't seem to think of a unique value I can pass in. Do I need to refactor my code a bit?
data "aws_ami" "web" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:component"
values = ["web"]
}
most_recent = true
}
I have a defaults module that acts as a metadata lookup where it fetches and outputs basic things like AMI ID and VPC IDs.
Default Module
# defaults/main.tf
data "aws_ami" "web" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:component"
values = ["web"]
}
most_recent = true
}
output "web_ami" {
value = "${data.aws_ami.web.id}"
}
Main code
# service_name/main.tf
module "defaults" {
source = "../defaults"
region = "${var.region}"
environment = "${var.environment}"
}
module "ftpserver" {
source . = "../ec2_instance"
ami_id = "${module.defaults.web_ami}"
...
}
I'd move the aws_ami data source into the module and have it look up the AMI directly rather than have it passed in from outside.
So I would change the ec2_instance module to look like:
variable "ami_component" {}
data "aws_ami" "selected" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:component"
values = ["${var.ami_component"]
}
most_recent = true
}
resource "aws_instance" "instance" {
ami = "${data.aws_ami.selected.id}"
instance_type = "t2.micro"
tags {
Name = "HelloWorld"
}
}
If you then felt like you needed to be able to override the AMI in the ec2_instance module you could change that to instead be:
variable "ami_component" {}
variable "override_ami" {
default = ""
}
data "aws_ami" "selected" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:component"
values = ["${var.ami_component"]
}
most_recent = true
}
resource "aws_instance" "instance" {
ami = "${var.override_ami != "" ? var.override_ami : data.aws_ami.selected.id}"
instance_type = "t2.micro"
tags {
Name = "HelloWorld"
}
}
This uses a conditional to check if the override_ami variable has been set to something else in which case it will use that, otherwise it will use the ami_component variable to look up the appropriate AMI and use that instead.
This has the benefit of moving the AMI selection logic into the Terraform module making the interface to that module much simpler.
I create instances with a default CentOS 7 AMI. This AMI creates automatically a volume and attached to the instance. Is it possible to read thats volume ID using terraform? I create the instance using the next code:
resource "aws_instance" "DCOS-master3" {
ami = "${var.aws_centos_ami}"
availability_zone = "eu-west-1b"
instance_type = "t2.medium"
key_name = "${var.aws_key_name}"
security_groups = ["${aws_security_group.bastion.id}"]
associate_public_ip_address = true
private_ip = "10.0.0.13"
source_dest_check = false
subnet_id = "${aws_subnet.eu-west-1b-public.id}"
tags {
Name = "master3"
}
}
You won't be able to extract EBS details from aws_instance since it's AWS side that provides an EBS volume to the resource.
But you can define a EBS data source with some filter.
data "aws_ebs_volume" "ebs_volume" {
most_recent = true
filter {
name = "attachment.instance-id"
values = ["${aws_instance.DCOS-master3.id}"]
}
}
output "ebs_volume_id" {
value = "${data.aws_ebs_volume.ebs_volume.id}"
}
You can refer EBS filters here:
http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-volumes.html
You can: aws_instance.DCOS-master3.root_block_device.0.volume_id
As described in Terraform docs:
For any root_block_device and ebs_block_device the volume_id is exported. e.g. aws_instance.web.root_block_device.0.volume_id
output "volume-id-C" {
description = "root volume-id"
#get the root volume id form the instance
value = element(tolist(data.aws_instance.DCOS-master3.root_block_device.*.volume_id),0)
}
output "volume-id-D" {
description = "ebs-volume-id"
#get the 1st esb volume id form the instance
value = element(tolist(data.aws_instance.DCOS-master3.ebs_block_device.*.volume_id),0)
}
You can get the volume name of an aws_instance like this:
output "instance" {
value = aws_instance.ec2_instance.volume_tags["Name"]
}
And you can set it as follows:
resource "aws_instance" "ec2_instance" {
ami = var.instance_ami
instance_type = var.instance_type
key_name = var.instance_key
...
tags = {
Name = "${var.server_name}_${var.instance_name[count.index]}"
}
volume_tags = {
Name = "local_${var.instance_name[count.index]}"
}
}