Terraform Project Documentation
Introduction
This document provides a step-by-step guide to setting up a Terraform project to deploy a sample "Hello World" application on an EC2 instance. The project uses S3 for storing Terraform state files and DynamoDB for state locking.
Prerequisites
Before you begin, ensure you have the following:
AWS CLI installed and configured with the appropriate permissions.
Terraform installed on your local machine.
An AWS account with access to create S3 buckets, DynamoDB tables, and EC2 instances.
Project Structure
Here is the overview of the project structure
terraform-project/
├── modules/
│ ├── ec2_instance/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── s3_bucket/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── dynamodb_table/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── main.tf
├── providers.tf
├── backend.tf
├── variables.tf
├── outputs.tf
└── workspaces/
├── dev.tfvars
└── prod.tfvars
Configuration Files
Providers
The providers.tf
file specifies the providers required for the project. In this case, we use the AWS provider for deploying infrastructure.
terraform {
required_providers {
aws = {
source = "hashicorp/aws",
version = "~> 5.0"
}
}
}
# AWS Provider
provider "aws" {
region = var.aws_region
}
Variables
The variables.tf
file defines the variables used in the project.
#Main Files Variables
variable "aws_region" {
description = "aws region"
type = string
}
# EC2 Instance Module Variables
variable "ec2_ami_id" {
description = "AMI Id for EC2 instance"
type = string
}
variable "ec2_instance_type" {
description = "Instance type for EC2 instance"
type = string
}
variable "ec2_instance_name" {
description = "Name of the EC2 instance"
type = string
}
variable "vpc_id" {
description = "VPC Id"
type = string
}
variable "ec2_security_group_name" {
description = "Name of the Security group for EC2"
type = string
}
variable "ec2_private_key" {
description = "Path for the EC2 pem file"
type = string
}
# S3 Module Variables
variable "s3_bucket_name" {
description = "Name of the s3 bucket"
type = string
}
# Dynamodb Module Variables
variable "aws_dynamodb_table_name" {
description = "Name of the dynamodb table"
type = string
}
Resources
The main.tf
file defines the resources, including modules for EC2, S3, and DynamoDB.
# Define Locals for using dynamic variables with the variable terraform.workspace ( dev/prod )
locals {
ec2_instance_name = "${terraform.workspace}-${var.ec2_instance_name}"
# s3_bucket_name = "${terraform.workspace}-${var.s3_bucket_name}"
# aws_dynamodb_table_name = "${terraform.workspace}-${var.aws_dynamodb_table_name}"
ec2_security_group_name = "${terraform.workspace}-${var.ec2_security_group_name}"
}
# Defining module for EC2 module
module "ec2_instance_module" {
source = "./modules/ec2-instance"
ec2_ami_id = var.ec2_ami_id
ec2_instance_name = local.ec2_instance_name
ec2_instance_type = var.ec2_instance_type
ec2_private_key = var.ec2_private_key
vpc_id = var.vpc_id
ec2_security_group_name = local.ec2_security_group_name
}
# Defining module for S3 Bucket
module "s3_bucket_module" {
source = "./modules/s3-bucket"
s3_bucket_name = var.s3_bucket_name
}
# Deniging module for DynamoDB
module "dynamodb_table_terraform_lock_module" {
source = "./modules/dynamo-db"
aws_dynamodb_table_name = var.aws_dynamodb_table_name
}
Modules
S3 Bucket Module
main.tf
in modules/s3_bucket
:
resource "aws_s3_bucket" "aws_s3_bucket" {
bucket = var.s3_bucket_name
}
variables.tf
in modules/s3_bucket
:
variable "s3_bucket_name" {
description = "Name of the s3 bucket"
type = string
}
outputs.tf
in modules/s3_bucket
:
output "aws-s3-s3_bucket_name" {
value = aws_s3_bucket.aws_s3_bucket.bucket
}
DynamoDB Table Module
main.tf
in modules/dynamodb_table
:
resource "aws_dynamodb_table" "aws_terraform_lock_dynamodb_table" {
name = var.aws_dynamodb_table_name
hash_key = "LockID"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S"
}
tags = {
"Name" = "Dev-Table"
}
}
variables.tf
in modules/dynamodb_table
:
variable "aws_dynamodb_table_name" {
description = "Name of the dynamodb table"
type = string
}
outputs.tf
in modules/dynamodb_table
:
output "aws_dynamodb_table_name" {
description = "Name of the dynamodb table"
value = aws_dynamodb_table.aws_terraform_lock_dynamodb_table.name
}
EC2 Instance Module
main.tf
in modules/ec2_instance
:
resource "aws_security_group" "web-app-security-group" {
name = var.ec2_security_group_name
vpc_id = var.vpc_id
}
resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4_22" {
security_group_id = aws_security_group.web-app-security-group.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 22
ip_protocol = "tcp"
to_port = 22
}
resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4_80" {
security_group_id = aws_security_group.web-app-security-group.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 80
ip_protocol = "tcp"
to_port = 80
}
resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4_all" {
security_group_id = aws_security_group.web-app-security-group.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" {
security_group_id = aws_security_group.web-app-security-group.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1" # semantically equivalent to all ports
}
resource "aws_instance" "aws_ec2_instance" {
ami = var.ec2_ami_id
instance_type = var.ec2_instance_type
key_name = "first-instance-key-pair"
vpc_security_group_ids = [ aws_security_group.web-app-security-group.id ]
tags = {
Name = var.ec2_instance_name
}
provisioner "remote-exec" {
inline = [
"sudo yum update -y",
"sudo yum install -y httpd",
"sudo systemctl start httpd",
"sudo systemctl enable httpd",
"echo '<h1>Hello, World!</h1>' | sudo tee /var/www/html/index.html"
]
connection {
user = "ec2-user"
host = self.public_ip
private_key = file(var.ec2_private_key)
}
}
}
remote-exec Provisioner:
The remote-exec
provisioner is used to run scripts or commands on a remote machine over SSH or WinRM connections. It's often used to configure or install software on provisioned instances.
In the above example, the remote-exec
provisioner connects to the AWS EC2 instance using SSH and runs a series of commands to update the package repositories, install Apache HTTP Server, and start the HTTP server.
variables.tf
in modules/ec2_instance
:
variable "ec2_ami_id" {
description = "AMI Id for EC2 instance"
type = string
}
variable "ec2_instance_type" {
description = "Instance type for EC2 instance"
type = string
}
variable "ec2_instance_name" {
description = "Name of the EC2 instance"
type = string
}
variable "ec2_private_key" {
description = "EC2 Private Key Path"
type = string
}
variable "ec2_security_group_name" {
description = "Name of the Security group for EC2"
type = string
}
variable "vpc_id" {
description = "VPC Id"
type = string
}
outputs.tf
in modules/ec2_instance
:
output "aws_ec2_instance_public_ip" {
description = "AWS EC2 instance public ip"
value = aws_instance.aws_ec2_instance.public_ip
}
Backend Configuration
The backend.tf
file configures the backend for storing the Terraform state in an S3 bucket and using DynamoDB for state locking.
terraform {
backend "s3" {
bucket = "web-terraform-backend-bucket"
key = "terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "web-terraform-lock-table"
}
}
Workspaces
Terraform workspaces allow you to manage multiple environments. The dev.tfvars
and prod.tfvars
files contain variable values specific to the dev and prod environments.
dev.tfvars
in workspaces/
:
# Main Variables
aws_region = "us-east-1"
# EC2 Module Instance Variables
ec2_ami_id = "ami-00beae93a2d981137"
ec2_instance_type = "t2.micro"
ec2_instance_name = "Web-App"
ec2_private_key = "./private-keys/first-instance-key-pair.rsa"
vpc_id = "vpc-09c2d21a970b5104a"
ec2_security_group_name = "Web-App-Security-Group"
# S3 Module Variables
s3_bucket_name = "web-terraform-backend-bucket"
# Dynamodb Module Variables
aws_dynamodb_table_name = "web-terraform-lock-table"
Note: Create a prod.tfvars under workspaces folder and define values which should pass when deploying infrastructure.
Deploying the Project
Step 1: Initialize Terraform
Open your terminal and navigate to your project directory. Run the following command to initialize Terraform, which will download the necessary provider plugins and set up the backend configuration.
terraform init
Step 2: Select or Create a Workspace
Terraform workspaces allow you to manage different environments (e.g., development, production). Create and switch to the desired workspace using the following commands:
terraform workspace new dev
terraform workspace select dev
Step 3: Plan the Configuration
Run the following command to plan the Terraform configuration. This command will show the resources defined in your configuration files how many resources is going to add or how many resources going to update or resources count to destroy.
terraform plan -var-file="./workspaces/dev.tfvars"
Step 3: Apply the Configuration
Run the following command to apply the Terraform configuration. This command will create the resources defined in your configuration files.
terraform apply -var-file="./workspaces/dev.tfvars"
When prompted to confirm the action, type yes
and press Enter.
Deployed Infrastructure in AWS
Accessing the Application
Step 1: Get the Public IP of the EC2 Instance
After the deployment is complete, you can get the public IP address of the EC2 instance.
Step 2: Open the Web Application
Open your web browser and navigate to http://<public_ip>
, replacing <public_ip>
with the actual public IP address obtained in the previous step. You should see the "Hello, World!" message.
Conclusion
This document provided a comprehensive guide to setting up a Terraform project to deploy a "Hello World" application on an EC2 instance, using S3 for state storage and DynamoDB for state locking. Adjust the configurations as needed for your specific use case. By following these steps, you should be able to replicate and understand the setup and deployment process for your Terraform project.