Spotinst raises $15M Series A Led By Intel Capital and Vertex Ventures Read The Official Press Release

Managing Spot and Reserved Instances with Terraform

Spot and Reserved Instances are key for managing cloud costs but tough to manage. With Spotinst integration, Terraform now makes it easy.

Karan Shetty

For those unfamiliar, Terraform is an Infrastructure as a code tool used for building, changing, and versioning infrastructure safely and efficiently. Basically an open-source, “cloud agnostic CloudFormation”, Terraform can manage existing and popular service providers as well as custom in-house solutions. Terraform uses configuration files to describe the components needed to run a single application or your entire infrastructure. It works by generating an execution plan describing what it will do to reach the desired state and then executes it to build the described infrastructure.

With RIs and Spot Instances as essential elements for lowering your cloud costs, creating templates that properly manage these instances can be difficult. Especially if you’re running RIs across accounts or looking to use Spot Instances for production workloads.

Thankfully, you can use Terraform and Spotinst together to easily manage your RI’s and Spot Instances, making it both simple to take advantage of the savings they have to offer and 100% risk-free (place any production workload without a single point of failure on Spot). All this by just creating and managing a Spotinst Elastigroup resource from within your Terraform template.

How it’s done: managing Spotinst with Terraform

  1. Download the corresponding Spotinst terraform-provider-spotinst binary for your Terraform installation.
  2. Configure Terraform to be able to find the binary file. Also, add environment variables which specify the path to the Terraform binary.
  3. Please make sure you are using the latest Spotinst provider (you can change the version number in the link to suit your Terraform version):

    linux_amd64
    http://spotinst-public.s3.amazonaws.com/integrations/terraform/v0.10.7/linux_amd64/terraform-provider-spotinst
    darwin_amd64

    http://spotinst-public.s3.amazonaws.com/integrations/terraform/v0.10.7/darwin_amd64/terraform-provider-spotinst
    windows_amd64
    http://spotinst-public.s3.amazonaws.com/integrations/terraform/v0.10.7/windows_amd64/terraform-provider-spotinst

  4. Make sure the downloaded binary (terraform-provider-spotinst) has execution permission.

    chmod +x terraform-provider-spotinst
  5. If you are on a Unix-like system, create a file named .terraformrc in your home directory:

    ~/.terraformrc

    If you are on a Windows system, create a file named terraform.rc in the %APPDATA% directory:

    **%APPDATA%/terraform.rc**
  6. Edit the .terraformrc or terraform.rc file and add the following content:
    providers {
       spotinst = "/path/to/terraform-provider-spotinst"
    }
  7. Create a new Terraform template and configure the Spotinst provider (you can find more details here)
    # Configure the Spotinst provider
    provider "spotinst" {
       token   = "${var.spotinst_token}"
       account = "${var.spotinst_account}"
    }
    
    # Create a new Elastigroup
    resource "spotinst_aws_group" "foo" {
       ...
    }
  8. Create a Personal Access Token and get your Spotinst Account ID.

Create a new Spotinst resource on Terraform:

Elastigroup is similar to an AWS Autoscaling group, wherein you can launch Servers with similar configuration and Auto Scaling policies.

Let’s go over the different Elastigroup Configurations (resource called: spotinst_aws_group) on terraform.

  1. Add the Elastigroup name, description and Product
