Elevating Infrastructure - Terraform in Action

Elevating Infrastructure - Terraform in Action

Introduction

Infrastructure as code (IAC) tools let you handle your infrastructure using files instead of clicking around in a user-friendly program. With IAC, you can create, edit, and take care of your infrastructure in a secure, reliable, and do-it-again manner by specifying how resources should look. You can even save, reuse, and exchange these configurations with others. Some of the popular IAC tools at present are:

  1. Terraform

  2. AWS CloudFormation

  3. Google Cloud Deployment Manager

  4. Azure Resource Manager (ARM) Templates

What is Terraform?

Terraform is a tool that helps you build and manage your computer servers, storage, and networking in an automated and consistent way using code instead of manual actions. It's like giving your infrastructure instructions in a language it understands so you can create, update, and delete resources easily.

Challenges in IT Infrastructure

Installation

  1. Terraform works on different operating systems. In this blog, we'll show you how to install Terraform on Windows. To get it, you can click here to visit the official Terraform installation page. After downloading, follow the steps below.

  2. Unzip the Terraform file and open the folder you find inside it.

  3. Copy the path of the Terraform file located within the Terraform folder and add it to your environmental variables through the advanced system settings.

  4. To check if Terraform is installed, open your terminal and try running terraform --version.

    If everything is set up correctly, running the above command in the terminal should display the Terraform version.

Terraform Syntax

The Terraform language uses a low-level syntax called HashiCorp Configuration Language (HCL). This syntax is also used in other HashiCorp products and various configuration languages. You don't need to be an expert in HCL to use Terraform effectively; having a basic understanding of it should suffice.

Blocks and Arguments?

In Terraform, a "block" is like a container that holds related settings for a particular resource or configuration element, such as a server or a network.

"Arguments" are the specific settings or parameters you put inside a block. These settings define the resource's configuration, like its name, size, or other attributes.

In simpler terms, think of a block as a box, and the arguments as the items you put inside that box to set up your resources the way you want.

In Terraform, an "identifier" is a unique name you give to each resource or configuration block to help Terraform keep track of and manage them. It's like a label or tag that distinguishes one thing from another, allowing you to work with and refer to them in your code.

resource "<provider>_<resource type>" "<resource name>" (
Argumentl = ""
Argument2 = ""

Execution of Infrastructure

terraform init -> terraform plan -> terraform apply

The terraform init command scans your Terraform configuration files in the folder and sets up all the necessary tools and dependencies for automation.

The terraform plan the command generates a blueprint or plans for what Terraform is going to do, including what it will install, the resource names, and the properties it will configure. It helps you see the changes before they are applied.

The terraform apply command is where the real action takes place. It executes the automation and carries out the changes defined in your Terraform configuration.

Docker with Terraform

To specify which provider Terraform should use for automation, you need to provide the provider's name along with its source and version. For Docker, you can include this code block in your 'main.tf' configuration file.

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "3.0.2"
    }
  }
}

What is a Provider?

The provider block sets up the configuration for the specified provider, which, in this example, is Docker. Providers are plugins that Terraform relies on to create and manage resources. Here's an example of a provider block for Docker:

provider "docker" {
  # Provider configuration settings go here
}

What is a Resource?

Resource blocks are used to define the individual parts of your infrastructure. A resource can represent a physical or virtual component like a Docker container or a logical resource such as a Heroku application. These blocks allow you to specify and manage the characteristics of these components in your Terraform configuration.

In resource blocks, two strings come before the block: the 'resource type' and the 'resource name'. For instance, in the example provided, the resource type is "docker_image," and the resource name is "nginx." These identifiers help Terraform understand and manage each resource uniquely within your configuration.

#Pulling the latest image of nginx
resource "docker_image" "nginx" {
    name         = "nginx:latest"
    keep_locally = false
}

#Running container from the above image
resource "docker_container" "nginx_container" {
    image = docker_image.nginx.latest
    name  = "nginx_container"
    ports {
        internal = 80
        external 80
    }
}

Additional Terraform Commands

  • terraform fmt: Formats the Terraform code.

  • terraform validate: This command checks the syntax and configuration of your Terraform files, ensuring they are correctly written and can be processed by Terraform without errors.

  • terraform show: It provides a human-readable representation of the current state of your infrastructure as managed by Terraform. It displays the details of the deployed resources.

  • terraform refresh: It updates the status of your infrastructure according to your configuration and refreshes the variables.

Terraform Variables

