Modules

Sign Up to Build

About this Architecture

Here is some information about this architecture.

How to Build This Solution

Here are the steps you can follow to build this solution on your own.

According to the official Terraform (TF) documentation, “A module is a container for multiple resources that are used together” [1 ]. In simpler words, modules help developers to reuse TF code in their projects without having to write TF resources again and again.

Modules are a way of packaging and reusing common code in the project.

For example, suppose you have a standard network design for your AWS accounts and projects. Instead of writing the long-form resource definitions in each project, you can create a networking module that standardizes your deployment. When a new project starts, you can essentially import that network design into the project with little effort and go on your way.

There are many advantages of using modules. Modules provide consistency by ensuring best practices when implementing configurations. If written well and documented, modules are easy to use and follow. If you’re in an organizational setting where standards are a must, you can create modules that comply and simply reuse them on projects. Lastly, Terraform modules helps organize configuration by structuring complex infrastructure into logical components [2 ].

What is a Module?

Let’s use a simple example to explore what a module is. Let’s say that you want to create a module that creates an S3 bucket. You create a Terraform project, create a main.tf file in the root directory, and add this code:

resource "aws_s3_bucket" "b" {
  bucket = var.bucket_name
  acl    = "private"

  tags = {
    Name        = var.bucket_name
    Environment = var.env
  }
}

As you can see, the code above references two values, var.bucket_name, and var.env. Let’s say you go through the process of publishing this to the Terraform Registry (more on that later) with the title ‘my-very-simple-s3-bucket’.

To use this module in new Terraform projects, you could simply add this to your project main.tf. The code below defines the module block. The source attribute points to the location of the published module.

module "my_s3_bucket" {
  source = ".../my-very-simple-s3-bucket"
  bucket_name = "yes-again-did-it"
  env = "dev"
}

This is a very basic example, but it demonstrates what modules are. The real power of modules comes when building more complex functionality.

How to Build a Module

Creating a Terraform module is straightforward. The directories and files will look like a normal Terraform project. However, Terraform has documented best practices to follow when creating modules. We will summarize them here:

  • Root Module: Place the relevant Terraform files in root module. The recommended files at a minimum are main.tf, variables.tf, outputs.tf. You can definitely create more complex file and directory structures

  • README File. This is a markdown file that describes what the module is and what it is used for. You don’t need to document the inputs and outputs in this file; the Terraform tooling will do this automatically

  • LICENSE: The license under which publishing this under (if public)

  • Variable & Output Descriptions: Use the description attribute for variables and outputs for good documentation

  • Nested Modules: If modules are used in the module, put them in the /modules directory

If you do want to create modules, the Module Composition article is a great resource.

How Modules are Shared

Modules can be shared publicly through the Terraform Registry, privately via Terraform Cloud, or via a version control system like GitHub/GitLab.

The Terraform Registry is a platform where anyone can discover a wide range of TF providers and modules. On the public Terraform Registry, anyone can publish and use providers and modules. Others will be able to access the module like below by creating a module configuration block and specifying the source of the module.

module "lambda" {
  source = "hashicorp/lambda/aws"

  # rest of configuration
}

There are private registries as well in which will provide the same benefits as the public registry. Modules can be saved in the private registry (e.g. GitHub) and it will only be accessible by its owner. Companies and teams can build their own registries of confidential modules.

Project Child Modules

Sometimes you’ll see a project that has a /modules directory. This directory often contains child modules that are local to the project you’re working on. For example, there might be a /modules/networking folder that has all of the network resources for the project. In this scenario, the root module main.tf file can reference the child networking module like so:

module "networking" {
  source    = "./modules/networking"

  #... other settings
} 

How Terraform Passes Data Between Modules

Let’s say that you have a networking module that creates a VPC and several subnets. You need to reference the network outputs, such as subnet IDs and ARNs, in other parts of your project, like EC2 instance settings. How does the data go from the networking module to the other resources?

Terraform does this via output values and variables. The networking module can generate outputs like this:

# networking module outputs.tf

output "vpc" {
  value = module.vpc
}

output "sg" {
  value = {
    web_alb_sg      = module.web_alb_sg.security_group_id
    web_server_sg   = module.web_server_sg.security_group_id
    app_alb_sg      = module.app_alb_sg.security_group_id
    app_server_sg   = module.app_server_sg.security_group_id
    db_sg           = module.db_sg.security_group_id
  }
}

Then, these values can be passed to the root module, and then down into other child modules, like this:

# root module main.tf

module "database" {
  source    = "./modules/database"

  vpc = module.networking.vpc
  sg  = module.networking.sg
}

