Search code examples
amazon-web-servicesamazon-ec2foreachterraformdevops

Attaching multiple ebs volumes to each ec2 instance using Terraform for each


I have this terraform code where im trying to create 4 ec2 instances which has different configurations for each ec2

resource "aws_instance" "ec2" {
  for_each = var.instances

  ami                         = each.value.ami
  instance_type               = each.value.instance_type
  key_name                    = each.value.key_name
  user_data                   = file("${path.module}/templates/${coalesce(each.value.userdata, "user_data")}.sh")
  associate_public_ip_address = false
  subnet_id                   = var.private_subnet_id
  availability_zone           = var.az


  root_block_device {
    volume_size           = each.value.volume_size
    delete_on_termination = true
    encrypted             = each.value.enable_volume_encryption

    tags = merge(var.tags, {
      Name = "${each.key}-vol"
    })
  }
  dynamic "ebs_block_device" {
    for_each = each.value.block_devices
    content {
      volume_type           = ebs_block_device.value.volume_type
      volume_size           = ebs_block_device.value.volume_size
      delete_on_termination = false
      tags                  = ebs_block_device.value.tags
      device_name           = ebs_block_device.value.device_name
    }
  }
  tags = merge(var.tags, {
    Name = "${each.key}"
  })
}

and iam passing the values as

instances = {
  instance01 = {
    ami                      = "ami-0da7657fe73215c0c"
    key_name                 = "dev-key"
    instance_type            = "t3a.medium"
    volume_size              = 16
    enable_volume_encryption = false
    userdata                 = "instance01"
    
    block_devices = [
      {
        device_name = "/dev/sdf"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "instance01-vol-1"
        }
      },
      {
        device_name = "/dev/sdp"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "instance01-vol-2"
        }
      }
    ]
  },
  instance02 = {
    ami                      = "ami-0da7657fe73215c0c"
    key_name                 = "dev-key"
    instance_type            = "t3a.medium"
    volume_size              = 16
    enable_volume_encryption = false
    userdata                 = "instance02"
 
    block_devices = [
      {
        device_name = "/dev/sdf"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "instance02-vol-1"
        }
      },
      {
        device_name = "/dev/sdp"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "instance02-vol-2"
        }
      }
    ]
  },
  node01 = {
    ami                      = "ami-0da7657fe73215c0c"
    key_name                 = "dev-key"
    instance_type            = "t3a.medium"
    volume_size              = 16
    enable_volume_encryption = false
    userdata                 = "node01"
    block_devices = [
      {
        device_name = "/dev/sdf"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "node01-vol-1"

        }
      }
    ]
  },
  node02 = {
    ami                      = "ami-0da7657fe73215c0c"
    key_name                 = "dev-key"
    instance_type            = "t3a.medium"
    volume_size              = 16
    enable_volume_encryption = false
    userdata                 = "instance04"
    block_devices = [
      {
        device_name = "/dev/sdp"
        volume_size = 16
        volume_type = "gp2"
        tags = {
          Name       = "node02-vol-1"
        }
      }
    ]
  }
}

the problem with this code is when i tried to change the ebs volume size, the changes are not automatically detected by Terraform, and terraform document says

NOTE: Currently, changes to the ebs_block_device configuration of existing resources cannot be automatically detected by Terraform. To manage changes and attachments of an EBS block to an instance, use the aws_ebs_volume and aws_volume_attachment resources instead. If you use ebs_block_device on an aws_instance, Terraform will assume management over the full set of non-root EBS block devices for the instance, treating additional block devices as drift. For this reason, ebs_block_device cannot be mixed with external aws_ebs_volume and aws_volume_attachment resources for a given instance.

How should i use these "aws_ebs_volume" and "aws_volume_attachment" resources for above code without using "dynamic ebs_block_device".

Can someone please help me here?

Tried using them but it creates only one additional volume for each ec2

resource "aws_ebs_volume" "additional_volumes" {
   for_each = var.instances

  size              = each.value.additional_volume_size
   type              = each.value.additional_volume_type
   availability_zone = var.az
   encrypted         = each.value.encrypted

   tags = {
     Name = "${each.key}"
  }
 }
 resource "aws_volume_attachment" "ec2_attachment" {
   for_each    = var.instances
   device_name = each.value.device_name
   instance_id = aws_instance.example[each.key].id
   volume_id   = aws_ebs_volume.additional_volumes[each.key].id
 }