Terraform variables are like placeholders in your configuration files. They let you store and reuse values that can be customized for different situations. It's a way to make your Terraform code flexible, allowing you to change settings easily without modifying the code itself.

#variable for giving filename
variable "filename" {
    default = "/home/ubuntu/demo-var.txt"
}

#variable for writing inside a file
variable "content" {
    default = "This line is coming from a variable"
}

These variables can be accessed by the var object in our main file.

#Accessing the variables that we've created above
resource "local_file" "devops" {
    filename = var.filename
    content = var.content
}

The code creates a file named var.filename(demo-var.txt) with specific content.

Data Types in Terraform

Terraform data types are like categories that define the kind of information a value can hold. They include simple types like numbers and strings and complex types like lists and maps. These types help Terraform understand and manage different kinds of data in your infrastructure code.

variable "file_contents" (
    type = map
    default = {
        "statementl" = "this is cool"
        "statement2" = "this is cooler"
    }
}

Object

In Terraform, an object is a data structure that holds information about a specific resource or configuration element. It contains attributes that describe the properties and settings of that resource, allowing Terraform to manage it effectively within your infrastructure code.

variable "devops" {
    type = object({
        name = string
        items = list(number)
    })

    default {
        name = "shubham"
        items = [1,2,3,4]
    }
}

#Give the output of the above variables
output "devops-op" {
    value = var.devops.name
}

output "devops-items" (
    value = var.devops.items
}

In Terraform, an output is like a way to share information from your infrastructure code. It lets you display or pass specific values, like an IP address or resource name, so you can use them outside of your Terraform configuration, making it easy to access and share important data.

Run terraform refresh to see the following output.

Terraform State

Terraform state is like a record-keeping system that stores information about the resources and configurations in your infrastructure. It keeps track of what's been created and how they relate to each other. This state data helps Terraform manage and update your infrastructure accurately.

You can use Terraform without the state, but the state is like a helpful record of your infrastructure. It explains why and how things were built. Think of the state as a blueprint of your real-world setup, complete with special IDs and details.

Terraform State commands

  • terraform state list: It is used to see a list of the resources that Terraform is managing in your setup.

  • terraform-state mv: Move items within the terraform state. It's handy for renaming resources without having to destroy and reapply them using the terraform apply command.

  • terraform state pull: Manually download and display the state information from the state file.

  • terraform state push: Manually upload a local state file to the remote state, making sure the remote state reflects the changes.

  • terraform state rm: Deletes items from the state. These items are no longer managed by Terraform, but they are not physically destroyed.

  • terraform state show: Displays the attributes of a single resource in the state, allowing you to inspect the details of that resource's configuration.

State Locking

State locking in Terraform is like a safety feature that prevents multiple people or processes from making changes to your infrastructure at the same time. It ensures that only one person or process can modify the infrastructure state to avoid conflicts and data corruption. It's like giving everyone a turn to make changes, so things stay organized and secure.

In Terraform, state locking is achieved through a mechanism called "state locking backends." These are external systems or services that manage and coordinate access to the Terraform state file.

Here's how it typically works:

  1. When you run a Terraform command (e.g., terraform apply), Terraform will request a lock from the state locking backend.

  2. If another process already has the lock, your command will wait until the lock is released.

  3. Once your command gets the lock, it can read and modify the state file.

  4. When your command is done, it releases the lock so that others can use it.

Common state locking backends include distributed systems like Consul, Amazon S3 with DynamoDB, or remote storage services that support locking. The specific backend and setup depend on your infrastructure and collaboration needs. State locking helps prevent conflicts and ensure safe, synchronized changes to your infrastructure.

Backend Management

In Terraform, a "backend" refers to the storage and remote execution configuration for your Terraform state file. The state file is a critical part of Terraform, as it stores information about your infrastructure and is used to plan, apply, and manage changes.

Backends in Terraform serve two primary purposes:

  1. State Storage: Backends determine where the state file is stored, which can be a local file, a remote storage service (like AWS S3, Azure Blob Storage, or HashiCorp Consul), or even a database. Storing the state remotely is crucial for collaboration and ensuring that all team members work with the same, up-to-date state.

  2. Remote Operations: Some backends can also enable remote operations, meaning Terraform commands like "apply" or "plan" can be executed on a remote infrastructure, making it easier to collaborate with others and maintain a consistent state.

By configuring a backend in your Terraform configuration, you define how the state file is managed and where it's stored, which can greatly impact the collaboration, security, and scalability of your Terraform projects.

Local

A "local backend" in Terraform stores the state file on your local machine. It's best for individual or simple projects but lacks collaboration features for larger teams. Remote backends are recommended for collaborative or production use.

