How would I go about creating and attaching more than one EBS volume to a single ec2 instance? I'm trying to pass a map with the device block variables:
block_device_mappings = [
{
device_name = "/dev/sdg"
app_disk_type = "gp3"
volume_size = "40"
},
{
device_name = "/dev/sdh"
app_disk_type = "gp3"
volume_size = "45"
}
]
This is my resource attachment:
resource "aws_ebs_volume" "ebs_volume" {
count = length(module.ec2-module.id)
encrypted = "true"
type = var.app_disk_type
kms_key_id = module.datasource-module.data.ebs_kms.arn
availability_zone = module.ec2-module.availability_zone[count.index]
size = var.app_disk_size
}
resource "aws_volume_attachment" "volume_attachment" {
count = length(module.ec2-module.id)
device_name = "/dev/sdg"
volume_id = aws_ebs_volume.ebs_volume[count.index].id
instance_id = module.ec2-module.id[count.index]
}
Related
I have a terraform tfvars file with a map of values that looks like this:
name_map = [
{
name = "devbox"
device_names = ["/dev/xvdg", "/dev/xvdh"]
volume_size = ["900", "200"]
group = "hosts"
instance_type = "m5a.2xlarge"
},
{
name = "devbox2"
device_names = ["/dev/xvdg", "/dev/xvdh"]
volume_size = ["300", "200"]
group = "hosts"
instance_type = "m5a.2xlarge"
}
]
]
My tf file looks like this:
resource "aws_instance" "node" {
count = length(var.name_map)
dynamic "ebs_block_device" {
for_each = [for device in var.name_map.*.device_names[count.index] : {
device_name = device,
volume_size = var.name_map.*.volume_size[count.index]
}]
content {
device_name = ebs_block_device.value.device_name
volume_type = "gp2"
volume_size = ebs_block_device.value.volume_size
delete_on_termination = true
}
}
So basically for the "devbox" instance I'd like "/dev/xvdg" to be 900 gbs, and "/dev/xvdh" to be 200 gbs. I'd like The current setup works to iterate through the device names of each mapping and get a single volume size but I'm trying to expand it to include different volume sizes for each device.
How would I do this?
I've tried a nested for_each statement but I keep getting errors. Would a flatten structure be the solution here? I'd love to see an example of what this would look like.
I think what you want to do is the following:
resource "aws_instance" "node" {
count = length(var.name_map)
instance_type = var.name_map[count.index].instance_type
ami = "..." # Fill in a valid AMI here
dynamic "ebs_block_device" {
for_each = [for i, device in var.name_map[count.index].device_names : {
device_name = device,
volume_size = var.name_map[count.index].volume_size[i]
}]
content {
device_name = ebs_block_device.value.device_name
volume_type = "gp2"
volume_size = ebs_block_device.value.volume_size
delete_on_termination = true
}
}
}
While this works, I suggest you do the following:
variable "name_map" {
default = [
{
name = "devbox"
devices = [
{
device_name = "/dev/xvdg",
volume_size = 900
},
{
device_name = "/dev/xvdh",
volume_size = 200
}
]
group = "hosts"
instance_type = "m5a.2xlarge"
},
{
name = "devbox2"
devices = [
{
device_name = "/dev/xvdg",
volume_size = 900
},
{
device_name = "/dev/xvdh",
volume_size = 200
}
]
group = "hosts"
instance_type = "m5a.2xlarge"
}
]
}
Note, the device_name and the volume_size are grouped together. Now we can use a simple foor loop where we don't have to rely on indexing:
resource "aws_instance" "node" {
count = length(var.name_map)
instance_type = var.name_map[count.index].instance_type
ami = "..." # fill in a valid AMI name
dynamic "ebs_block_device" {
# Notice the i variable (index) was dropped here
for_each = [for device in var.name_map[count.index].devices : {
device_name = device.device_name,
volume_size = device.volume_size
}]
content {
device_name = ebs_block_device.value.device_name
volume_type = "gp2"
volume_size = ebs_block_device.value.volume_size
delete_on_termination = true
}
}
}
I would nest your map further to create something like this:
name_map = [
{
name = "devbox"
root_block_device = {
...settings
}
ebs_block_devices = toSet([
{
name = "/dev/xvdg"
size = "900"
},{
name = "/dev/xvdh"
size = "200"
}
])
group = "hosts"
instance_type = "m5a.2xlarge"
},
...
]
and then in your resource code you can loop over the set for each instance:
resource "aws_instance" "instance" {
count = length(var.name_map)
...
root_block_device {
...settings from var.name_map[count.index].root_block_device
}
dynamic "ebs_block_device" {
for_each = var.name_map[count.index].ebs_block_devices
content {
device_name = ebs_block_device.value.name
volume_size = ebs_block_device.value.size
}
}
}
If you want the root volume to persist post termination I would suggest adding an EBS root volume, otherwise you can ignore the root_block_device and it will create an ephemeral device that contains the image.
I am new to terraform and been trying to figure out how attach a new drive when dealing with ebs_block_device and without tearing down the whole instance.
resource "aws_instance" "test_directory_controller" {
for_each = aws_network_interface.test_directory_controller
ami = local.test_ami_id
key_name = var.test_instance_key_name
instance_type = var.test_instance_type
iam_instance_profile = module.test_manager.test_instance_profile_name
network_interface {
network_interface_id = each.value.id
device_index = 0
}
root_block_device {
volume_size = 120
encrypted = true
}
ebs_block_device {
device_name = "/dev/sdh"
volume_size = 40
encrypted = true
}
Here is the new code that I have added :
resource "aws_ebs_volume" "test_directory_d_drive" {
for_each = aws_network_interface.test_directory_controller
availability_zone = each.key
size = 40
encrypted = true
tags = {
Name = "Local Disk"
DriveLetter = "D"
}
}
resource "aws_volume_attachment" "test_volume_attachment" {
for_each = aws_network_interface.test_directory_controller
device_name = "xvdf"
volume_id = aws_instance.test_directory_controller[each.key].id
instance_id = aws_ebs_volume.test_directory_d_drive[each.key].id
}
The new code above works but according to hashicorp, we can not add aws_volume_attachment and ebs_block. My question here is how do I just add a new ebs_block_device without tearing down the whole instance?
I have instances created in a private subnet using terraform. Each instance in its own AZ. The instances were created using for each. I am now attaching ebs volumes to each of the instances and am running into an error specifying the instances created with for each. Below is the code and variables for the resources and the error.
resource "aws_instance" "private" {
for_each = var.priv_subnet
ami = var.ec2_amis[var.region]
instance_type = each.value.instance_type
key_name = aws_key_pair.main.key_name
subnet_id = aws_subnet.private[each.key].id
vpc_security_group_ids = [
aws_security_group.main_sg.id,
aws_security_group.instance_sg.id
]
tags = {
Name = each.value.tag
}
}
resource "aws_ebs_volume" "partition" {
for_each = var.volumes
availability_zone = each.value.availability_zone
size = each.value.size
tags = {
Name = each.key
}
}
resource "aws_volume_attachment" "ebs_att" {
for_each = aws_ebs_volume.partition
device_name = contains(["Primary", "Worker1", "Worker2"], each.key) ? "/dev/sdf" : "/dev/sdg"
volume_id = each.value.id
instance_id = aws_instance.private.id
}
Variables
variable "volumes" {
type = map(object({
size = string
availability_zone = string
}))
default = {
"Primary" = {
size = "200"
availability_zone = "us-west-2a"
}
"PrimarySecondary" = {
size = "100"
availability_zone = "us-west-2a"
}
"Worker1" = {
size = "200"
availability_zone = "us-west-2b"
}
"Worker1Secondary" = {
size = "100"
availability_zone = "us-west-2b"
}
"Worker2" = {
size = "200"
availability_zone = "us-west-2c"
}
"Worker2Secondary" = {
size = "100"
availability_zone = "us-west-2c"
}
}
}
variable "priv_subnet" {
type = map(object({
instance_type = string
subnet = string
tag = string
}))
default = {
"us-west-2a" = {
instance_type = "m4.2xlarge"
subnet = 4
tag = "Primary"
}
"us-west-2b" = {
instance_type = "m4.4xlarge"
subnet = 5
tag = "Worker1"
}
"us-west-2c" = {
instance_type = "m4.4xlarge"
subnet = 6
tag = "Worker2"
}
}
}
Error
Error: Unsupported attribute
on vpc.tf line 51, in resource "aws_volume_attachment" "ebs_att":
51: instance_id = aws_instance.private[each.value.tag].id
|----------------
| each.value is object with 12 attributes
This object does not have an attribute named "tag".
I had to specify:
instance_id = aws_instance.private[each.value.availability_zone].id
in aws_volume_attachment resource
How would I go about creating and attaching more than one EBS volume to an instance?
The code below works when attaching a single EBS volume. My main concern is creating a map between the size of the EBS volume and the device name. I've tried a variant of things, creating a list, etc. But no luck.
# Create EBS volume
resource "aws_ebs_volume" "ebs_volume" {
count = "${var.ec2_create_volume == true ? var.ec2_instance_count : 0 }"
availability_zone = "${aws_instance.ec2.*.availability_zone[count.index]}"
size = "${var.ec2_ebs_volume_size}"
type = "${var.ec2_ebs_volume_type}"
}
# Attach EBS Volume
resource "aws_volume_attachment" "volume_attachment" {
count = "${var.ec2_create_volume == true ? var.ec2_instance_count : 0 }"
device_name = "${var.ec2_device_name}"
volume_id = "${aws_ebs_volume.ebs_volume.*.id[count.index]}"
instance_id = "${aws_instance.ec2.*.id[count.index]}"
}
You almost there, try using element(list, index) - it will loop over the list. For example, this config will successfully create 2 ec2 instances with 3 additional ebs volumes attached to each:
variable "ec2_device_names" {
default = [
"/dev/sdd",
"/dev/sde",
"/dev/sdf",
]
}
variable "ec2_instance_count" {
default = 2
}
variable "ec2_ebs_volume_count" {
default = 3
}
resource "aws_instance" "ec2" {
count = "${var.ec2_instance_count}"
ami = "${var.aws_ami_id}"
instance_type = "${var.ec2_instance_type}"
}
resource "aws_ebs_volume" "ebs_volume" {
count = "${var.ec2_instance_count * var.ec2_ebs_volume_count}"
availability_zone = "${element(aws_instance.ec2.*.availability_zone, count.index)}"
size = "${var.ec2_ebs_volume_size}"
}
resource "aws_volume_attachment" "volume_attachement" {
count = "${var.ec2_instance_count * var.ec2_ebs_volume_count}"
volume_id = "${aws_ebs_volume.ebs_volume.*.id[count.index]}"
device_name = "${element(var.ec2_device_names, count.index)}"
instance_id = "${element(aws_instance.ec2.*.id, count.index)}"
}
Incase anyone else is looking for the answer. The solution below works for multiple instances across multiple az. Here device_name is list of string so we need to pass as many names as the number of additional volumes and volume_count is the length of list of number additional_volume_size.
resource "aws_ebs_volume" "ebs_volume" {
count = var.instance_count * var.volume_count
availability_zone = aws_instance.ec2[floor(count.index/var.volume_count)].availability_zone
size = var.additional_volume_size[count.index%var.volume_count]
}
resource "aws_volume_attachment" "volume_attachement" {
count = var.instance_count * var.volume_count
volume_id = element(aws_ebs_volume.ebs_volume.*.id, count.index)
device_name = element(var.device_name, count.index)
instance_id = element(aws_instance.ec2.*.id, floor(count.index/var.volume_count))
}
Multiple EC2 instances with multiple EBS volumes of different sizes. This works with odd or even number of volumes.
instance_count = 3
ebs_volume_count = 2
ec2_ebs_volume_size = [10, 15]
ec2_device_names = ["/dev/sdd", "/dev/sde"]
variable "instance_count" {
type = number
default = 1
}
variable "ebs_volume_count" {
type = number
default = 0
}
variable "ec2_ebs_volume_size" {
type = list(any)
default = [
10
]
}
variable "ec2_device_names" {
type = list(any)
default = [
"/dev/sdd"
]
}
variable "availability_zones" {
type = list(any)
}
variable "subnet_ids" {
type = list(any)
}
resource "aws_instance" "ec2_instance" {
count = var.instance_count
ami = var.aws_ami_id
availability_zone = var.availability_zones[count.index]
subnet_id = var.subnet_ids[count.index]
instance_type = var.ec2_instance_type
}
resource "aws_ebs_volume" "ebs_volume" {
count = var.instance_count * var.ebs_volume_count
availability_zone = "${element(aws_instance.ec2_instance.*.availability_zone, floor (count.index/var.ebs_volume_count))}"
size = var.ec2_ebs_volume_size[count.index%var.ebs_volume_count]
}
resource "aws_volume_attachment" "volume_attachement" {
count = var.instance_count * var.ebs_volume_count
volume_id = aws_ebs_volume.ebs_volume.*.id[count.index]
device_name = var.ec2_device_names[count.index%var.ebs_volume_count]
instance_id = "${element(aws_instance.ec2_instance.*.id, floor (count.index/var.ebs_volume_count))}"
}
I read the Terraform spot fleet example usages from here.
What is the significance of "iam_instance_profile_arn" and what does it do in Example 1?
I'm getting the error "launch_specification.0: invalid or unknown key: tags" in some cases, while not in others so I thought maybe it is related to the iam_profile.
iam_instance_profile_arn = "${aws_iam_instance_profile.example.arn}"
Example 1:
# Request a Spot fleet
resource "aws_spot_fleet_request" "cheap_compute" {
iam_fleet_role = "arn:aws:iam::12345678:role/spot-fleet"
spot_price = "0.03"
allocation_strategy = "diversified"
target_capacity = 6
valid_until = "2019-11-04T20:44:20Z"
launch_specification {
instance_type = "m4.10xlarge"
ami = "ami-1234"
spot_price = "2.793"
placement_tenancy = "dedicated"
iam_instance_profile_arn = "${aws_iam_instance_profile.example.arn}"
}
launch_specification {
instance_type = "m4.4xlarge"
ami = "ami-5678"
key_name = "my-key"
spot_price = "1.117"
iam_instance_profile_arn = "${aws_iam_instance_profile.example.arn}"
availability_zone = "us-west-1a"
subnet_id = "subnet-1234"
weighted_capacity = 35
root_block_device {
volume_size = "300"
volume_type = "gp2"
}
tags {
Name = "spot-fleet-example"
}
}
}
Example 2:
resource "aws_spot_fleet_request" "foo" {
iam_fleet_role = "arn:aws:iam::12345678:role/spot-fleet"
spot_price = "0.005"
target_capacity = 2
valid_until = "2019-11-04T20:44:20Z"
launch_specification {
instance_type = "m1.small"
ami = "ami-d06a90b0"
key_name = "my-key"
availability_zone = "us-west-2a"
}
launch_specification {
instance_type = "m3.large"
ami = "ami-d06a90b0"
key_name = "my-key"
availability_zone = "us-west-2a"
}
depends_on = ["aws_iam_policy_attachment.test-attach"]
}
The instance profile is unrelated to the error. The error is saying this:
tags {
Name = "spot-fleet-example"
}
Part of the first example isnt recognized. You can read about what instance profiles are here:
An instance profile is a container for an IAM role that you can use to
pass role information to an EC2 instance when the instance starts.