Categories
Cloud Computing

AWS EC2 instance creation using Terraform

Introduction

Amazon Elastic Cloud Compute (EC2) instances form the backbone of compute resources in AWS. They are virtual machines that can be provisioned in the cloud and utilized to perform compute tasks, run services such as AWS EMR, AWS ECS etc.

Terraform is an infrastructure as code (IAC) tool that lets users define both cloud and on-prem resources in human-readable configuration files that they can version, reuse, and share.

Terraform is comparable to similar tools such as Ansible, Chef and Poppet.

Users on various cloud platforms can create terraform config/template files and call the terraform script to provision cloud resources such as virtual machines, relational databases etc.

One unique feature of Terraform provisioning process is that the process is declarative, rather than imperative and this makes it attractive.

This article will highlight how to provision AWS EC2 instances using Terraform, as well as discuss the benefits of AWS EC2 as the primary computing workhorse in the AWS cloud infrastructure stack.

AWS EC2 advantages

EC2 has many benefits, including:

Reliability – EC2 instances are 99% available and instances can be easily replaced.

Security – EC2 instances are located within a VPC in AWS. The instances are defined within a specific ip range and access is granted via security credentials.

Pricing Flexibility – Can choose from among on demand, spot, reserved and dedicated instances

  • Flexible configuration and infrastucture – users can choose between compute optimized, memory optimized, GPU and based instances.
  • Flexible configuration – operating systems, amount resources (memory/cpu/disk) can be selected for provisioned instances
  • Scalability – compute can be horizontally scaled with instances added or removed depending on the needs of the computation task.
  • Flexible use Instances do ot have to be up and running 24×7 but can be provisioned and run on demand

Getting Started with Terraform

Terraform can be downloaded from here : https://www.terraform.io/

To find out how to install :

Once terraform has been installed, run terraform to verify that it runs ok.

 terraform

Terraform code is written in HCL or Hashicorp Configuration Language.

HCL is declarative, meaning the developer specifies what the end state of the system should be, and Terraform creates the necessary infrastructure to achieve this.

Simple barebones ec2, no networking

For this step we will create a barebones plain vanilla EC2 instance to demonstrate what we can do with Terraform.

When we start using Terraform, we need to let it know what cloud provider we need.

This is specified in the code snippet :

 provider "aws" {
     region = "us-east-2"
 }

We also need to specify the AWS region. Note that here we specify the us-east-2 region rather than us-east-1 The reason for doing this is that we wish our instance to be provisioned in the default VPC. Our account was created before 2013-12-04, and for such accounts there is no default VPC in us-east-1. The reason why we want our instance to be provisioned in a default VPC is explained below.

Next, we declare the resource we would like created via the resource directive:

resource "aws_instance" "terraform-barebones-ec2" {
    ami           = "ami-0aeb7c931a5a61206"
    instance_type = "t3.micro"
    associate_public_ip_address = "false"
    tags = {
       Name = "Terraform-example"
   }
 }

When creating an ec2 instance, we must specify an Amazon Machine Image (AMI). The AMI determines what operating system the EC2 instance will run on. The instance type also needs to be specified, and here we have chosen a free tier type so we don’t incur any costs.

We also specify a tags section where we provide a name for the ec2 instance to easily identify it in the AWS console.

To run this code, we create a subdirectory simple-ec2 and put the code above in a file main.tf

mkdir simple-ec2
cd simple-ec2

To run the code, we first do

terraform init

Output:

This step initializes the directory, identifies the provider needed – AWS and downloads any needed code for provisioning of the instance.

Next, we run the following command:

 terraform plan

This generates the steps that will be run to provision the instance.

We then provision our instance by running

 terraform apply

Why provision in default VPC ? If we were to launch our instance in us-east-1 we would not have access to a default VPC. The consequence of this is that the instance would need to have a VPC as well as other network resources such as subnet created, and Network ACLs and route tables properly configured. Using the default VPC reduces the work the Terraform script needs to do.

Launching barebones ec2 instance without specifying a subnet results in the instance being launched in the default vpc and default subnet. If this subnet has the setting “Auto-assign public ipv4 address” set to Yes, the ec2 instance will have a public ip address assigned.

Even if associate_public_ip_address is set to false, as long as the default subnet has the “Auto-assign public ipv4 address” set to true, the ec2 instance will be created with a public ip address auto-assigned. The only way to “fix” this is to specify a private subnet via the subnet_id setting.

The code can be downloaded here:

https://github.com/sabitech1/dataeng/blob/main/terraform/tutorials/intro-ec2/simple-ec2/main.tf

EC2 instance with webserver

For this example, we will build on the code we used for the previous step, and make some new additions.

