Lab 3 : Getting Started with Terraform

In this lab you are going to learn how to, * Create a Terraform Configuration * Add and initialise a Cloud Provisioner * Write a Resource by using HCL * Validate Configurations * Create a Change Plan * Apply Configurations to Build Actual Infrastructure * Modify Existing Infrastructure created with Terraform * Destroy the Infrastructure created by Terraform

Getting Started with terraform CLI

You could get started with terraform and check the list of commands for Terraform by running,

terrform -h

From the commands listed , the following are most commonly used ones,

terraform init
terraform validate
terraform plan
terraform apply
terraform destroy

Adding Terraform Configurations

mkdir terraform-code
cd terraform-code

You will begin by defining required_providers block for terraform. This defines which providers should terraform install, from where, and which version.

Create main.tf with the following initial configuration

terraform {
  required_providers {
    aws = {
      source  = "registry.terraform.io/hashicorp/aws"
      version = "~> 4.29"
    }
  }
}

Provider Source Components

You could read more about Provider Requirements at Provider Requirements - Configuration Language | Terraform by HashiCorp and about AWS Provider’s Version etc. used above at https://registry.terraform.io/providers/hashicorp/aws/latest.

Check the providers configuration using

terraform providers

Adding AWS Cloud Provider

In Terraform, Providers are responsible for understanding API interactions from * IaaS : e.g. AWS, Azure, GCP * PaaS : e.g. Heroku, Kubernetes * SaaS : e.g. CloudFlare, DNSSimple, DNSMadeEasy

Let’s add AWS provider to main.tf as follows,

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

As seen in the above given example, Provider is a way to instruct Terraform to interact with AWS API. Every provider needs some kind of credentials to get authenticated with the API. In this example, we would provide AWS access key and secret key by adding those to the environment.

Export your AWS access and secret keys as environment variables.

export AWS_ACCESS_KEY_ID="Access-Key"
export AWS_SECRET_ACCESS_KEY="Secret-Key"

You could also add the above lines to your ~/.bashrc , ~/.zshrc files based on which shell you are using to avoid setting these credentials every time you open a new shell. Now run the following command to initialize your environment.

terraform init

This should initialize the environment by * Installing the provider from the registry as defined in main.tf * Adding a lock file .terraform.lock.hcl to record the provider details installed.

Launching a EC2 Instance with Terraform

To create anything with terraform, you need to write a Resource. You could get started by reading about using Resources hereResources Overview - Configuration Language | Terraform by HashiCorp

Resources are the building block in Terraform. These the actual cloud entities that you are either creating/deleting or modifying.

Following is a syntax for writing a simple resource. There are a few more properties such as meta-arguments, condition checks, timeouts etc. which we would explore later.

To get started, with the AWS provider, you would create EC2 instance by using aws_instance as a resource. You could refer to this document Terraform Registry, to find the details about using this resource. You should be specially paying attention to the Argument References

Add the following code to same main.tf file to define creation of a new EC2 instance by name frontend.

resource "aws_instance" "frontend" {
  ami           = "ami-052efd3df9dad4825"
  instance_type = "t2.micro"

   tags = {
    Name       = "tf-frontend-01"
    App        = "devops-demo"
    Maintainer = "Gourav Shah"
  }
}

Validate terraform configurations with,

terraform validate

Optionally, you could further format the configuration file automatically to follow correct terraform style with,

terraform fmt

Now you could let Terraform create a Change Plan using the following command,

terraform plan

To apply this plan and build actual infrastructure, in this case create a ec2 instance with,

terraform apply

Terraform apply first generates a plan and then asks for approval as,

[Sample Output]

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Say yes to proceed, which will create the instance.

[Sample Output]

aws_instance.frontend: Creating...
aws_instance.frontend: Still creating... [10s elapsed]
aws_instance.frontend: Still creating... [20s elapsed]
aws_instance.frontend: Still creating... [30s elapsed]
aws_instance.frontend: Creation complete after 36s [id=i-04b03b981dbc55d7c]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Try running apply command again.

terraform apply

It does nothing this time since the instance was already created.

Check the state file created using the following or similar command specific to your OS,

cat terraform.tfstate

Modifying Properties of an Existing Instance

Begin by applying terraform once

terraform apply 

