Terraform - Getting Started

An intro to Terraform with AWS

What is Terraform?

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. It has an open-source infrastructure and a code software tool that provides a consistent CLI workflow to manage hundreds of cloud services. Basically, if you want to manage the entire infrastructure using CLI and have efficient rollbacks during errors, Terraform proves to be an amazing tool. Read the official docs here

Advantages of using Terraform:

  • It works with a great number of latest and mostly used cloud providers.
  • It is very effective in managing the infrastructure directly from the CLI.
  • It gives a deep understanding of how the deployment is working.
  • It is smart enough to deal with failover, updation and deletion.

Alternatives for Terraform:

There are other great alternatives to Terraform like:

  • AWS CloudFormation
  • Serverless
  • Pulumi
  • Ansible

Some important terms to know before we start with Terraform:

  • Provider : Providers are a logical abstraction of an upstream API. They are responsible for understanding API interactions and exposing resources. A list of provider are available here.

  • Resource : Resources are the most important element in the Terraform language. Each resource block describes one or more infrastructure objects in the cloud provider.

  • Output : Terraform output values allow you to export structured data about your resources.Outputs are also necessary to share data from a child module to your root module.

  • Module : A module allows you to group resources together and reuse this group later, possibly many times.

So, this article will help you get started with Terraform which will help in manage AWS services. Follow the docs for installation of Terraform. You can also do this by using Homebrew as shown below:

brew install terraform
terraform -v ##checks the version

Implementation

Now, here we will try to launch an EC2 instance and carry out the entire process using terraform. A prerequisite is that you have an AWS account setup ,know how to generate access keys and its configuration on local system. Let's start off:

  • Create a main.tf file and update its contents as follows while also defining the provider and specified account access as shown:
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region                  = "us-east-1"
  shared_credentials_file = "/Users/adityaprakash/.aws/credentials"
  profile                 = "default"
}

Never add hard coded values for the access key and credentials. Always try to use their reference.

  • Next, its is important to do some research before proceeding to set up the instance as shown below:
# VPC
resource "aws_vpc" "prod" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "prod"
  }
}


# Internet Gateway
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.prod.id
}


# Custom route table
resource "aws_route_table" "prod-route-table" {
  vpc_id = aws_vpc.prod.id

  route {
    cidr_block = "0.0.0.0/0" #send IPv4 traffic wherever this route points
    gateway_id = aws_internet_gateway.gw.id
  }

  route {
    ipv6_cidr_block        = "::/0"
    gateway_id             = aws_internet_gateway.gw.id
  }

  tags = {
    Name = "prod"
  }
}


# Create subnet
resource "aws_subnet" "subnet-1" {
  vpc_id = aws_vpc.prod.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "prod-subnet"
  }
}


# Associate Subnet with Route Table
resource "aws_route_table_association" "a" {
  subnet_id      = aws_subnet.subnet-1.id
  route_table_id = aws_route_table.prod-route-table.id
}

The general syntax for defining a resource is aws_resource name you want to specify for the resource {.......} . Meanwhile, the syntax for referring an earlier defined resource is referencing_resource = referred_resource . name . id

  • In the next step, let's create a VPC (aws_vpc) wherein the entire workflow will take place. Then, we define an Internet Gateway(aws_internet_gateway) that will provide an internet connectivity to the EC2 instance. Next, lets define a subnet(aws_subnet) and associate it to a route table(aws_route_table) that determines where network traffic from your subnet or gateway is directed. To associate, we use the (aws_route_table_association) resource.
  • After that, let's setup the security group, network interface and elastic IP to be attached to the instance. Here, we have to define the security groups in order to define the inbound and outbound traffic. Next, define the required network interface and create an Elastic IP (static and allow will be reachable from the internet) to be attached to the instance. Look how it's done below:
# Network Interface
resource "aws_network_interface" "nic" {
  subnet_id       = aws_subnet.subnet-1.id
  private_ips     = ["10.0.1.50"]
  security_groups = [aws_security_group.allow_web.id]
}

# Attach Elastic IP to n/w interface -------Req. deployment of IGW first as well as the EC2 instance being setup first.
resource "aws_eip" "one" {
  vpc                       = true #wheteher in VPC or not
  network_interface         = aws_network_interface.nic.id
  associate_with_private_ip = "10.0.1.50"
  depends_on = [aws_internet_gateway.gw, aws_instance.testIntance]
}

Note that here we have used the depends_on to ensure that the Elastic IP to be created after the Internet gateway and EC2 instance have been setup.

  • Let's go on to code the creation of the EC2 instance. Here, we create the script that downloads Apache on the instance and creates an index.html file to be rendered when the URL of instance is hit. Learn how this is done by following the code below:
# EC2 Instance
resource "aws_instance" "testIntance" {
  ami = "ami-02fe94dee086c0c37"
  instance_type = "t2.micro"
  availability_zone = "us-east-1a"
  key_name = "terraform"

  network_interface {
    device_index = 0
    network_interface_id = aws_network_interface.nic.id
  }

  user_data = << - EOF
                #!/bin/bash
                sudo apt update -y
                sudo apt install apache2 -y
                sudo systemctl start apache2
                sudo bash -c 'echo first web server > /var/www/html/index.html'
                EOF
    tags = {
      Name = "test-instance"
    }
}

It is important toensure that you the correct ami-id of the instance you want to setup to AWS to the ami-ids of the instances.

Now, let's run the following commands to check the progress:

  • terraform init to initialise the the backend and download all the required plugins for the provider.
  • terraform plan will show you all the resources that will be configured and setup in your AWS account but will not deploy them.
  • terraform apply to deploy all the resources.
  • If everything goes fine, you will get the following output: applyec2.png ec2.png
  • Finally, if you want to destroy all the resources created (Be careful here☠️), use terraform destroy

Discussing AWS without a serverless implementation feels incomplete. So, now let us deploy a basic Lambda function using Terraform as shown:

Let's look at how a hello_lambda.py file looks like:

import os

def lambda_handler(event, context):
    return "{} from Lambda!".format(os.environ['greeting'])

Next, let's how a main.tf file looks like:

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

# Configure the AWS Provider
provider "aws" {
  region                  = "us-east-1"
  shared_credentials_file = "/Users/adityaprakash/.aws/credentials"
  profile                 = "default"
}


provider "archive" {}

data "archive_file" "zip" {
  type        = "zip"
  source_file = "hello_lambda.py"
  output_path = "hello_lambda.zip"
}


resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}



resource "aws_lambda_function" "lambda" {
  function_name = "hello_lambda"

  filename         = "${data.archive_file.zip.output_path}"
  source_code_hash = "${data.archive_file.zip.output_base64sha256}"

  role    = aws_iam_role.iam_for_lambda.arn
  handler = "hello_lambda.lambda_handler"
  runtime = "python3.6"

  environment {
    variables = {
      greeting = "Hello"
    }
  }
}

In this section, we do three major things:

  • Firstly, we use data to input the Lambda file and output a zip file to be uploaded for the Lambda implementation.
  • Second, we create an IAM role to be assumed by the lambda function.
  • Finally, we create the Lambda function using the zip file we created above as its source code before running the following commands to deploy the function:
terrform init
terraform plan
terraform apply

Whenever we update any particular resource, Terraform does not deploy all the resources again but it is smart enough to detect the resource that has been updated and carry out only the required changes.The same applies to deletion of any resource.

To bring it an end, I would just like to say that giving Terraform a try is worth a shot and will definitely help one to deeply understand the cloud services better. Hope this article helps you get started with it.

Thanks for reading. 😊

No Comments Yet