Terraform Modules

Complete Terraform Tutorial Part – 5 – Terraform Modules

As part of or Complete Terraform Tutorial, this is Part – 5 explaining about Terraform Modules and how to create Re-Useable Terraform Modules. In our Terraform Tutorial series, we have already discussed on Terraform Modules as an introduction (Terraform Modules – Digitalvarys). This article is about learning how to create re-useable Terraform Modules and how can we use it. Especially, when to use the module and folder structures of the Terraform.

So Far!

This is Part – 5 of the Complete Terraform Tutorial Series. Before taking this, We recommend you to take a look at Part -4, Part -3, Part -2 and Part -1

Complete Terraform Tutorial Part – 4 – Terraform State

Detailed explanation on Terraform State and the various options of it

Terraform Folder Structure

To create a module in your folder structure, we need to create a folder which you need to named called ‘module’ in your root of the Terraform directory. Inside the directory, we can have one or more .tf files. So, from now onwards, you can call the modules mentioned in the folder by using the module block in your configuration file. The .tf file which you are defining inside the module folder will use all the concepts of configuration files but most commonly, Input Variables, Output Variables, and Resources.

So, the folder structure can be structured as mentioned below. Here, we are going to use ‘tree’ command-line tool to see the folder structure and layout of the Terraform Project.

$ tree example/
example/
├── README.md
├── main.tf
├── networking
│	├── README.md
│	├── region
│	│	├── main.tf
│	│	├── outputs.tf
│	│	├── security_group.tf
│	│	├── subnets.tf
│	│	├── terraform.template.tfvars
│	│	├── variables.tf
│	│	└── vpc.tf
│	├── regions.tf
│	├── subnet
│	│	├── outputs.tf
│	│	├── security_group.tf
│	│	├── subnet.tf
│	│	├── terraform.template.tfvars
│	│	└── variables.tf
│	└── variables.tf
├── outputs.tf
└── variables.tf

3 directories, 19 files

Within the module, you can also call other modules using the Configuration block as follows.

module " us-east-1" {
  source = "./network"
  cidr_block = "10.1.0.0/18"
}

So, when you have your own module inside the root directory, you can call the name of the folder as the module name. This means, the main.tf will have the block as mentioned above and that will get the values from the folder called network. When we call the module, It will look for the main.tf inside the module folder and executes the code mentioned in that.

Also, the above is an example of a Nested Module. Which means you can have a module inside the module. In this example, the folder Network is a module and inside that, region and subnet are other submodules of the same.  

Terraform Registry

Just like Docker Registry or Ansible Galaxy, you can have your own Registry or space where you can publish your existing module and reuse it. Basically, Terraform Registry is available in the following ways.

  • Terraform Registry – Public Registry (Hashicorps Own)
  • Terraform Cloud -Private Registry
  • Your Own Registry – With Terraform API.

Terraform Registry – Public Registry

It is for the public where everyone can access this registry. Terraform gives you a fantastic option to browse, filter, find the author, and the number of downloads. This will give you a detailed dependency tree and all available Input and Output variables.

Terraform Cloud – Private Registry

Terraform Cloud is not only for the Registry, but it will give you the platform to run and manage your Terraform Configuration file on the Cloud. Since the Terraform Privacy matters for the Enterprise, terraform offers a Private environment for every user which includes Private Registry with all the features of Terraform Public Registry.

Your Own Registry.

If you still need your Terraform Registry to need to be on your own premises, you can have your own web server and use Terraform API to publish and reuse from your own Infrastructure. We will discuss more on how to create your own Terraform Registry in our upcoming article.

However, there is always some alternative for everything. Terraform supports modules can be used from various other sources like Your Own Computer, GitHub, S3 Buckets, BitBucket, and more.

Say,

# Local Path
module "example-module" {
  source = "./example-module"
}

# GitHub
module "example-module" {
  source = "github.com/hashicorp/example"
}

# BitBucket
module "example-module" {
  source = "bitbucket.org/hashicorp/terraform-example-module-aws"
}

# Your Own Git Repo
module "example-module" {
  source = "git::https://example.com/example-module.git"
}

# Your Own Mercurial Repo
module "example-module" {
  source = "hg::http://example.com/example-module.hg"
}

# S3 Bucket
module "example-module" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/example-modules/example-module.zip"
}

Different ways to improve Terraform Module.

Module Composition

Terraform Configuration with simple straight forward module declaration is so simple and we don’t have to nest any more variables used in the module to call it. But when it comes to nested Modules used in the Terraform configuration, we need to handle it in such a way that it should be reused at the same time easy to understand.

Hence, we must write the modules in such a way that it should take components of different modules and make a simple but bigger system. This functionality is an example of Terraform Module Composition

For example, assume we have AWS provider and we have an application cluster to configure the network within AWS. We have the following Terraform configuration code with the module,

module "network" {
  source = "./modules/aws-network"

  base_cidr_block = "10.0.0.0/16"
}

module "application_cluster" {
  source = "./modules/aws-application_cluster"

  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.subnet_ids
}

In this, we have two different modules. One is Network Module which defines AWS Network properties, and another is your own application cluster configurations. So, as mentioned, we are mentioning the VPC ID and Subnet ID from the module which is declared in the same project directory.

Dependency inversion

When we have Nested Modules, we need to manage dependency. When we have dependency talk, we need some standards and principles to regulate and format the dependency. So, Dependency inversion will help us creating such decouple modules. Dependency inversion says,

  • High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  • Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

As per this, we car re-write the above Terraform Configuration as:

data "vpc" "main" {
  tags = {
    Environment = "production"
  }
}

data "subnet_ids" "main" {
  vpc_id = data.aws_vpc.main.id
}

module "application_cluster" {
  source = "./modules/aws-application_cluster"

  vpc_id     = data.vpc.main.id
  subnet_ids = data.subnet_ids.main.ids
}

So, in this code, network module can be abstracted as data and second module application_cluster is now dependent on the data.

Conclusion

When we use Terraform Modules in the right way, it makes writing Terraform configuration easier. In this article, we have discussed how to create re-useable terraform modules. This is our fifth part of the Complete Terraform Tutorial series of DigitalVarys. In our upcoming article, we will discuss how to push the Modules in Private and Public Terraform Registry. Also, we will discuss more Terraform Providers, Backend, and more features as parts of our complete terraform tutorial. Stay tuned and subscribe DigitalVarys for more articles and study materials on DevOps, Agile, DevSecOps, and App Development.

Leave a Reply