Terraform Modules,modular composition and module versioning

Terraform Modules,modular composition and module versioning

TerraWeek Day 5

What are modules in Terraform?

A Terraform module is a collection of standard configuration files in a dedicated directory. Terraform modules encapsulate groups of resources dedicated to one task, reducing the amount of code you have to develop for similar infrastructure components.

Some say that Terraform modules are a way of extending your present Terraform configuration with existing parts of reusable code reducing the amount of code you have to develop for similar infrastructure components. Others say that the Terraform module definition is a single or many .tf files stacked together in their own directory. Both are correct.

Module blocks can also be used to force compliance on other resources—to deploy databases with encrypted disks, for example. By hard-coding the encryption configuration and not exposing it through variables, you’re making sure that every time the module is used, the disks are going to be encrypted.

A typical module would resemble this:

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

Types of Terraform Modules

With Terraform, you are most likely to come across one of three main categories of modules:

  1. Root Module

  2. Child Module

  3. Published Modules

module types

why do we need modules in Terraform?

A Terraform module allows you to create logical abstraction on the top of some resource set. In other words, a module allows you to group resources together and reuse this group later, also sharing the modules between teams or even in the community.

Assuming you have a EC2 server that hosted in AWS, what AWS resources this EC2 server is using?

  • EC2 instance itself

  • A EBS volume for store application data

  • A elastic IP for public access

  • A security group for ingress and egress traffic

  • A VPC

  • A Subnet

Module structure

Modules that are intended to be reused use the same Terraform code and code style guidelines as the root module we wrote. Generally speaking, a complete module will look like:

Let’s explore the items one by one:

  • README.md: It describes the purpose of the module. It should be written in Markdown syntax. There is no need to describe the input and output of the module, because the tool automatically collects the relevant information. If external files or images are referenced in README, be sure to use an absolute URL path with a specific version number to prevent pointing to the wrong version in the future.

  • LICENSE: It describes the use of the module. If you want to release a module publicly, it is best to consider including an explicit license agreement file, many organizations will not use modules without an explicit license agreement.

  • examples: As its name suggest, folder contains examples. This is optional.

  • variables.tf: It contains all of the module's input variables. Input variables should have a clear description of their purpose.

  • outputs.tf: It contains all the output values ​​of the module. The output value should have a clear description of the purpose.

  • modules: Embedded module folder, for the purpose of encapsulating complexity or reusing code, we can create some embedded modules in the modules subdirectory. All embedded modules that contain a README file can be used by external users; modules without README files are considered to be used only within the current module (optional).

  • main.tf : The main entry point for the module. For a simple module, all resources can be defined in it; if it is a more complex module, we can distribute the created resources to different code files, but the code referring to the embedded module should still be kept in main.tf .

If a module contains multiple embedded modules, they should avoid referencing each other, and the root module is responsible for combining them.

Besides a complete module structure, sometime for convenient, it is okay to have a minimal module looks like following:

What are the benefits of using modules in Terraform?

  1. Code Repetition:- As your Terraform infrastructure grows in scale, the code will need to do so as well. Copy-pasting an entire stack of code whenever you need more than a single instance of something isn’t scalable or efficient. It’s a waste of many things, time in particular.

  2. **Lack of Code Clarity:-**Copy-pasting isn’t clean either. Good code is readable, and great code documents itself. A modular approach addresses all those pesky details. A configuration of connecting modules dedicated to particular tasks is much easier to read and understand.

  3. **Lack of Compliance:-**When you create a Terraform module in compliance with appropriate standards and best practices, whenever you reuse it, it follows the same right pattern. No matter if it’s encryption, redundancy, or lifecycle policies—practices configured inside the module will be enforced, so that you won’t have to do it again personally.

  4. Human Error:- When you copy-paste or create a group of resources from scratch, it’s easy to make a mistake, like rewriting or renaming something. To make sure that doesn’t happen, create a single Terraform module, test it, then use it in multiple places to check whether all of the elements are correct. It’s easier to check and test what you type into a single block than scroll, jumping from one place to another, and so on.

    As you can see, there are a lot of advantages to using Terraform modules, so long as they aren’t overused. Find the right balance and keep it.

Create/Define a module in Terraform to encapsulate reusable infrastructure configuration in a modular and scalable manner. E.g. EC2 instance in AWS

Step 1:- First we need to create a separate directory for this then we need to create EC2 instance in AWS for that need to create ec2_instance.tf

vi 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 = "AMI ID for the EC2 instance"
  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
  }
}

Step 2:- Now we need to create main.tf

vi main.tf

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

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

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"
}

Step 3:- we need to run terraform init -This command will scan your tf files in that folder and install all the required automation things

Now we need to run terraform plan -This command will create an execution plan for terraforming, the things that will be installed, the names, and the properties added.