Since there is no change from the previous run, it does not plan to change anything. The property of terraform where it refreshes the current state, compares it with the desired state, and decides to make change only if necessary is idempotence. This makes terraform safe to apply multiple times.

Lets try making a change by adding a property which is mutable. To do this, just add one more tag to the existing map of tags as,

...
   tags = {
    Name       = "tf-frontend-01"
    App        = "devops-demo"
    Maintainer = "Gourav Shah"
    Role       = "frontend"
  }
}

Where Role = "frontend" is the new property.

[Sample Output]

  # aws_instance.frontend will be updated in-place
  ~ resource "aws_instance" "frontend" {
        id                                   = "i-04b03b981dbc55d7c"
      ~ tags                                 = {
          + "Role"       = "frontend"
            # (3 unchanged elements hidden)
        }
      ~ tags_all                             = {
          + "Role"       = "frontend"
            # (3 unchanged elements hidden)
        }
        # (29 unchanged attributes hidden)

        # (7 unchanged blocks hidden)
    }

Since this property is mutable , it would update the instance in place, without needing for it to be recreate. Say yes to apply the changes.

Now, try updating a property which is immutable e.g. keypair. Update main.tf by adding a key pair to be associated with this instance.

resource "aws_instance" "frontend" {
  ami           = "ami-052efd3df9dad4825"
  instance_type = "t2.micro"
  key_name      = "demo"

   tags = {
    Name       = "tf-frontend-01"
    App        = "devops-demo"
    Maintainer = "Gourav Shah"
    Role       = "frontend"
  }
}

Where, key_name = "demo" is the new argument added. Ensure a key pair by name demo is present on AWS for the region for which you are creating this instance.

terraform apply

This time terraform plan should show you that its going to recreate the instance, since the change is immutable.

  # aws_instance.frontend must be replaced
-/+ resource "aws_instance" "frontend" {
      ~ arn                                  = "arn:aws:ec2:us-east-1:665496447754:instance/i-0a477fc11e0055d72" -> (known after apply)
      ~ associate_public_ip_address          = true -> (known after apply)
..
..
      + key_name                             = "demo" # forces replacement
..
..
Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?

Say yes to have the instance be recreated, this time with a key pair.

[Sample Output]

aws_instance.frontend: Destroying... [id=i-0a477fc11e0055d72]
aws_instance.frontend: Still destroying... [id=i-0a477fc11e0055d72, 10s elapsed]
aws_instance.frontend: Still destroying... [id=i-0a477fc11e0055d72, 20s elapsed]
aws_instance.frontend: Still destroying... [id=i-0a477fc11e0055d72, 30s elapsed]
aws_instance.frontend: Destruction complete after 32s
aws_instance.frontend: Creating...
aws_instance.frontend: Still creating... [10s elapsed]
aws_instance.frontend: Still creating... [20s elapsed]
aws_instance.frontend: Still creating... [30s elapsed]
aws_instance.frontend: Creation complete after 37s [id=i-02b2a78e90ac9e89e]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

If you check from the AWS Console, you shall now see a new instance created, this time associated with the keypair you defined e.g. demo as shows in the image below.

Deleting Instance with Terraform Destroy

terraform destroy -auto-approve

[Sample Output]

Terraform will perform the following actions:

  # aws_instance.frontend will be destroyed
  - resource "aws_instance" "frontend" {
      - ami                                  = "ami-052efd3df9dad4825" -> null
..
...

Plan: 0 to add, 0 to change, 1 to destroy.
aws_instance.frontend: Destroying... [id=i-02b2a78e90ac9e89e]
aws_instance.frontend: Still destroying... [id=i-02b2a78e90ac9e89e, 10s elapsed]
aws_instance.frontend: Still destroying... [id=i-02b2a78e90ac9e89e, 20s elapsed]
aws_instance.frontend: Still destroying... [id=i-02b2a78e90ac9e89e, 30s elapsed]
aws_instance.frontend: Destruction complete after 32s

Destroy complete! Resources: 1 destroyed.

Summary

In this lab, you learnt how to get started with terraform to build your infrastructure. You configured terraform, added and initialised a provider, created a resource using HCL (Hashicorp Configuration Language), learnt to apply the configuration and also to modify and destroy a resource with terraform.