Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

aws_instance - changing volume_size

I am trying to increase size of my root volume for my ami ami-0d013c5896434b38a - I am using Terraform to provision this.

Just to clarify - I have only one instance. And I want to make sure that if I need to increase the disk space, I don't have to destroy the machine first. Elasticity (EC2) is my reason to believe that it's doable.

Does anyone know whether this is doable? Yes, I could simply do terraform plan and do a dry-run, but just double-checking.

like image 901
ha9u63ar Avatar asked Feb 02 '26 00:02

ha9u63ar


2 Answers

I'm running Terraform 1.0.1 and would like to change my volume_size from 20gb to 30gb.

After run terraform apply

[...]
# aws_instance.typo3_staging_1 will be updated in-place
~ resource "aws_instance" "staging_1" {
    id                          = "i-0eb2f8af6c8ac4125"
    tags                        = {
    "Name" = "Staging 1"
    "Team" = "DevOps"
  }
  # (28 unchanged attributes hidden)

  ~ root_block_device {
      tags                  = {}
    ~ volume_size           = 20 -> 30
    # (8 unchanged attributes hidden)
  }
  # (4 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
[...]

I see that terraform will not destroy the system. Now a simple 'yes' change the volume. After ~33sec the root_block_device has been changed.

A login on the ec2 shows nothing has been changed. df shows the old 20gb size of the root partition. But a simple sudo reboot increased the disk space by 10gb without destoring the current system. All docker containers on that instance runs as expected. Perfect.

My Terraform resource config for such aws_instance is:

resource "aws_instance" "staging_1" {

  instance_type = "t3.medium"
  ebs_optimized = true

  ami                    = "ami-001183208be54f75c"
  key_name               = aws_key_pair.master_key.key_name
  subnet_id              = aws_subnet.web_development_private_a.id
  vpc_security_group_ids = [aws_security_group.ec2_staging.id]

  root_block_device {
    volume_size = 30 # in GB <<----- I increased this!
    volume_type = "gp3"
    encrypted   = true
    kms_key_id  = data.aws_kms_key.customer_master_key.arn
  }

  # This is for T3 only (doesn't apply to M5/R5/...)
  # standard: Baseline of 20% or 30% CPU. Short bursts of 100% CPU are possible, but under a budget. Throttled, if budget is 0.
  # unlimited: Always 100% CPU possible, but costs are higher, if over burst budget.
  credit_specification {
    cpu_credits = "unlimited"
  }

  metadata_options {
    http_endpoint = "enabled"
    http_tokens   = "required"
  }

  lifecycle {
    prevent_destroy = true
  }

  tags = {
    Name = "Staging 1"
    Team = "DevOps"
  }
  volume_tags = {
    Name = "Staging 1"
    Team = "DevOps"
  }
}

like image 145
Lars Avatar answered Feb 04 '26 14:02

Lars


It is doable through the AWS Console or AWS CLI, but not through Terraform, based on a quick test.

Changing the volume_size parameter from 10 to 20 in an aws_instance definition such as the one below caused a destroy/re-create of the instance. Using Terraform 0.15.0.

If you need to keep managing the instance with Terraform, consider the option of (1) performing the modification outside of Terraform (aws console or CLI) and (2) importing the modified resource back into Terraform.

In the second section of the answer I describe a simple example of re-importing into Terraform the state of the aws_instance modified through the console.

Disclaimer: do this at your own risk and after suitable testing in a non-production environment. Read carefully the warnings in the documentation for the terraform import command

Testing EBS modification done in Terraform - requires instance replacement

resource "aws_instance" "testebs" {
  availability_zone           = local.aznames[0]
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = "t2.micro"
  associate_public_ip_address = true
  key_name                    = "zzzzzzzz"
  ebs_block_device {
    device_name = "/dev/sda1"
    volume_size = 20
  }
}

I paste below the full config and the output of terraform plan. The ami is a recent Ubuntu 20.04 for eu-west-1, not the one in the original question.

File ebstest.tf

terraform {
  required_version = "~> 0.15.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region  = "eu-west-1"
  profile = "xxxxxxx"
}

data "aws_ami" "ubuntu" {
  most_recent = true
  filter {
    name = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }
  owners = ["099720109477"] # Canonical
}

data "aws_availability_zones" "available" {
  state = "available"
}
locals {
  aznames = data.aws_availability_zones.available.names
}

resource "aws_instance" "testebs" {
  availability_zone           = local.aznames[0]
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = "t2.micro"
  associate_public_ip_address = true
  key_name                    = "zzzzzzzz"
  ebs_block_device {
    device_name = "/dev/sda1"
    volume_size = 20
  }
  tags = {
    Name = "testebs-${local.aznames[0]}"
  }
}

Output of terraform plan:

$ terraform plan
aws_instance.testebs: Refreshing state... [id=i-0e1fededb2e432a98]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_instance.testebs must be replaced
-/+ resource "aws_instance" "testebs" {
      ~ arn                          = "arn:aws:ec2:eu-west-1:xxxxxxxxxxxxx:instance/i-0e1fededb2e432a98" -> (known after apply)
      ~ cpu_core_count               = 1 -> (known after apply)
      ~ cpu_threads_per_core         = 1 -> (known after apply)
      - disable_api_termination      = false -> null
      - ebs_optimized                = false -> null
      - hibernation                  = false -> null
      + host_id                      = (known after apply)
      ~ id                           = "i-0e1fededb2e432a98" -> (known after apply)
      ~ instance_state               = "running" -> (known after apply)
      ~ ipv6_address_count           = 0 -> (known after apply)
      ~ ipv6_addresses               = [] -> (known after apply)
      - monitoring                   = false -> null
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      ~ primary_network_interface_id = "eni-0a923724fec1e76ce" -> (known after apply)
      ~ private_dns                  = "ip-172-31-13-57.eu-west-1.compute.internal" -> (known after apply)
      ~ private_ip                   = "172.31.13.57" -> (known after apply)
      ~ public_dns                   = "ec2-3-250-102-86.eu-west-1.compute.amazonaws.com" -> (known after apply)
      ~ public_ip                    = "3.250.102.86" -> (known after apply)
      ~ secondary_private_ips        = [] -> (known after apply)
      ~ security_groups              = [
          - "default",
        ] -> (known after apply)
      ~ subnet_id                    = "subnet-192e767f" -> (known after apply)
        tags                         = {
            "Name" = "testebs-eu-west-1a"
        }
      ~ tenancy                      = "default" -> (known after apply)
      ~ vpc_security_group_ids       = [
          - "sg-d7dc5a9a",
        ] -> (known after apply)
        # (7 unchanged attributes hidden)

      - credit_specification {
          - cpu_credits = "standard" -> null
        }

      + ebs_block_device { # forces replacement
          + delete_on_termination = true
          + device_name           = "/dev/sda1"
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = 20
          + volume_type           = (known after apply)
        }
      - ebs_block_device { # forces replacement
          - delete_on_termination = true -> null
          - device_name           = "/dev/sda1" -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - snapshot_id           = "snap-0f4b18aebb4264157" -> null
          - tags                  = {} -> null
          - throughput            = 0 -> null
          - volume_id             = "vol-01eade74ebeba666f" -> null
          - volume_size           = 10 -> null
          - volume_type           = "gp2" -> null
        }

      ~ enclave_options {
          ~ enabled = false -> (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      ~ metadata_options {
          ~ http_endpoint               = "enabled" -> (known after apply)
          ~ http_put_response_hop_limit = 1 -> (known after apply)
          ~ http_tokens                 = "optional" -> (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      ~ root_block_device {
          ~ delete_on_termination = true -> (known after apply)
          ~ device_name           = "/dev/sda1" -> (known after apply)
          ~ encrypted             = false -> (known after apply)
          ~ iops                  = 100 -> (known after apply)
          + kms_key_id            = (known after apply)
          ~ tags                  = {} -> (known after apply)
          ~ throughput            = 0 -> (known after apply)
          ~ volume_id             = "vol-01eade74ebeba666f" -> (known after apply)
          ~ volume_size           = 10 -> (known after apply)
          ~ volume_type           = "gp2" -> (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Testing modification through AWS Console and importing resource from terraform (see disclaimer/warning above).

  1. Create instance with 10G volume as per the previous part of the answer.
  2. Modify EBS volume size to 20G in AWS Console. (did not extend the volume at OS level -- exercise for the reader :D)
  3. List Terraform items in the state file and select aws_instance resource for which the state will be removed
$ terraform state list
data.aws_ami.ubuntu
data.aws_availability_zones.available
aws_instance.testebs
  1. Remove state (terraform state rm) for the aws_instance resource.
$ terraform state rm aws_instance.testebs
Removed aws_instance.testebs
Successfully removed 1 resource instance(s).
  1. Using terraform import, import the aws_resource using the instance-id of the modified EC2 instance.
$ terraform import aws_instance.testebs  i-xxxxxxxxxxxxxxxx
aws_instance.testebs: Importing from ID "i-xxxxxxxxxxxxxxxx"...
aws_instance.testebs: Import prepared!
  Prepared aws_instance for import
aws_instance.testebs: Refreshing state... [id=i-xxxxxxxxxxxxxxxx]

Import successful!

The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform.
  1. Verify that terraform can manage the imported instance properly (modify instance through terraform and verify behavior)
like image 22
RafaP Avatar answered Feb 04 '26 14:02

RafaP