Enhance Your IaC Strategy with Modular Terraform Configurations

Introduction

Terraform is a robust infrastructure as code (IaC) tool that empowers you to define and manage your infrastructure through code. By modularizing your Terraform configuration, you can break down your setup into reusable, manageable, and scalable components. This tutorial will walk you through creating a modularized Terraform setup, covering essential resources such as EC2 instances, S3 buckets, VPCs, and RDS instances

Project Structure

We'll start by defining our project structure. Here’s how our directory layout will look:

.
├── main.tf
├── terraform.tf
├── variables.tf
├── modules
│   ├── ec2
│   │   ├── main.tf
│   │   ├── variables.tf
│   ├── s3
│   │   ├── main.tf
│   │   ├── variables.tf
│   ├── vpc
│   │   ├── main.tf
│   │   ├── variables.tf
│   ├── rds
│   │   ├── main.tf
│   │   ├── variables.tf

Main Configuration Files

main.tf

The main.tf file is where we define our primary Terraform configuration. This file calls the modules we have created for our infrastructure.

module "dev_infra" {
  source         = "./modules/ec2"
  instance_count = 3
  my_env         = "dev"
  instance_type  = "t2.micro"
  ami            = "ami-09040d770ffe2224f"
}

module "prd_infra" {
  source         = "./modules/ec2"
  instance_count = 3
  my_env         = "prd"
  instance_type  = "t2.medium"
  ami            = "ami-09040d770ffe2224f"
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "demo-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-2a"]
  private_subnets = ["10.0.1.0/24"]
  public_subnets  = ["10.0.101.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = true

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

module "rds" {
  source              = "./modules/rds"
  instance_identifier = "my-rds-instance"
  instance_class      = "db.t2.micro"
  engine              = "mysql"
  engine_version      = "8.0"
  username            = "admin"
  password            = "password"
  db_name             = "mydb"
  allocated_storage   = 20
  my_env              = "dev"
}
  • dev_infra and prd_infra modules: These modules create EC2 instances for development and production environments.

  • vpc module: This module sets up a Virtual Private Cloud (VPC) using a publicly available VPC module.

  • rds module: This module creates an RDS instance.

terraform.tf

The terraform.tf file configures the Terraform backend and provider.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.50.0"
    }
  }
  backend "s3" {
    bucket         = "first-demo-state-bucket"
    key            = "terraform.tfstate"
    region         = "us-east-2"
    dynamodb_table = "first-demo-state-table"
  }
}

provider "aws" {
  region = "us-east-2"
}
  • terraform block: Specifies the required providers and backend configuration.

  • provider block: Configures the AWS provider.

variables.tf

The variables.tf file defines the variables used in the main configuration.

variable "ami" {
  type        = string
  description = "this will store ami for my instance"
}

variable "instance_count" {
  type        = number
  description = "this is the number of instances you need"
}

variable "instance_type" {
  type        = string
  description = "instance type"
}

variable "my_env" {
  type        = string
  description = "this will store the env for my infra"
}
  • ami: The Amazon Machine Image ID for the EC2 instances.

  • instance_count: The number of EC2 instances to create.

  • instance_type: The type of EC2 instances to create.

  • my_env: The environment (e.g., dev, prd).

Module Configuration Files

EC2 Module

modules/ec2/main.tf

resource "aws_instance" "my_demo_instance" {
  count         = var.instance_count
  ami           = var.ami
  instance_type = var.instance_type
  tags = {
    Name = "${var.my_env}-instance"
    env  = var.my_env
  }
}

This file defines an AWS EC2 instance resource. The aws_instance block creates instances based on specified variables like AMI, instance type, and instance count. It also tags instances with the environment name. The count parameter allows for creating multiple instances, while the tags parameter helps in organizing and managing the instances by associating them with the environment they belong to. This modular approach makes it easy to deploy and manage EC2 instances in different environments by simply changing variable values.

modules/ec2/variables.tf

variable "ami" {
  type        = string
  description = "this will store ami for my instance"
}

variable "instance_count" {
  type        = number
  description = "this is the number of instances you need"
}

variable "instance_type" {
  type        = string
  description = "instance type"
}

variable "my_env" {
  type        = string
  description = "this will store the env for my infra"
}

This file declares variables used in the EC2 module. It includes variables for the AMI ID, instance count, instance type, and environment name, making the configuration flexible and reusable. By externalizing these parameters, the module can be reused across different environments and projects without modifying the main configuration file. This promotes consistency and reduces the potential for errors, as the same core logic is applied universally with environment-specific parameters.

S3 Module

modules/s3/main.tf

  •     resource "aws_s3_bucket" "my_bucket" {
          bucket = "${var.my_env}-first-terraform-app-bucket"
          tags = {
            Name = "${var.my_env}-first-terraform-app-bucket"
          }
        }
    

