DEV Community

Cover image for Terraform Practice pt3: Modules Usage and Sources
Andrey Frol
Andrey Frol

Posted on

Terraform Practice pt3: Modules Usage and Sources

Terraform modules are an incredibly important part of the Terraform.

Modules are the main way to package and reuse resource configurations within Terraform. Proper use of modules will make your code more readable and reusable. It also promotes consistency and best practices.

Terraform module is a collection of configuration files inside a folder. We have actually worked with a module in previous chapter. It was a root module configuration.

With modules we can organize our configurations. It may not seem that important with smaller size configurations, but modules become indispensable as infrastructure grows with time and grows in complexity.

How do we create a module and make sure that Terraform knows about it and loads it when necessary?
That's what we will learn next.

 

Code for this example: github

 

How to make a Terraform module

 

First we need to create a new folder within our terraform project directory, let's call it server. Inside this directory I will also create a new file called server.tf:

$ mkdir server
$ cd server
$ touch server.tf
Enter fullscreen mode Exit fullscreen mode

 

This is the folder structure. Your editor may show .terraform/ folder in there as well, this is normal. Folders and files that start with a . are hidden on Unix systems like MacOS and Linux. This diagrams shows non-hidden files and folders:

.
├── main.tf
├── outputs.tf
├── server
│   └── server.tf
├── terraform.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf
Enter fullscreen mode Exit fullscreen mode

 

Let's create a configuration inside our new server.tf file:

# server/server.tf

variable "subnet_id" {}
variable "size" {
    default = "t2.micro"
}
variable "security_groups" {
  type = list(any)
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"]
}

resource "aws_instance" "web_server" {
  ami = data.aws_ami.ubuntu.id
  instance_type = var.size
  subnet_id = var.subnet_id
  vpc_security_group_ids = var.security_groups

  tags = {
    Name = "Web Server from module"
    Terraform = "true"
  }
}

output "public_ip" {
  value = aws_instance.web_server.public_ip
}
output "public_dns" {
  value = aws_instance.web_server.public_dns
}
Enter fullscreen mode Exit fullscreen mode

 

Now we will use this new module in our main configuration. Modules are invoked using the module block type. Within this block we must specify the location of the module so that Terraform knows where to look. The name of the module is only for use to reference it in our code. The important part is the directory that stores module's files. We are free to call it whatever we want. I will name mine my_server_module for illustration purposes.
Add this code to the main.tf file:

# main.tf

# ... some code above

module "my_server_module" {
  source          = "./server"
  subnet_id       = aws_subnet.public_subnet.id
  security_groups = [aws_security_group.public_sg.id]
}
Enter fullscreen mode Exit fullscreen mode

 

Before we proceed we need to run terraform init command. This command is required every time we add new modules and providers:

$ terraform init
Enter fullscreen mode Exit fullscreen mode

 

Next we validate our code to check for any syntax errors:

$ terraform validate
# Output
Success! The configuration is valid.
Enter fullscreen mode Exit fullscreen mode

 

We can also check if the new module was added by running the providers command:

$ terraform providers
# Output
Providers required by configuration:
.
├── provider[registry.terraform.io/hashicorp/aws] >= 2.7.0
└── module.my_server_module
    └── provider[registry.terraform.io/hashicorp/aws]
Enter fullscreen mode Exit fullscreen mode

 

Let's see what the plan command will show us:

$ terraform plan
# Output
Plan: 19 to add, 0 to change, 0 to destroy.
Enter fullscreen mode Exit fullscreen mode

Which is correct since we added only 1 new resource: web_server.

 

I will deploy my current configuration to check that it is working:

$ terraform apply
# Output
Apply complete! Resources: 19 added, 0 changed, 0 destroyed.

Outputs:

security_group_private = "sg-08af5494603e89b47"
security_group_public = "sg-070da46a3a465887f"
Enter fullscreen mode Exit fullscreen mode

 

I can also check if the module configuration works by checking the state. This command will output a list of all deployed resources:

$ terraform state list
# Output
data.aws_availability_zones.available
data.aws_region.current
aws_eip.nat_gateway_eip
aws_internet_gateway.internet_gateway
aws_nat_gateway.nat_gateway
aws_route_table.private_route_table
aws_route_table.public_route_table
aws_route_table_association.private
aws_route_table_association.public
aws_security_group.private_sg
aws_security_group.public_sg
aws_security_group_rule.private_in
aws_security_group_rule.private_out
aws_security_group_rule.public_http_in
aws_security_group_rule.public_https_in
aws_security_group_rule.public_out
aws_security_group_rule.public_ssh_in
aws_subnet.private_subnet
aws_subnet.public_subnet
aws_vpc.vpc
module.my_server_module.data.aws_ami.ubuntu
module.my_server_module.aws_instance.web_server
Enter fullscreen mode Exit fullscreen mode

Last 2 entries are from my module. Awesome!

 

There is also a way to see a lot of individual settings for a particular resource using state show <resource> command like this:

$ terraform state show module.my_server_module.aws_instance.web_server
Enter fullscreen mode Exit fullscreen mode

You should get a long list of properties for this resource.

 

How to reuse modules

 

So how can this module be useful? Let's illustrate it with deploying another instance using this module, but this time we will put in a private subnet.

# main.tf

# ... some code above

module "another_server_from_a_module" {
  # location of module directory
  source          = "./server"
  subnet_id       = aws_subnet.private_subnet.id
  security_groups = [aws_security_group.private_sg.id]
}
Enter fullscreen mode Exit fullscreen mode

We need to run terraform init again to add this module. You can verify that this new resource shows up both in plan, apply, and state.

 

Terraform Module sources

Modules are just Terraform configuration files located outside out current working directory.

Modules can be added using a variety of sources. This is a list of some of the supportws ways to specify module source in Terraform module block:

  • Local paths
  • Terraform registry
  • GitHub/Bitbucket or other generic Git/Mercurial repos
  • HTTP URLs
  • S3 Bucket (AWS) or GCS Bucket (GCP)

 

Terraform Module from local path

This is what we used earlier in this article. The convention is to create a modules/ directory within our current working directory and place module directories there. Let's do that while we are at it.
Create a new directory for modules and place our server module there. Update project structure can be found below:

.
├── main.tf
├── modules
│   └── server
│       └── server.tf
├── outputs.tf
├── terraform.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf

2 directories, 7 files
Enter fullscreen mode Exit fullscreen mode

 

To import a module from local path you just need to specify the path to that module and initialize terraform to load it into our configuration:

# main.tf

# ... some code above

module "server_from_local_module" {
  # local module
  source          = "./modules/server"
  subnet_id       = aws_subnet.private_subnet.id
  security_groups = [aws_security_group.private_sg.id]
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to update sources for our 2 existing modules to reflect new module location.
Run terraform init to update modules.

 

Terraform Public Module Registry

Terraform conveniently provides a public registry of modules. Checkout https://registry.terraform.io/ to what is available there.

I will select an autoscaling module for this example. This module has been used more than 3 million times and it seems to be quite popular.

To add this module we need to go to module's page and copy the code.

Adding module from Terraform Registry

 

Now we need add a bit of our own code to it to make it work with our configuration. I added a data block to specify the AMI for the autoscaling:

# main.tf

# ... some code above

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"]
}

module "autoscaling_from_registry" {
  source  = "terraform-aws-modules/autoscaling/aws"
  version = "6.5.0"

  name = "demo_module_asg"

  vpc_zone_identifier = [aws_subnet.private_subnet.id]
  min_size = 0
  max_size = 1
  desired_capacity = 1

  image_id = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"

  tags = {
    Name = "Web servers from asg module"
    Terraform = "true"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now can run init to make Terraform load this module and add it to our configuration. Verify the code with terraform validate and you do a dry-run with terraform plan.

 

Terraform Modules from GitHub repo

Terraform has support for Github repos and it will automatically recognize that the link points to a github repo. Notice that we do not specify version attribute in this case.

We will add the same autoscaling group module, however this time the source will point to github:

# main.tf

# ... some code above

module "autoscaling_from_github" {
  source  = "github.com/terraform-aws-modules/terraform-aws-autoscaling"

  name = "demo_module_asg"

  vpc_zone_identifier = [aws_subnet.private_subnet.id]
  min_size = 0
  max_size = 1
  desired_capacity = 1

  image_id = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"

  tags = {
    Name = "Web servers from asg module"
    Terraform = "true"
  }
}
Enter fullscreen mode Exit fullscreen mode

 

Conclusion

In this article we got our first taste of Terraform modules.

Thank you for reading! See you in the next chapter!

Oldest comments (0)