terraform {
  backend "local" {
    path = "path/to/statefile.tfstate"
  }
}

Remote

In Terraform, a "remote backend" is a configuration option that stores the Terraform state file on a remote server or service, rather than on your local machine. This is a recommended approach for team collaboration and production environments because it provides centralized state management, access control, and state locking to prevent conflicts when multiple users or processes are working on the same infrastructure.

Common remote backend options include cloud storage services like AWS S3, Azure Blob Storage, or databases like HashiCorp Consul. Remote backends also often support additional features such as remote operations, making it easier for multiple team members to work together on infrastructure projects securely and efficiently.

terraform {
    backend " remote" {}
}

Modules

In Terraform, a module is like a reusable building block for your infrastructure code. It allows you to encapsulate and organize related resources and configurations so that you can use them in different parts of your projects. Modules simplify your Terraform code by promoting reusability and modularity.

In Terraform, there are mainly two types of modules:

  1. Root Modules: These are the primary configuration files where you define your infrastructure. They can include child modules and represent the entry point for your Terraform configuration.

  2. Child Modules: Child modules are reusable components that you create to encapsulate specific parts of your infrastructure. They make your code more organized and easier to manage. You can use child modules within root modules or even reuse them in other projects to build complex infrastructure more efficiently.

variable "image_id" {
    type = string
}

resource 'aws_instance" "myec2" {
    ami = var.image_id
    instance_type = "t2.micro"
}

module "dbserver" {
    source = "./db"    
    image_id = "ami-@528a5175983e7f28"
}

Terraform Functions

In Terraform, functions are like tools that help you manipulate data and make decisions within your configuration. They perform various tasks, such as formatting strings, doing math, or making choices based on conditions. Functions enable you to work with values and create dynamic configurations in your Terraform code.

In Terraform, you can't create your custom functions. You can only use the functions that are already provided by Terraform, which are built into the language. These pre-existing functions help you work with data and make choices in your Terraform code.

Provisioners

In Terraform, provisioners are like scripts or commands that you can run on your resources after they've been created. They help you set up and configure those resources, install software, or perform other tasks. Provisioners are often used for tasks that can't be defined in the resource configuration itself.

resource "aws_instance" "web" {
    # ...
    # Copies the myapp. conf
    # file to / etc/myapp. conf
    provisioner "file" {
        source = "conf/myapp.conf"
        destination = "/etc/myapp.conf"
    }
}

There are primarily two types of provisioners in Terraform:

  1. Local-Exec Provisioners: These provisioners run scripts or commands on the machine where Terraform is executed. They are typically used for tasks like local setup, file copying, or executing custom scripts.

     resource "aws_instance" "web" {
         # ...
         provisioner "local-exec" {
             command = "echo The server's IP address is ${setf.private_ip}"
         }
     }
    
  2. Remote-Exec Provisioners: These provisioners connect to the resource via SSH or WinRM and run scripts or commands on the remote machine. They are used for tasks that require configuring resources on the remote server, like software installation or configuration changes.

     resource "aws_instance" "web" {
         # ...
         provisioner "remote—exec" {
             inline = [ 
                 "puppet apply",
                 "consul join ${aws_instance.web.private_ip}",
             ]
         }
     }
    

Both types of provisioners help in automating the setup and configuration of resources in Terraform.

Meta Arguments

In Terraform, "meta-arguments" are special configuration settings that apply to a resource block as a whole, rather than defining a specific attribute of the resource. They provide additional metadata and control over how Terraform manages the resource. Common meta-arguments include:

  1. count and for_each: These allow you to create multiple instances of a resource, based on conditions or input variables.

     # count usecase
     resource "aws_instance" "web_count" {
         count = 4
         ami = = "ami-08c40ec9ead489470"
         instance_type = "t2.micro"
     }
    
     #for_each usecase
     locals {
         ami_ids = toset([
             "ami-ObOdcb5067f052a63",
             "ami-08c40ec9ead489470",
         ])
     }
    
     resource "aws_instance" "web_for_each" {
         for_each = locals.ami_ids
         ami = each.value
         instance_type = "t2.micro"
    
         tags = {
             Name = "Server ${each.key}"
         }
     }
    
  2. depends_on: It specifies dependencies between resources, ensuring one resource is created or updated before another.

  3. lifecycle: This argument provides control over resource behavior, such as creating, updating, or destroying a resource.

Meta-arguments are used to fine-tune how Terraform interacts with resources and manages complex resource relationships.