This file defines an AWS S3 bucket resource. The aws_s3_bucket block creates a bucket with a name and tags based on the environment name, making it easy to identify and manage across different environments. Using the my_env variable ensures that bucket names are unique and environment-specific, preventing conflicts and aiding in resource organization. This setup is particularly useful for managing multiple buckets across various stages of development, testing, and production environments.

modules/s3/variables.tf

  •     variable "my_env" {
          type        = string
          description = "this will store the env for my infra"
        }
    

This file declares a variable for the environment name, which is used to dynamically name and tag the S3 bucket, ensuring consistency across different environments. By leveraging a single variable, this module ensures that all related resources within an environment are consistently labeled, simplifying management and reducing the risk of misconfiguration. This approach enhances the clarity of resource allocation and usage across multiple environments.

VPC Module

modules/vpc/main.tf

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = var.name
  cidr = var.cidr

  azs             = var.azs
  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets

  enable_nat_gateway = var.enable_nat_gateway
  enable_vpn_gateway = var.enable_vpn_gateway

  tags = var.tags
}

This file uses the terraform-aws-modules/vpc/aws module to create a VPC. It sets various parameters such as VPC name, CIDR block, availability zones, subnets, NAT gateway, and VPN gateway options, all configured through variables. This modular design simplifies the creation of a VPC by using a well-maintained external module. By specifying these parameters through variables, the VPC configuration can be easily adapted to different requirements and environments, ensuring a scalable and flexible network infrastructure.

modules/vpc/variables.tf

variable "name" {
  type = string
}

variable "cidr" {
  type = string
}

variable "azs" {
  type = list(string)
}

variable "private_subnets" {
  type = list(string)
}

variable "public_subnets" {
  type = list(string)
}

variable "enable_nat_gateway" {
  type = bool
}

variable "enable_vpn_gateway" {
  type = bool
}

variable "tags" {
  type = map(string)
}

This file declares variables for configuring the VPC module, including VPC name, CIDR block, availability zones, private and public subnets, NAT and VPN gateway options, and tags for resource identification. These variables allow for a highly customizable VPC setup that can accommodate a variety of network architectures. By using these variables, organizations can quickly replicate network configurations across multiple environments while maintaining control over key parameters.

RDS Module

modules/rds/main.tf

resource "aws_db_instance" "rds_instance" {
  instance_identifier = var.instance_identifier
  instance_class      = var.instance_class
  engine              = var.engine
  engine_version      = var.engine_version
  username            = var.username
  password            = var.password
  db_name             = var.db_name
  allocated_storage   = var.allocated_storage

  tags = {
    Name = "${var.my_env}-rds-instance"
    env  = var.my_env
  }
}

This file defines an AWS RDS instance resource. The aws_db_instance block creates an RDS instance with specified parameters like instance identifier, class, engine, engine version, username, password, database name, and allocated storage. It also tags the instance with the environment name. This configuration ensures that database instances are appropriately configured and tagged, facilitating easier management and monitoring. The use of variables allows for easy adjustments to database configurations as requirements change.

modules/rds/variables.tf

variable "instance_identifier" {
  type        = string
  description = "The identifier for the RDS instance"
}

variable "instance_class" {
  type        = string
  description = "The class of the RDS instance"
}

variable "engine" {
  type        = string
  description = "The database engine to use"
}

variable "engine_version" {
  type        = string
  description = "The version of the database engine"
}

variable "username" {
  type        = string
  description = "The username for the RDS instance"
}

variable "password" {
  type        = string
  description = "The password for the RDS instance"
}

variable "db_name" {
  type        = string
  description = "The name of the database"
}

variable "allocated_storage" {
  type        = number
  description = "The allocated storage for the RDS instance in GB"
}

variable "my_env" {
  type        = string
  description = "This will store the environment for the RDS instance"
}

This file declares variables used in the RDS module, covering the instance identifier, class, engine, engine version, username, password, database name, allocated storage, and environment name. These variables allow for a flexible and customizable RDS instance configuration. By externalizing these parameters, this module can be reused to deploy RDS instances with different specifications across multiple environments, ensuring consistency and reducing the likelihood of configuration errors.

Conclusion

These modular Terraform configurations provide a structured approach to defining AWS resources. By using variables, they enable flexibility and reusability, allowing for easy customization and scaling of infrastructure components across different environments. This modular design promotes best practices in infrastructure as code (IaC), enabling teams to manage and provision resources efficiently and consistently. The use of modules also helps in maintaining a clean and organized codebase, making it easier to manage, review, and collaborate on infrastructure configurations. Overall, this approach streamlines the deployment process and enhances the scalability and maintainability of cloud infrastructure.