resource "spotinst_aws_group" "elastigroup-name" {
   name        = "elastigroup-name"
   description = "Created by Terraform"
   product     = "Linux/UNIX"

2. Add your Target, Minimum and Maximum number of instances you want in your Elastigroup

capacity {
   target  = "1"
   minimum = "1"
   maximum = "1"
}

3. Define the strategy of the Elastigroup as the percentage of Spot instances that would spin up, in case there are no Spot instances available then to fall back to on-demand instances and to use the Reservations present in your account

strategy {
   risk                       = "100"
   draining_timeout           = "180"
   utilize_reserved_instances = true
   fallback_to_ondemand       = true
}

4. Define the Elastigroup’s On-demand and Spot instance types

instance_types {
   ondemand = "t2.small"
   spot = [
          "m4.large",
          "c3.large",
          "c4.large"
   ]
}

5. Associate the Elastigroup with one or multiple subnets

availability_zone {
   name      = "us-east-1a"
   subnet_id = "subnet-2682f90b"
}

availability_zone {
   name      = "us-east-1c"
   subnet_id = "subnet-09eb9a52"
}

6. Add launch specification to the instances launched by your group

launch_specification {
   monitoring           = false
   image_id             = "ami-32b0b649"
   key_pair             = "test"
   security_group_ids   = ["sg-848954f4"]
   iam_instance_profile  = "test-role"
   user_data            = "IyEvYmluL2Jhc2gKc3VkbyB5dW0gdXBkYXRlIC15"
}

user_data is base64 decoded

7. Tag the instances launched by the group

tags {
   Name      = "test-tf"
   CreatedBy = "Spotinst"
}

8. You can also use our Blue/Green deployment in case you want to deploy a new version after an update to the group configuration

roll_config {
   should_roll           = false
   batch_size_percentage = "25"
   grace_period          = "300"
}

Creating your first Elastigroup (and running your first Spot instances)

To Initialize Terraform run terraform init as shown below:

$ terraform init

Initializing provider plugins...

Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

In your terminal, from the folder where you created the terraform template, run the terraform plan command:

$ terraform plan

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ spotinst_aws_group.elastigroup
     availability_zone.#:                                       "2"
     availability_zone.1024954750.name:                         "us-east-1a"
     availability_zone.1024954750.subnet_id:                    "subnet-2682f90b"
     availability_zone.2410426630.name:                         "us-east-1c"
     availability_zone.2410426630.subnet_id:                    "subnet-09eb9a52"
     capacity.#:                                                "1"
     capacity.3157626798.maximum:                               "1"
     capacity.3157626798.minimum:                               "1"
     capacity.3157626798.target:                                "1"
     capacity.3157626798.unit:                                  "<computed>"
     description:                                               "Testing tf"
     instance_types.#:                                          "1"
     instance_types.2591038051.ondemand:                        "t2.small"
     instance_types.2591038051.spot.#:                          "3"
     instance_types.2591038051.spot.0:                          "m4.large"
     instance_types.2591038051.spot.1:                          "c3.large"
     instance_types.2591038051.spot.2:                          "c4.large"
     launch_specification.#:                                    "1"
     launch_specification.1996551530.ebs_optimized:             "<computed>"
     launch_specification.1996551530.health_check_grace_period: ""
     launch_specification.1996551530.health_check_type:         ""
     launch_specification.1996551530.iam_instance_profile:      "test-role"
     launch_specification.1996551530.iam_role:                  ""
     launch_specification.1996551530.image_id:                  "ami-32b0b649"
     launch_specification.1996551530.key_pair:                  "test2"
     launch_specification.1996551530.load_balancer_names.#:     "0"
     launch_specification.1996551530.monitoring:                "false"
     launch_specification.1996551530.security_group_ids.#:      "1"
     launch_specification.1996551530.security_group_ids.0:      "sg-848954f4"
     launch_specification.1996551530.shutdown_script:           ""
     launch_specification.1996551530.tenancy:                   ""
     launch_specification.1996551530.user_data:                 "fe9b62ddb7c5853c13912f8188b708a88f6f95e9"
     name:                                                      "test-tf"
     product:                                                   "Linux/UNIX"
     roll_config.#:                                             "1"
     roll_config.2383879618.batch_size_percentage:              "25"
     roll_config.2383879618.grace_period:                       "300"
     roll_config.2383879618.health_check_type:                  ""
     roll_config.2383879618.should_roll:                        "false"
     strategy.#:                                                "1"
     strategy.3674874163.availability_vs_cost:                  "<computed>"
     strategy.3674874163.draining_timeout:                      "180"
     strategy.3674874163.fallback_to_ondemand:                  "true"
     strategy.3674874163.ondemand_count:                        ""
     strategy.3674874163.risk:                                  "100"
     strategy.3674874163.spin_up_time:                          "<computed>"
     strategy.3674874163.utilize_reserved_instances:            "true"
     tags.%:                                                    "2"
     tags.CreatedBy:                                            "Spotinst"
     tags.Name:                                                 "test-tf"

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

To create the Elastigroup run terraform apply command:

$ terraform apply

spotinst_aws_group.elastigroup: Creating...
 availability_zone.#:                                       "" => "2"
 availability_zone.1024954750.name:                         "" => "us-east-1a"
 availability_zone.1024954750.subnet_id:                    "" => "subnet-2682f90b"
 availability_zone.2410426630.name:                         "" => "us-east-1c"
 availability_zone.2410426630.subnet_id:                    "" => "subnet-09eb9a52"
 capacity.#:                                                "" => "1"
 capacity.3157626798.maximum:                               "" => "1"
 capacity.3157626798.minimum:                               "" => "1"
 capacity.3157626798.target:                                "" => "1"
 capacity.3157626798.unit:                                  "" => "<computed>"
 description:                                               "" => "Testing tf"
 instance_types.#:                                          "" => "1"
 instance_types.2591038051.ondemand:                        "" => "t2.small"
 instance_types.2591038051.spot.#:                          "" => "3"
 instance_types.2591038051.spot.0:                          "" => "m4.large"
 instance_types.2591038051.spot.1:                          "" => "c3.large"
 instance_types.2591038051.spot.2:                          "" => "c4.large"
 launch_specification.#:                                    "" => "1"
 launch_specification.1996551530.ebs_optimized:             "" => "<computed>"
 launch_specification.1996551530.health_check_grace_period: "" => ""
 launch_specification.1996551530.health_check_type:         "" => ""
 launch_specification.1996551530.iam_instance_profile:      "" => "test-role"
 launch_specification.1996551530.iam_role:                  "" => ""
 launch_specification.1996551530.image_id:                  "" => "ami-32b0b649"
 launch_specification.1996551530.key_pair:                  "" => "test2"
 launch_specification.1996551530.load_balancer_names.#:     "" => "0"
 launch_specification.1996551530.monitoring:                "" => "false"
 launch_specification.1996551530.security_group_ids.#:      "" => "1"
 launch_specification.1996551530.security_group_ids.0:      "" => "sg-848954f4"
 launch_specification.1996551530.shutdown_script:           "" => ""
 launch_specification.1996551530.tenancy:                   "" => ""
 launch_specification.1996551530.user_data:                 "" => "fe9b62ddb7c5853c13912f8188b708a88f6f95e9"
 name:                                                      "" => "test-tf"
 product:                                                   "" => "Linux/UNIX"
 roll_config.#:                                             "" => "1"
 roll_config.2383879618.batch_size_percentage:              "" => "25"
 roll_config.2383879618.grace_period:                       "" => "300"
 roll_config.2383879618.health_check_type:                  "" => ""
 roll_config.2383879618.should_roll:                        "" => "false"
 strategy.#:                                                "" => "1"
 strategy.3674874163.availability_vs_cost:                  "" => "<computed>"
 strategy.3674874163.draining_timeout:                      "" => "180"
 strategy.3674874163.fallback_to_ondemand:                  "" => "true"
 strategy.3674874163.ondemand_count:                        "" => ""
 strategy.3674874163.risk:                                  "" => "100"
 strategy.3674874163.spin_up_time:                          "" => "<computed>"
 strategy.3674874163.utilize_reserved_instances:            "" => "true"
 tags.%:                                                    "" => "2"
 tags.CreatedBy:                                            "" => "Spotinst"
 tags.Name:                                                 "" => "test-tf"

spotinst_aws_group.elastigroup: Creation complete after 8s (ID: sig-747be7a7)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

You can verify the Created Elastigroup in your Spotinst Console.

Elastigroup seamlessly integrates with the reservations present in your AWS account, below is an Elastigroup which has both RI and Spot Instances:

Cleanup

Run terraform plan -destroy to check which resource is being deleted

$ terraform plan -destroy

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

spotinst_aws_group.elastigroup: Refreshing state... (ID: sig-747be7a7)
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

 - spotinst_aws_group.elastigroup

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

Finally to delete: terraform destroy

$ terraform destroy

spotinst_aws_group.elastigroup: Refreshing state... (ID: sig-747be7a7)

The Terraform destroy plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning.
Resources shown in red will be destroyed.

 - spotinst_aws_group.elastigroup

Do you really want to destroy?

 Terraform will delete all your managed infrastructure, as shown above.
 There is no undo. Only 'yes' will be accepted to confirm.
  Enter a value: yes

spotinst_aws_group.elastigroup: Destroying... (ID: sig-747be7a7)
spotinst_aws_group.elastigroup: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.

 

Final Words

If you’re using IaaC to provision servers, it makes your life far easier, but managing costs can still be a challenge. Spot Instances allow you to take advantage of the excess capacity of cloud providers to save around 70-80% on costs. Reserved instances, on the other hand, allow you to reserve an instance for a set period of time, reducing costs by 40-75% depending on the instance. Combining these two options within a single terraform plan helps you to take advantage of this costs savings while you sit back and enjoy 🙂

 

Best,

 

Karan Shetty

Stay current

Sign up for our newsletter, and we'll send you the latest updates on Spotinst, tips, tutorials and more cool stuff!