cover image
Cloud

Create an EC2 Spot Instance with Terraform

Terraform is an Infrastructure as Code (IaC) tool by HashiCorp. Using it, you can reproducibly create server instances on cloud providers like AWS or Digital Ocean. In this article I show you how to create an AWS EC2 Spot instance server with Terraform.

AWS EC2 Spot instances are EC2 instances available at discount prices. EC2 (Elastic Compute Cloud) is Amazon's term for servers in their data center that you can do basically anything you want with. You can use it to run software. For my purpose, I am using an instance to create a CentOS Linux server for testing purposes. After the testing is done, the EC2 Spot instance is destroyed.

First, you need to sign up to AWS and install the AWS command line tool (CLI).

Terraform is accessing the AWS CLI and using the credentials you have configured there. After installing Terraform, we define a Terraform file that contains the configuration. The file has an .tf file name ending.

The creation of an EC2 instance is a little bit more complicated, because you need to define networking and security rules.

You can find the source code on my GitHub page under: https://github.com/tderflinger/terraform-ec2-spot

VPC

First, you define your VPC (Virtual Private Cloud) network that is a logically isolated subnetwork of the AWS cloud.

resource "aws_vpc" "test-env" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

resource "aws_subnet" "subnet-uno" {
  # creates a subnet
  cidr_block        = "${cidrsubnet(aws_vpc.test-env.cidr_block, 3, 1)}"
  vpc_id            = "${aws_vpc.test-env.id}"
  availability_zone = "us-east-1a"
}

This definition uses the concept of CIDR addresses. The concept of the network addresses is nicely explained in the Digital Ocean article.

The availability zone is us-east-1a, but you can choose the one you prefer. You can look up your availability zone in this AWS article.

Security Groups

After that, you need to define your security groups. They are like a firewall. In this case I allow port 22 (SSH), port 80 (HTTP) and 443 (HTTPS) in. The ingress part is the data inflow. Egress is the outflow and everything can flow out.

resource "aws_security_group" "ingress-ssh-test" {
  name   = "allow-ssh-sg"
  vpc_id = "${aws_vpc.test-env.id}"

  ingress {
    cidr_blocks = [
      "0.0.0.0/0"
    ]

    from_port = 22
    to_port   = 22
    protocol  = "tcp"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "ingress-http-test" {
  name   = "allow-http-sg"
  vpc_id = "${aws_vpc.test-env.id}"

  ingress {
    cidr_blocks = [
      "0.0.0.0/0"
    ]

    from_port = 80
    to_port   = 80
    protocol  = "tcp"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "ingress-https-test" {
  name   = "allow-https-sg"
  vpc_id = "${aws_vpc.test-env.id}"

  ingress {
    cidr_blocks = [
      "0.0.0.0/0"
    ]

    from_port = 443
    to_port   = 443
    protocol  = "tcp"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Internet Gateway

Then you need to define your Elastic IP address, that is the IPv4 IP address where you can reach your server. You also need to declare a couple of resources, like an Internet Gateway that serves as a network address translation (NAT) and routing tables.

resource "aws_eip" "ip-test-env" {
  instance = "${aws_spot_instance_request.test_worker.spot_instance_id}"
  vpc      = true
}

resource "aws_internet_gateway" "test-env-gw" {
  vpc_id = "${aws_vpc.test-env.id}"
}

resource "aws_route_table" "route-table-test-env" {
  vpc_id = "${aws_vpc.test-env.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.test-env-gw.id}"
  }
}

resource "aws_route_table_association" "subnet-association" {
  subnet_id      = "${aws_subnet.subnet-uno.id}"
  route_table_id = "${aws_route_table.route-table-test-env.id}"
}

SSH Access

Next, you will want to securely log in to your server. For that you define the public key with that you can log in. You need to adapt the file path to your public key on your system.

resource "aws_key_pair" "spot_key" {
  key_name   = "spot_key"
  public_key = "${file("/home/xx/.ssh/id_rsa.pub")}"
}

Spot Request

The last piece in the Terraform definition file is the actual spot instance request. There, you have to define the Amazon Machine Image (AMI). The AMI is basically an image file that contains the operating system. In our case it is CentOS 7.4. You can find a list of public AMIs of your region in the AWS console under the EC2 menu.

You also need to define an appropriate instance type. On the AWS page, you can find a list of instance types. The other properties are references to the SSH key, to the security groups and the subnet ID. In the spotprice entry, you specify how much the EC2 spot instance should cost maximally per minute. The blockdurationminutes specifies the time of the duration of the instance. After that time, the instance is automatically destroyed.

resource "aws_spot_instance_request" "test_worker" {
  ami                    = "ami-66a7871c"
  spot_price             = "0.016"
  instance_type          = "t3.small"
  spot_type              = "one-time"
  block_duration_minutes = "120"
  wait_for_fulfillment   = "true"
  key_name               = "spot_key"

  security_groups = ["${aws_security_group.ingress-ssh-test.id}", "${aws_security_group.ingress-http-test.id}",
  "${aws_security_group.ingress-https-test.id}"]
  subnet_id = "${aws_subnet.subnet-uno.id}"
}

Running Terraform

In order to actually apply and create the EC2 spot instance, you need to call Terraform in the directory where you stored the definition file.

Be aware that your AWS user that you configured in your AWS CLI must have the rights to create EC2 instances. Use the AWS IAM console to create the necessary rights.

Use the following two commands to run the creation of the EC2 spot instance:

terraform init
terraform apply

It will ask you the region and whether everything is good to go. After the successful creation, there will be a terraform.tfstate file. In it you can find the public IP address for the instance. Also, in the AWS web interface, you can go to the EC2 section. There you will find your instance, and you also can get the public IP address.

You can then connect with SSH to the public instance like this:

ssh root@ip

When you do not need the instance anymore run the destroy command:

terraform destroy

Conclusion

A server instance on Amazon EC2 is not as easy to set up as on other providers like Digital Ocean. But you have more possibilities to configure. With Terraform, you have the opportunity of an automatic and easily configurable creation of EC2 Spot instances. The mantra of Infrastructure as Code is to be able to automate as much as possible all system administration tasks. Terraform is a vital tool in this respect.

In one of my next articles, I will show you how to use Ansible to provision the just created EC2 spot instance.

Sources and Further Reading

  • Infrastructure as Code: Managing Servers in the Cloud, 2016, Kief Morris, O'Reilly Media

  • Source code of this article: https://github.com/tderflinger/terraform-ec2-spot

  • Article by Hasitha Algewatta: https://medium.com/@hmalgewatta/setting-up-an-aws-ec2-instance-with-ssh-access-using-terraform-c336c812322f

  • Article by Digital Ocean on networking: https://www.digitalocean.com/community/tutorials/understanding-ip-addresses-subnets-and-cidr-notation-for-networking

  • CIDR address calculation tool: https://www.ultratools.com/tools/netMask

  • Article by Alberto Acuna: https://blog.albertoacuna.com/using-terraform-to-create-an-ec2-instance-within-a-public-subnet-in-aws/

Published 11 Jul 2019
Thomas Derflinger

Written by Thomas Derflinger

I am a visionary entrepreneur and software developer. In this blog I mainly write about web programming and related topics like IoT.