terraform apply -The actual execution and automation happen in this command.

Module Composition

In Terraform, module composition refers to the practice of combining multiple reusable modules together to create a more complex infrastructure configuration. It allows you to build larger and more sophisticated infrastructure deployments by leveraging the modular nature of Terraform.

Module composition follows a hierarchical structure where a module can include other modules as its building blocks. This approach promotes code reusability, maintainability, and scalability. By breaking down your infrastructure into smaller, reusable modules, you can manage them independently and compose them to form a larger infrastructure.

The process of module composition involves using the module block in your Terraform configuration files to instantiate and configure modules. You can pass input variables to the modules and use output values from one module as input to another.

Here's a basic example of module composition in Terraform:

hclCopy codemodule "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
}

module "app_server" {
  source = "./modules/app_server"
  subnet_id = module.vpc.subnet_id
  instance_type = "t2.micro"
}

module "database" {
  source = "./modules/database"
  subnet_id = module.vpc.subnet_id
}

In the example above, three modules are composed together to create an infrastructure configuration. The vpc module defines a virtual private cloud, the app_server module provisions an application server, and the database module creates a database instance. The subnet_id output from the vpc module is used as an input variable for the app_server and database modules.

By composing modules, you can create more complex infrastructure setups while maintaining modularity and reusability across your Terraform codebase.

module versioning

Module versioning in Terraform refers to the practice of assigning specific versions to modules to ensure consistency and stability in infrastructure deployments. When using modules in Terraform, you can specify the version of a module you want to use, allowing you to control which version of the module is used in your infrastructure configuration.

Module versions are defined using version constraints, which can include specific versions, version ranges, or other constraints. Terraform supports a variety of version constraint syntax, including exact versions, version ranges, and operators like greater than or equal to (>=) and less than or equal to (<=).

Module versions can be managed using a version control system like Git or a module registry service like the Terraform Registry. When you reference a module in your Terraform configuration, you can specify the module source along with the desired version.

Here's an example of specifying a module version in Terraform:

hclCopy codemodule "example" {
  source  = "git::https://github.com/example/repo.git?ref=v1.2.0"
}

In the example above, the module "example" is sourced from a Git repository, and the ?ref=v1.2.0 specifies that version 1.2.0 of the module should be used.

Module versioning allows you to control when and how module updates are applied to your infrastructure. By specifying a specific version or version constraint, you can ensure that your infrastructure remains consistent and predictable, even as new versions of the module are released.

When making changes to a module, it's good practice to follow semantic versioning principles, where backward-compatible changes are considered patch versions (v1.0.1), backward-compatible feature additions are considered minor versions (v1.1.0), and backward-incompatible changes are considered major versions (v2.0.0). This helps users of your module understand the impact of updating to a new version.

Overall, module versioning in Terraform provides a mechanism to control and manage the lifecycle of reusable infrastructure components, ensuring stability and reproducibility in your deployments.

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

There are several ways to lock Terraform module versions to ensure that the same module version is used consistently across different deployments or team members. Here are the common methods:

  1. Explicit Version Constraint: You can specify an explicit version constraint in your Terraform configuration files. For example:

     hclCopy codemodule "example" {
       source  = "git::https://github.com/example/repo.git?ref=v1.2.0"
     }
    

    In this example, the module version v1.2.0 is explicitly specified in the module source URL. This ensures that Terraform always uses that specific version of the module.

  2. Terraform Configuration Lock File: Terraform can generate a lock file, often named terraform.lock.hcl or terraform.tf.lock.hcl, which captures the exact versions of all modules and providers used in your configuration. This file can be committed to version control, allowing you to recreate the exact infrastructure state with the specified module versions. You can generate the lock file using the terraform init command.

  3. Module Registry: If you're using a module registry service like the Terraform Registry, you can specify the module version directly in the module source. For example:

     hclCopy codemodule "example" {
       source  = "registry.example.com/example/repo/aws"
       version = "1.2.0"
     }
    

    In this case, the module version 1.2.0 is specified explicitly in the version attribute. The module registry ensures that the specified version is used for the module.

  4. Private Module Registries: If you're using a private module registry, you can set up access controls and permissions to manage module versions and restrict access to specific versions.

By using these locking mechanisms, you can ensure that the same version of a module is consistently used, reducing the chances of unexpected changes or compatibility issues in your infrastructure deployments. It helps to maintain stability, reproducibility, and predictability across different environments and team members working on the same project.

Devops#devops,#TerraWeekDay5

Thank you for reading!! I hope you find this article helpful!!

if any queries or corrections to be done to this blog please let me know.

Happy Learning!!

Saikat Mukherjee

Thank You Images - Free Download on Freepik

Did you find this article valuable?

Support Saikat Mukherjee by becoming a sponsor. Any amount is appreciated!