Hello World with Terraform

2021.06.10

This article talks about Terraform, an open source infrastructure automation tool by HashiCorp in a 101-introductory fashion.

What is Terraform?

Terraform is an Infrastructure As Code (IaC) tool which allows users to provision infrastructure through code. In simple words, whatever resources ( infrastructure ) an application needs, can be build using terraform programmatically. Terraform allows programmers to provision, change, and version infrastructure automatically and efficiently.

The principle (IaC) behind terraform comes from the Agile and DevOps world with aims to make infrastructure provisioning in an automated and repeated manner, and thus taking away error-prone manual configuration and management.

What makes Terraform so special?

The following features make terraform so unique and amazing:-

  • Declarative:- Terraform language is declarative in nature, which means we tell terraform what we desire as an end state, and it will handle the execution on its own, which is unlike the case in imperative style where we need to define every single step on how to perform a task.
  • Providers:- Terraform supports a lot of  IaaS, Saas, Paas providers. Providers are a logical abstraction of an upstream API. They are responsible for understanding API interactions and exposing resources.
  • State:-  Terraform stores and maintain the current state of your infrastructure and configuration. This state is used to avoid configuration discrepancies when a new resource is added to existing infrastructure or some modifications take place.

Deploy EC2 using Terraform

  • This blog deploys an EC2 instance configured with an apache server and security group which allows HTTP and SSH traffic only.

Pre-requisites

  • Terraform's config files where you define resources have a .tf extension, and this blog uses Visual studio code and Terraform extension in vscode by HashiCorp.
  • This blog uses start.tf as the main config file and Terraform v0.15.4 version.
  • aws cli must be configured.

Define a provider

  • Defining a plugin allows us to talk to a specific set of API for a particular service. Terraform will figure out what provider plugins needs to be installed based on provider configuration.
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

Configure the provider

provider "aws" {
  region = "ap-southeast-1" //region where resources need to be deployed
}

Defining Resource blocks

  •  Another important component of terraform config file which describes one or more infrastructure objects like EC2, Load Balancer, VPC, etc.
  • A resource block  declares a resource of a given type ("aws_instance") with a given local name ("terraform-ec2"). The name is used to refer to this resource from elsewhere in the config file but has no meaning outside the config file's scope.
  • The resource type and name together serve as an identifier for a given resource.
  • Following codes are the declaration for accessing ami id, and security group with inbound and outbound rules.
#for accesing ami id for particular region

data "aws_ami" "instance_id" {
  owners      = ["amazon"]
  most_recent = true

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }
}

# for declaring security group for ec2 instance resource 

resource "aws_security_group" "allow_web" {
  name        = "allow_web.traffic"
  description = "Allow TLS web traffic"

  ingress {
    description = "SSH from VPC"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

  }
  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

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

  tags = {
    Name = "Created by Terraform"
  }
}
  • The following declaration is about ec2 resource with all the necessary parameters like ami id, security group, user data, key, instance type, availability zone and tags.
resource "aws_instance" "terraform-ec2" {
  ami               = data.aws_ami.instance_id.id
  instance_type     = "t2.micro"
  availability_zone = "ap-southeast-1a"
  key_name          = "jatin-marek-learning1-key-pair"
  security_groups   = [aws_security_group.allow_web.name]
  user_data         = <<-EOF
                #! /bin/bash
                sudo yum update
                sudo yum install -y httpd
                sudo systemctl start httpd
                sudo systemctl enable httpd
                echo "
<h1>Deployed via Terraform</h1>

" | sudo tee /var/www/html/index.html
        EOF
  tags = {
    Name = "Created by Terraform"
  }
}

Referencing resources in config file

  • We can reference other resources which are being defined in our config code using reference expressions.
  • The syntax is as follows <RESOURCE TYPE>.<NAME>.<property> represents a managed resource of the given type and name and its property.
  • The following lines show how resources are referenced.
resource "aws_instance" "terraform-ec2" {
  ami               = data.aws_ami.instance_id.id
  security_groups   = [aws_security_group.allow_web.name]
}

Terraform Commands and files

  • Just like any other program language we need to run/execute the written code, in terraform world, this means to generate a state in the form of a blueprint and then apply it to spin up the resources to build our infrastructure.
  • There is a general workflow that every terraform project follows. Divided into 3 stages.

Code/init stage

  • In this stage, the providers and resources are declared which we have done in our start.tf file.
terraform init
  • After writing our resource declaration this command looks at config to download required plugins to interact with our provider by creating a .terraform directory for storing all provider-related code.

Plan Stage

  • This stage creates an execution plan, which is like a preview of changes that Terraform plans to make to your infrastructure. By default, when Terraform creates a plan it:
    • Reads the current state of any already-existing remote objects to make sure that the Terraform state is up-to-date.
    • Compares the current configuration to the prior state and noting any differences.
    • Proposes a set of change actions that should, if applied, make the remote objects match the configuration.
  • Terraform state is stored in terraform.tfstate file, this file should NOT be edited by the user directly.
 terraform plan

Apply/Destroy Stage

  • In this stage, terraform spins up the resources using cloud provider API and output some variables either autogenerated or user-defined.
  • Order of declaration is not important (with some exceptions) as terraform is smart enough to decide that order during provisioning of infrastructure.
  • Terraform's execution should not be assumed as a step-wise execution, terraform uses a blueprint for execution so whatever resources in the plan are new/modified/deleted, only those will be touched
  • start.tf file before running apply command looks like this .
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

# Configure the AWS Provider

provider "aws" {
  region = "ap-southeast-1" //region where resources need to be deployed
}
variable "tag_for_ec2" {

  description = "Tags for ec2"
  type        = any
}

data "aws_ami" "instance_id" {
  owners      = ["amazon"]
  most_recent = true

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }
}

resource "aws_instance" "terraform-ec2" {
  ami               = data.aws_ami.instance_id.id
  instance_type     = "t2.micro"
  availability_zone = "ap-southeast-1a"
  key_name          = "jatin-marek-learning1-key-pair"
  security_groups   = [aws_security_group.allow_web.name]

  user_data         = <<-EOF
                #! /bin/bash
                sudo yum update
                sudo yum install -y httpd
                sudo systemctl start httpd
                sudo systemctl enable httpd
                echo "
<h1>Deployed via Terraform</h1>

" | sudo tee /var/www/html/index.html
        EOF
  tags = {
    Name = "Created by Terraform"
  }
}

resource "aws_security_group" "allow_web" {
  name        = "allow_web.traffic"
  description = "Allow TLS web traffic"

  ingress {
    description = "SSH from VPC"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

  }
  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]

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

  tags = {
    Name = "Created by Terraform"
  }
}
terraform apply //to create resources for the infrastructure.

  • To destroy resources there are 2 ways:
    • either run destroy command.
    • terraform destroy //to destroy resources from the infrastructure.
    • Delete the code in the config file and run terraform apply since terraform's execution depends on the blueprint/plan.
  • We can confirm our provision in the aws console by accessing our website.

Conclusion

Here we saw how terraform helps in automating the provisioning of resources in the cloud. In the future playing with terraforming can go into more depth like using variables, targeting specific resources for deployment, conditional expressions, etc.

Till then happy learning.