You’ll get experience working with these concepts throughout remaining labs.

Lab Time!

Let’s put your newfound knowledge to work with this awesome lab. You will create and use a local child networking module. The networking module will be used by an EC2 instance in the main project file.

Get Your AWS Credentials

If you're using the Skillmix Labs feature, open the lab settings (the beaker icon) on the right side of the code editor. Then, click the Start Lab button to start hte lab environment.

Wait for the credentials to load. Then run this in the terminal.

Be sure to enter in your own access key and secret key and name your profile 'smx-lab'.

$ aws configure --profile smx-lab
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: us-west-2
Default output format [None]: 

Note: If you're using your own AWS account you'll need to ensure that you've created and configured a named AWS CLI profile named smx-lab.

Create a Working Directory & Files

Let’s start off by creating a working directory and the files that we’ll need to use. Open your terminal or command prompt and create a working directory.

# create the directory
$ mkdir my-new-module

# create the files needed
$ touch LICENSE README.md main.tf variables.tf outputs.tf 

Create the Main Config

Next, we will create the main configuration. This configuration will define the project settings, the provider info, and define one EC2 instance.

Open the main.tf file and add this code:

terraform {
  required_version = ">= 0.15"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.46"
    }
  }
}

provider "aws" {
  region  = "us-west-2"
  profile = "skillmix-lab"
}

# defining out networking module
module "networking" {
  source    = "./modules/networking"
} 

# creating an ec2 instance 
resource "aws_instance" "web_server" {
  ami           = "ami-0cf6f5c8a62fa5da6"
  instance_type = "t2.micro"
  vpc_security_group_ids = [module.networking.sec_group.id]
  subnet_id              = module.networking.subnet.id

  tags = {
    Name = "skillmix-lab-instance"
  }
}

Code Review

  • First, we set the terraform and provider configs; this should be familiar at least.

  • Next, we create the module block for the networking module. We set the source as a directory within the root project. You’ll be creating this directory soon.

  • Finally, we create an EC2 instance with the aws_instance block. Note the vpc_security_group_ids and subnet_id settings attribute values; these are references to the networking module that you will create next.

Create the Networking Module

Now let’s get into the module part. First you’ll create the directory structure for the module. Then, you’ll create the module resources. Then, we’ll show you how to connect the module to the rest of the project through variables and outputs.

Create the Module Directory & Files

Let’s start by creating a place to work.

# in the root module
$ mkdir -p modules/networking

# cd into the directory
$ cd modules/networking

# create the files we need
# touch main.tf outputs.tf

Create the VPC Config

Open the ./modules/networking/main.tf file and add the following code:

resource "aws_vpc" "my_vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public_subnet" {
  vpc_id            = aws_vpc.my_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-west-2a"
}

resource "aws_internet_gateway" "my_ig" {
  vpc_id = aws_vpc.my_vpc.id
}

resource "aws_route_table" "public_route" {
  vpc_id = aws_vpc.my_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.my_ig.id
  }
}

resource "aws_route_table_association" "public_rt_assoc" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route.id
}

resource "aws_security_group" "web_sg" {
  name   = "SSH"
  vpc_id = aws_vpc.my_vpc.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = -1
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Code Review

The above code creates all of the resources needed for a working VPC. It has the VPC itself, a subnet, an internet gateway, a route table, the route table association, and a security group. This is just a basic configuration to demonstrate how modules work.

Create the Module Outputs File

Our goal is to “bubble up” outputs from the network module to the root module so that the values can be used in other parts of the project. To do this, we will define what output values we want to bubble up in the outputs.tf file.

For this lab, we want to output the VPC, subnet, and security group values.

Open the ./modules/networking/outputs.tf file and add the following content.

output "vpc" {
  value = aws_vpc.my_vpc
}

output "subnet" {
  value = aws_subnet.public_subnet
}

output "sec_group" {
  value = aws_security_group.web_sg
} 

Code Review

  • We defined three values to output from this module.

  • For each value, we reference the resources created in the main.tf file. Note how these references are made; it uses the <resource_type>.<resource_label> notation.

Run the Workflow

Now, let’s execute the Terraform CLI workflow to test our work.

# make sure you're in the root directory, and not the networking module directory
$ pwd

(make sure it's the root directory)


# initialize the project
$ terraform init

...output


# plan the changes
$ terraform plan

...output


# apply the changes
$ terraform apply

...output

CLI Review

If all went well, the changes should have been applied. You have just created a local module, outputted values from it, and used those values to create an EC2 instance in the networking module’s VPC and subnet. Pretty cool!