Terraform - Day 1

Terraform - Day 1

Terraform is a tool for building, changing, and versioning infrastructure for different cloud providers (eg: AWS, Azure, GCP etc.)

Terraform Installation on Centos 7:

Machine Info: cat /etc/*-release

[root@ip-172-31-31-245 centos]# cat /etc/*-release
CentOS Linux release 7.9.2009 (Core)
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

CentOS Linux release 7.9.2009 (Core)
CentOS Linux release 7.9.2009 (Core)

Terraform Installation:

1- Make sure you have wget and unzip tool installed in your system. If you don't have then you can download from below command.

yum install wget unzip -y

2- Update your packages.

yum update -y

3- Download Terraform Package

wget releases.hashicorp.com/terraform/0.15.3/ter..

4- Unzip Package

unzip terraform_0.15.3_linux_amd64.zip -d /usr/bin/

5- Terraform Version

terraform -v

[root@ip-172-31-31-245 centos]# terraform -v
Terraform v0.15.3
on linux_amd64

Authentication:

To make changes(creating resources) in your AWS account by Terraform, you need to set AWS creds by IAM user. The AWS provider offers a flexible means of providing credentials for authentication.

1- Static credentials - Hard coded creds are never recommended. Static credentials can be provided by adding an access_key and secret_key in the AWS provider block.

provider "aws" {
  region     = "us-east-1"
  access_key = "your-access-key"
  secret_key = "your-secret-key"
}

2- Environment Variables - You can provide your credentials via the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY

$ export AWS_ACCESS_KEY_ID="anaccesskey"
$ export AWS_SECRET_ACCESS_KEY="asecretkey"
$ export AWS_DEFAULT_REGION="us-east-1"

3- Shared Credentials File - You can use an AWS credentials or configuration file to specify your credentials. The default location is $HOME/.aws/credentials

You can use an AWS credentials or configuration file to specify your credentials.

On Linux: The default location is $HOME/.aws/credentials On Windows: "%USERPROFILE%.aws\credentials" [eg: C:\Users\user-name.aws]

Points:

  1. Terraform code is written in HashiCorp Configuration Language (HCL).
  2. Extension of terraform file is - .tf

Let's get started -

We'll start deploying a single EC2 Instance.

Step 1- Authentication

[root@ip-172-31-31-245 single-server]# aws --version
aws-cli/2.2.4 Python/3.8.8 Linux/3.10.0-1062.12.1.el7.x86_64 exe/x86_64.centos.7 prompt/off
[root@ip-172-31-31-245 single-server]# aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: us-east-1
Default output format [None]:

Step 2 - Configure provider

We have to create a file called main.tf & configure provider details. Provider is something for which cloud we're going to create resource. We'll be using AWS here.

main.tf

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

Step 3 - Resource Configuration

In each cloud provider, we have different kind of resources that we create. Let's say you want to create an EC2 Instance, LOAD Balancer, Database, Storage etc.

Syntax of creating resource:

resource "provider-type" "name" {
 resource-configuration
}
resource "aws_instance" "my-ec2-instance" {
  ami = "ami-09e67e426f25ce0d7"
  instance_type = "t2.micro"

  tags = {
    Name = "my-terraform-instance"
  }
}

There are many other arguments for resource aws_instance, we'll take it later.

Full Code:

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

resource "aws_instance" "my-ec2-instance" {
  ami = "ami-09e67e426f25ce0d7"
  instance_type = "t2.micro"

  tags = {
    Name = "my-terraform-instance"
  }
}

Above code is enough to quickly deploy a EC2 Instance with default configuration.

[root@ip-172-31-31-245 single-server]# pwd
/home/centos/Terraform/single-server
[root@ip-172-31-31-245 single-server]# ls -ltrh
total 4.0K
-rw-r--r--. 1 root root 151 May 14 16:47 main.tf

Deploy via Terraform:

1- Terraform Init: First of all, you have to go to main.tf directory & run terraform init command which will scan code & check out provider details which we have mentioned in the code & download the code for them. Provider code will be stored into a .terraform folder after download.

[root@ip-172-31-31-245 single-server]# terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v3.40.0...
- Installed hashicorp/aws v3.40.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
[root@ip-172-31-31-245 single-server]# ls -ltrha
total 8.0K
drwxr-xr-x. 3 root root   27 May 14 16:47 ..
-rw-r--r--. 1 root root  151 May 14 16:47 main.tf
drwxr-xr-x. 3 root root   23 May 14 16:48 .terraform
-rw-r--r--. 1 root root 1.1K May 14 16:48 .terraform.lock.hcl
drwxr-xr-x. 3 root root   66 May 14 16:48 .

2- Terraform Plan: Basically plan commands shows what terraform will create before you deploy, it's way to check your code before deployment. Output of terraform plan command as plus sign (+) [It means these things will be added/created] & minus sign(-) [It means these things will be deleted] & tilde sign(~)[It means things will be modified].

[root@ip-172-31-31-245 single-server]# terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.my-ec2-instance will be created
  + resource "aws_instance" "my-ec2-instance" {
      + ami                                  = "ami-09e67e426f25ce0d7"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

3- Terraform Apply: Now to create an instance, run terraform apply command, terraform apply command will show similar output as plan command but here it will ask for confirmation to deploy the changes.

[root@ip-172-31-31-245 single-server]# terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.my-ec2-instance will be created
  + resource "aws_instance" "my-ec2-instance" {
      + ami                                  = "ami-09e67e426f25ce0d7"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "my-terraform-instance"
        }
      + tags_all                             = {
          + "Name" = "my-terraform-instance"
        }
      + tenancy                              = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

aws_instance.my-ec2-instance: Creating...
aws_instance.my-ec2-instance: Still creating... [10s elapsed]
aws_instance.my-ec2-instance: Still creating... [20s elapsed]
aws_instance.my-ec2-instance: Still creating... [30s elapsed]
aws_instance.my-ec2-instance: Creation complete after 31s [id=i-xxxxxxxxxxxxxxxxxx]

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

Hurraayy, we deployed an EC2 Instance, let's check in our AWS account.

image.png

Please note that, Terraform keeps track of all resources which already created so if you run terraform apply command after making some changes in same main.tf, it will say refreshing state.

Let's say I change tag name from my-terraform-instance to my-new-terraform-instance

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

resource "aws_instance" "my-ec2-instance" {
  ami = "ami-09e67e426f25ce0d7"
  instance_type = "t2.micro"

  tags = {
    Name = "my-new-terraform-instance"
  }
}

Now run terraform apply.

[root@ip-172-31-31-245 single-server]# terraform apply
aws_instance.my-ec2-instance: Refreshing state... [id=i-xxxxxxxxxxxxxxxxxxx]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_instance.my-ec2-instance will be updated in-place
  ~ resource "aws_instance" "my-ec2-instance" {
        id                                   = "i-xxxxxxxxxxxxxxxxx"
      ~ tags                                 = {
          ~ "Name" = "my-terraform-instance" -> "my-new-terraform-instance"
        }
      ~ tags_all                             = {
          ~ "Name" = "my-terraform-instance" -> "my-new-terraform-instance"
        }
        # (27 unchanged attributes hidden)




        # (4 unchanged blocks hidden)
    }

Plan: 0 to add, 1 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

aws_instance.my-ec2-instance: Modifying... [id=i-xxxxxxxxxxxxxxxxxxxxx]
aws_instance.my-ec2-instance: Modifications complete after 0s [id=i-xxxxxxxxxxxxxx]

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

We can see the tag name changes on console.

image.png

That's all for Day-1, will meet with Day-2 Blog soon, stay tuned!