Terra Week Challenge Day 5

What are the Modules in Terraform

Modules are reusable infrastructure configuration components in Terraform that can be used to group and organize collections of resources. In your Terraform codebase, they offer a means of producing reusable, shareable, and composable components.

A function in programming is analogous to a module in Terraform. It accepts inputs, executes a series of commands, or generates a series of resources, and then outputs are produced. You can break down complex infrastructure configurations into more controllable and reusable components using modules.

You may have a simple set of Terraform configuration files, such as:

.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf

Calling Modules: Terraform commands will only directly use the configuration files in one directory, which is usually the current working directory. However, your configuration can use module blocks to call modules in other directories. When Terraform encounters a module block, it loads and processes that module's configuration files.

A module that is called by another configuration is sometimes referred to as a "child module" of that configuration.

Local and Remote Modules: Modules can either be loaded from the local filesystem or a remote source. Terraform supports a variety of remote sources, including the Terraform Registry, most version control systems, HTTP URLs, and Terraform Cloud or Terraform Enterprise private module registries.

Modules can be used to organize your Terraform code by grouping related resources. This makes it easier to understand and manage your Terraform code. Modules can also be used to share code with others. By publishing your modules to the Terraform Registry, you can make them available to other Terraform users.

Terraform has its own module registry called the Terraform Registry, which hosts a collection of pre-built modules that you can use directly in your configurations. This makes it easier to discover and reuse community-contributed modules for common infrastructure patterns and providers.

Why do we need modules in Terraform?

Encapsulation: Modules give you the ability to combine several resources and their settings into a single reusable part. You can define output variables to give the calling code information and input variables to allow for customization. This encapsulation aids in keeping your infrastructure codebase organized and up-to-date.

Reusability: By enabling you to build infrastructure components that can be utilized in numerous projects or environments, modules enable reusability. You can deploy infrastructure consistently across several contexts by declaring a module just once and then instantiating it numerous times with diverse input values.

Collaboration and Versioning: Modules can be versioned, which makes it simpler to manage changes and work with others. By tracking and managing module updates, versioning makes infrastructure deployments across teams and projects consistent and foreseeable.

Scalability: You may scale your infrastructure setups effectively using modules. Modules offer a disciplined and organized way to handle the growing number of resources and dependencies as your infrastructure becomes more sophisticated. By dividing information into smaller, more manageable components that can be reused and combined, they help you manage complexity.

Create a module in Terraform to encapsulate reusable infrastructure configuration in a modular and scalable manner. For example - EC2 instance in AWS, Resource group in Azure, and Cloud storage bucket in GCP

First, create a new directory for your module, and within that directory, create a file named aws_ec2_instance.tf, azure_resource_group.tf, and gcp_cloud_storage.tf.

Inside aws_ec2_instance.tf, define the module for the AWS EC2 instance using the module block:

aws_ec2_instance.tf


variable "aws_region" {
  description = "AWS region for the resources"
  type        = string
  default     = "us-west-1"
}

variable "instance_name" {
  description = "Name of the EC2 instance"
  type        = string
}

variable "instance_type" {
  description = "Type of EC2 instance"
  type        = string
}

variable "ami_id" {
  description = "<Your EC2 Instance AMI Key>"
  type        = string
}

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

provider "aws" {
  region = var.aws_region
}

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

Inside azure_resource_group.tf, define the module for the Azure resource group using the module block:

azure_resource_group.tf

variable "resource_group_name" {
  description = "Name of the Azure resource group"
  type        = string
}

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 2.0"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "resource_group" {
  name     = var.resource_group_name
  location = var.azure_region
}

Inside gcp_cloud_storage.tf, define the module for the GCP cloud storage bucket using the module block:

gcp_cloud_storage.tf

variable "bucket_name" {
  description = "Name of the GCP cloud storage bucket"
  type        = string
}

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

provider "google" {
  project = var.gcp_project
  region  = var.gcp_region
}

resource "google_storage_bucket" "cloud_storage_bucket" {
  name     = var.bucket_name
  location = var.gcp_region
}

To use these modules, create a new Terraform configuration file (e.g., main.tf) in a different directory. In main.tf, you can call the modules using the module blocks:

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 2.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

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

provider "azurerm" {
  features {}
}

provider "google" {
  project = "my-gcp-project"
  region  = "us-central1"
}

module "aws_ec2" {
  source         = "./path/to/aws_ec2_instance/module/directory"
  instance_name  = "my-instance"
  instance_type  = "t2.micro"
  ami_id         = "ami-0123456789"
  aws_region     = "us-west-2"
}

module "azure_resource_group" {
  source                = "./path/to/azure_resource_group/module/directory"
  resource_group_name   = "my-resource-group"
  azure_region          = "westus2"
}