Solution

  • You can try something like the following, "flattening" the device list to create. In this way, you create a list of the devices to create, adding a reference to the instance.

    In your module, add a locals.tf file with the content:

    locals {
      ebs_volumes = flatten([
        for k, v in var.instances : [
          for device, device in v.block_devices : {
            device_key   = "${k}-${device.tags.Name}"
            instance_key = k
            ebs_device   = device
          }
        ]
      ])
    }
    
    

    Then, in the main file, remove the dynamic block from ec2 instance and iterate the list of the EBS resources to create:

    resource "aws_ebs_volume" "volume" {
      for_each = { for elem in local.ebs_volumes : elem.device_key => elem }
    
      availability_zone = var.az
      size              = each.value.ebs_device.volume_size
      tags              = each.value.ebs_device.tags
    }
    
    resource "aws_volume_attachment" "ebs_att" {
      for_each = { for elem in local.ebs_volumes : elem.device_key => elem }
    
      device_name = each.value.ebs_device.device_name
      volume_id   = aws_ebs_volume.volume[each.value.device_key].id
      instance_id = aws_instance.ec2[each.value.instance_key].id
    }
    

    The plan shows something like the following. Note the key of each resource created.

    # aws_ebs_volume.volume["instance01-instance01-vol-1"] will be created
      + resource "aws_ebs_volume" "volume" {
          + arn               = (known after apply)
          + availability_zone = "us-east-2a"
          + encrypted         = (known after apply)
          + final_snapshot    = false
          + id                = (known after apply)
          + iops              = (known after apply)
          + kms_key_id        = (known after apply)
          + size              = 16
          + snapshot_id       = (known after apply)
          + tags              = {
              + "Name" = "instance01-vol-1"
            }
          + tags_all          = {
              + "Name" = "instance01-vol-1"
            }
          + throughput        = (known after apply)
          + type              = (known after apply)
        }
    
      # aws_ebs_volume.volume["instance01-instance01-vol-2"] will be created
      + resource "aws_ebs_volume" "volume" {
          + arn               = (known after apply)
          + availability_zone = "us-east-2a"
          + encrypted         = (known after apply)
          + final_snapshot    = false
          + id                = (known after apply)
          + iops              = (known after apply)
          + kms_key_id        = (known after apply)
          + size              = 16
          + snapshot_id       = (known after apply)
          + tags              = {
              + "Name" = "instance01-vol-2"
            }
          + tags_all          = {
              + "Name" = "instance01-vol-2"
            }
          + throughput        = (known after apply)
          + type              = (known after apply)
        }
    
      # aws_ebs_volume.volume["instance02-instance02-vol-1"] will be created
      + resource "aws_ebs_volume" "volume" {
          + arn               = (known after apply)
          + availability_zone = "us-east-2a"
          + encrypted         = (known after apply)
          + final_snapshot    = false
          + id                = (known after apply)
          + iops              = (known after apply)
          + kms_key_id        = (known after apply)
          + size              = 16
          + snapshot_id       = (known after apply)
          + tags              = {
              + "Name" = "instance02-vol-1"
            }
          + tags_all          = {
              + "Name" = "instance02-vol-1"
            }
          + throughput        = (known after apply)
          + type              = (known after apply)
        }
    
    [...]
    
    # aws_volume_attachment.ebs_att["instance01-instance01-vol-1"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdf"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # aws_volume_attachment.ebs_att["instance01-instance01-vol-2"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdp"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # aws_volume_attachment.ebs_att["instance02-instance02-vol-1"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdf"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # aws_volume_attachment.ebs_att["instance02-instance02-vol-2"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdp"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # aws_volume_attachment.ebs_att["instance03-instance03-vol-1"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdf"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }
    
      # aws_volume_attachment.ebs_att["instance04-instance04-vol-1"] will be created
      + resource "aws_volume_attachment" "ebs_att" {
          + device_name = "/dev/sdp"
          + id          = (known after apply)
          + instance_id = (known after apply)
          + volume_id   = (known after apply)
        }