Terraform State lock using backend S3 and Modules project

Terraform State lock using backend S3 and Modules project

In this session, we will be learning about the Terraform state

What is Terraform State?

Terraform logs information about the resources it has created in a state file. This enables Terraform to know which resources are under its control and when to update and destroy them. The terraform state file, by default, is named terraform.tfstate and is held in the same directory where Terraform is run. It is created after running terraform apply.

The actual content of this file is a JSON-formatted mapping of the resources defined in the configuration and those that exist in your infrastructure. When Terraform is run, it can then use this mapping to compare infrastructure to the code and make any adjustments as necessary.

Storing State Files

State files, by default, are stored in the local directory where Terraform is run. If you are using Terraform to test or for a personal project, this is fine (as long as your state file is secure and backed up!). However, when working on Terraform projects in a team, this becomes a problem because multiple people will need to access the state file.

Also, when using automation and CI/CD pipelines to run Terraform, the state file needs to be accessible, and permission must be given to the service principal running the pipeline to access the storage account container that holds the state file. This makes shared storage the perfect candidate to hold the state file. Permissions can be granted as needed. Azure Storage accounts or Amazon S3 buckets are an ideal choice. You can also use a tool such as Spacelift to manage your state for you.

You should store your state files remotely, not on your local machine! The location of the remote state file can then be referenced using a backendblock in the terraform block (which is usually in the main.tf file).

TASK 1: Storing state file in s3 Backend

Create a terraform.tf

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

Create a provider.tf

provider "aws" {
   region = "var.aws_region"
}

Create a varaible.tf, which contains all the varibles

variable "ami" {
  default = "ami-007855ac798b5175e"
}

variable "instance_type" {
  default = "t2.micro"
}

variable "instance_name" {
    default = "batch3-demo-instance"
}

variable "bucket_name" {
    default = "batch3-demo-bucket"
}

variable "state_bucket_name" {
    default = "batch3-demo-state-bucket"
}

variable "state_table_name" {
    default = "batch3-demo-state-table"
}

variable "aws_region" {
    default = "us-east-1"
}

Create EC2.tf

resource "aws_instance" "my_instance" {
    ami = var.ami
    instance_type = var.instance_type
    tags = {
        Name = var.instance_name
    }
}

Create output.tf

output "aws_ec2_instance_ip" {
    value = aws_instance.my_instance.public_ip
}

Create s3.tf to create an s3 bucket

resource "aws_s3_bucket" "my_state_bucket" {
    bucket = var.state_bucket_name
    tags = {
        Name = var.state_bucket_name
    }
}

Create dynamo.tf to create a dynamodb

resource "aws_dynamodb_table" "my_state_table" {
    name = var.state_table_name
    billing_mode = "PAY_PER_REQUEST"
    hash_key = "LockID"
    attribute {
        name = "LockID"
        type = "S"
    }
    tags = {
        Name = var.state_table_name
    }
}

Run terraform Apply

Make sure you provide S3 and dynamodb access to the IAM user

Run the terraform commands again

We have created an EC2 instance, an s3 bucket to store the state file and dynamodb

Adding backend script

Now in terraform.tf add backend script with s3 bucket name and state file name in Key

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

terraform {
backend "s3" {
    bucket = "batch3-demo-state-bucket"
    key = "terraform.tfstate"
    region = "us-east-1"
    dynamodb_table = "batch3-demo-state-table"
  }
}

Now apply to Terraform init, plan and apply

Now if you go back and check the bucket, it will have a state file stored in it. This is how we store the state files in s3 Backend.


Modules project

In this project, we will be using the modules concept to create different environments like DEV, PROD and STG and add resources into it instantly

Create a provider.tf

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

Create a terraform.tf

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

Create main.tf, where we will be using modules concept

# dev 
module "dev-app" {
    source = "./my_app_infra_module"
    my_env = "dev"
    instance_type = "t2.micro"
    ami = "ami-007855ac798b5175e" 
}

#prd
module "prd-app" {
    source = "./my_app_infra_module"
    my_env = "prd"
    instance_type = "t2.medium"
    ami = "ami-007855ac798b5175e" 
}

#stg
module "stg-app" {
    source = "./my_app_infra_module"
    my_env = "stg"
    instance_type = "t2.small"
    ami = "ami-007855ac798b5175e" 

}

Also, create a backend.tf to create an s3 bucket and dynamodb to store the state file at the time of running Backend s3.

resource "aws_s3_bucket" "my_state_bucket" {
    bucket = var.state_bucket_name
    tags = {
        Name = var.state_bucket_name
    }
}

resource "aws_dynamodb_table" "my_state_table" {
    name = var.state_table_name
    billing_mode = "PAY_PER_REQUEST"
    hash_key = "LockID"
    attribute {
        name = "LockID"
        type = "S"
    }
    tags = {
        Name = var.state_table_name
    }
}


variable "state_bucket_name" {
    default = "batch3-demo-state-bucket"
}
variable "state_table_name" {
    default = "batch3-demo-state-table"
}
variable "aws_region" {
    default = "us-east-1"
}

Now create a folder, where we will be placing all our infra files

Create a variable.tf

We will be creating all the variables like my-env, instance_type, ami as string type and my_env will be the environment that we declare in the module

variable "my_env" {
    description = "The environment for the app"
    type = string
}

variable "instance_type" {
    description = "value of the instance type"
    type = string
}

variable "ami" {
    description = "value of the ami"
    type = string
}

Create myserver.tf

resource "aws_instance" "my_app_server" {
    count = 2
    ami = var.ami
    instance_type = var.instance_type
    tags = {
        Name = "${var.my_env}-batch3-app-server"
    }
}

Create a mybucket.tf

resource "aws_instance" "my_app_server" {
    count = 2
    ami = var.ami
    instance_type = var.instance_type
    tags = {
        Name = "${var.my_env}-batch3-app-server"
    }
}

create mytable.tf

resource "aws_dynamodb_table" "my_app_table" {
    name = "${var.my_env}-batch3-app-table"
    billing_mode = "PAY_PER_REQUEST"
    hash_key = "userID"
    attribute {
        name = "userID"
        type = "S"
    }
    tags = {
        Name = "${var.my_env}-batch3-app-table"
    }
}

Apply terraform commands

We have created 6 instances, 4 buckets in which one bucket will be used to store the state file when we run the backend in S3 and 4 Dynamodb tables out of which 1db is for the state file.

Backend S3

Now we will run the Backend for S3 in main.tf by adding the below code

Apply the terraform commands again

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

terraform {
  backend "s3" {
    bucket = "batch3-demo-state-bucket"
    key = "terraform.tfstate"
    region = "us-east-1"
    dynamodb_table = "batch3-demo-state-table"
  }
}

Verify if the state file is stored in the desired bucket or not