Here is some information about this architecture.
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 ].
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.
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.
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.
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
}
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.
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.
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.
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
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.
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.
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
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.
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.
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!