module "gcp_cloud_storage" {
  source        = "./path/to/gcp_cloud_storage/module/directory"
  bucket_name   = "my-bucket"
  gcp_project   = "my-gcp-project"
  gcp_region    = "us-central1"
}

The essential providers must be specified in the terraform block of your main configuration file. By doing this, consistency is ensured and conflicts when utilising modules with various provider requirements are avoided. You may centrally monitor and enforce provider requirements for your whole infrastructure by using the required_providers block. This strategy makes it easier for others to comprehend and replicate your configuration.

In this illustration, the source identifies the directories that hold the module configurations by their relative path. The values for the variables can be changed to suit your needs.

The EC2 instance in AWS, the resource group in Azure, and the cloud storage bucket in GCP may now be created by running the terraform init, plan, and apply commands in the directory where main.tf is placed.

What are the best practices for modular composition and module versioning?

Modular Composition: Modular composition in Terraform refers to the practice of breaking down infrastructure configurations into smaller, reusable units called modules. Each module encapsulates a specific set of related resources or configurations.

Modular Versioning: Assigning version numbers to modules in Terraform is known as module versioning. It adheres to semantic versioning concepts, in which the impact of module modifications is indicated by the version number. Module versioning ensures an organized method of managing and controlling changes in infrastructure configurations by assisting users in understanding the compatibility and impact of updates.

Modular Composition Best Practices

  • Single Responsibility Principle: Create modules having a single responsibility by adhering to the single responsibility principle. Each module ought to have a certain group of associated resources or configurations. This encourages modularity and makes it simpler to use, maintain, and reuse modules.

  • Separation of Concerns: Use logical separation of concerns to organize your infrastructure code. Create modules for each of the separate functional domains or infrastructural elements that you have identified. Your codebase can be better managed and organized as a result.

  • Reusability and Composability: Design modules with reusability and composability in mind. To maximize their applicability across other projects or environments, make sure they are not dependent on any particular project features or assumptions. Additionally, check that modules may be combined easily to create bigger systems.

Modular Versioning Best Practices

  • Follow semantic versioning when creating your modules. Depending on the effects of the module's modifications, assign version numbers. Users of the module can better understand the compatibility and effects of changes thanks to semantic versioning.

  • Versions that cannot be changed once they have been released are known as immutable versions. Instead, make breaking changes by releasing a new version. By doing this, users who rely on a certain module version are protected from unpleasant surprises.

  • Maintain a detailed changelog or release notes for your modules that list the modifications performed in each version. When upgrading to new versions, this aids users in understanding what has changed and enables them to make wise selections.

What are the ways to lock Terraform module versions? Explain with code snippets.

In Terraform, you can lock module versions to ensure that the same module versions are used consistently across different environments or deployments. There are several ways to achieve this, depending on the approach you prefer. Here are two common methods:

  1. Using a versions.tf file:

You can create a versions.tf file in your Terraform configuration directory to explicitly specify the module versions to use. Here's an example:

# versions.tf

terraform {
  required_providers {
    aws = ">= 3.0"
  }
}

module "example" {
  source  = "terraform-aws-modules/example/aws"
  version = "2.0.0"
}

In the above example, the versions.tf file includes a terraform block that specifies the required provider version. Additionally, the module block specifies the module source and version to use. By setting the version attribute, you lock the module to a specific version (in this case, version 2.0.0). When you run terraform init, Terraform will download and use that specific module version.

  1. Using a .terraform.lock.hcl file:

You can use a .terraform.lock.hcl file to lock module versions based on the installed versions during terraform init. This file is automatically generated when you run terraform init with a version constraint or specific version specified for a module. Here's an example:

# main.tf

module "example" {
  source  = "terraform-aws-modules/example/aws"
  version = "~> 2.0"

After running terraform init with the above configuration, Terraform generates a .terraform.lock.hcl file with the resolved module versions:

# .terraform.lock.hcl

terraform {
  required_providers {
    aws = {
      version = ">= 3.0"
    }
  }
}

provider "registry.terraform.io/hashicorp/aws" {
  version = ">= 3.0"
  source  = "registry.terraform.io/hashicorp/aws"
}

module "example" {
  source  = "terraform-aws-modules/example/aws"
  version = "2.3.0"
}

The .terraform.lock.hcl file includes the resolved versions for the provider and module, based on the specified constraints or versions in your configuration. When you run subsequent terraform init commands, Terraform will use the versions specified in the lock file, ensuring consistent module versions across deployments.

Using either of these methods helps maintain consistency and avoids unexpected changes in module versions between deployments. It's important to commit the versions.tf or .terraform.lock.hcl file to version control to ensure that the specified module versions are used by everyone working on the Terraform configuration.

Thank you for taking out the time and effort for reading my blog! Your support and feedback are always valuable and appreciated.