The big difference in this step is that our ec2 instance will host a webserver that we can then connect to from our web browser. This necessitates the following changes:

  1. An httpd server needs to be installed on the ec2 instance once it has been provisioned.
  2. The EC2 instance needs to be provisioned with a security group that allows httpd requests via the requisite ports.
  3. The EC2 instance needs to be provisioned with a public IP address so it can be accessed from outside the VPC.

The first change we will make requires a new directive we haven’t seen yet to declare a variable for the web server port:

variable "server_port" {
   description = "The port the server will use for HTTP requests"
   type        = number
   default     = 80
}

The HTTP server will listen on port 80, specified by the “default” attribute.

We can then refer to this variable in other sections of the code as follows:

 ${var.server_port}

We specify the creation of a security group which permits trafiic through the appropriate ports. This is done via the resource directive:

   resource "aws_security_group" "sg-instance" {
     name = "terraform-http-ec2-sg"

   ingress {
      from_port   = var.server_port
      to_port     = var.server_port
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
     }
  }

The ingress section is used to specify the ports allowing traffic into the instance as well as protocol and the allowed IP address range. The cidr_blocks setting can be set to “0.0.0.0/0” – allow traffic from ALL ips, or a range of addresses or a specific IP address e.g. 98.109.133.78/32

So to facilitate step 1 – installation of an http server, we make changes to the resource "aws_instance" directive:

First, we add an attribute specifying the security group we defined by the addition of vpc_security_group_ids setting

We also add a user_data section that will be run once the instance has been provisioned. It will install an httpd busybox server. The code is as follows:

resource "aws_instance" "terraform-http-ec2" {
  ami           = "ami-0aeb7c931a5a61206"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.sg-instance.id]
   
 user_data = <<-EOF
            #!/bin/bash
            echo "Hello, World" > index.html
            nohup busybox httpd -f -p ${var.http_port} &
            EOF
  tags = {
      Name = "Terraform-http-ec2"
    }
   }

user_data basically consists of an inline bash script which uses the busybox set of Unix utilties to run an httpd server. Note that the port is referenced as the variable defined above.

The code can be downloaded here:

https://github.com/sabitech1/dataeng/blob/main/terraform/tutorials/intro-ec2/ec2-webserver/main.tf

EC2 instance with ssh

The main difference between the EC2 instance allowing ssh access vs webserver is that in this instance, the port allowing inbound traffic changes from port 80 (httpd) to 22.

The differences are in the 2 sippets of code below:

  variable "server_port" {
    description = "The port the server will use for HTTP requests"
    type        = number
    default     = 22
  }

Here we specify port 22 instead of 80.

Also, a key pair file needs to be generated and downloaded

  resource "aws_instance" "terraform-ec2-ssh" {
    ami           = "ami-0aeb7c931a5a61206"
    instance_type = "t3.micro"
    vpc_security_group_ids = [aws_security_group.sg-instance.id]
    key_name = "user-ec2"


    tags = {
      Name = "Terraform-ssh-ec2"
    }
    }

Code can be downloaded here :

https://github.com/sabitech1/dataeng/blob/main/terraform/tutorials/intro-ec2/ec2-ssh/main.tf

EC2 instance with both ssh and httpd

In order to spin up an instance with both ssh and httpd capabilities, we just need to specify 2 ports for the ingress block:

 variable "http_port" {
    description = "HTTP server port"
    type        = number
    default     = 80
  }

  variable "ssh_port" {
    description = "SSH port"
    type        = number
    default     = 22
  }


   resource "aws_security_group" "instance" {
      name = "terraform-ssh-http-sg"
    ingress {
      from_port   = var.http_port
      to_port     = var.http_port
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
      from_port   = var.ssh_port
      to_port     = var.ssh_port
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

and ensure that we specify the key pair for ssh and set up httpd in the instance definition:

  resource "aws_instance" "terraform-ssh-http-instance" {
     ami           = var.ami_id     # "ami-0aeb7c931a5a61206"
     instance_type = "t3.micro"
     vpc_security_group_ids = [aws_security_group.sg-instance.id]
     key_name = "user-ec2"
     user_data = <<-EOF
              #!/bin/bash
              echo "Hello, World" > index.html
              nohup busybox httpd -f -p ${var.http_port} &
              EOF

     tags = {
        Name = "Terraform-ssh-http-ec2"
      }
    }

Code can be downloaded here : https://github.com/sabitech1/dataeng/blob/main/terraform/tutorials/intro-ec2/ec2-ssh-webserver/main.tf

Terraform commands

terraform init

  • Initialize working directory containing Terraform configuration files.

terraform plan

  • Creates execution plan that lets user preview changes Terraform will make to your cloud infrastructure.

terraform apply

  • Executes actions to achieve state defined in Hashicorp configuration files.

terraform destroy

  • Destroy all remote objects managed by a particular Terraform configuration.