Terraform Modules

The Good, The Bad, and the Resourcely
AUTHOR
Chris Reuter
PUBLISH DATE
June 18, 2024

A modern alternative to Terraform modules

Terraform modules are a popular way to re-use Terraform code: giving developers an easy method to deploy what otherwise might be complex cloud resource combinations. They are essentially templates, intended for people that either aren’t familiar with Terraform or nuances of cloud resources (or both). Terraform modules are an elegant but incomplete solution to a problem in a world where more and more developers are being asked to deploy infrastructure.

Benefits of Terraform modules

Imagine building walls of a house: you would need to buy lumber, create custom plans, and use a saw & nail gun to frame your walls piece-by-piece. Terraform modules are like pre-built wall sections or roof trusses when building a house - you can use these modules to quickly assemble your house (cloud infrastructure), even adding your own inputs (wall color or fixtures) with less effort and chance of error.

Terraform modules let you keep your code organized and standardized across your organization. Most importantly, they allow developers to focus on what infrastructure they want, versus focusing on the nuances of Terraform.

Let’s consider deploying a single application on AWS with a VM and a load balancer. You might think this would require only two cloud resources (an EC2 instance and a load balancer). If you want to successfully set up an EC2 instance and load balancer, you would need to write Terraform to create and configure:

  • EC2 instance
  • AWS VPC
  • Subnets
  • Internet gateway
  • Target group
  • Load balancer
  • Listener

This is where Terraform modules are helpful: they provide templates for the variety of combinations of cloud resources needed to deploy different services.

Drawbacks of Terraform modules

While modules can help developers deploy quickly, they abstract away complexity that may ultimately be impactful. While this abstraction may reduce mental load, it also means incremental changes to the module must be perfectly backward compatible. Terraform output of modules cannot be edited, and users are restricted to using the module as-is: associated cloud resources are “locked-in” to that module and can’t be changed. If developers want to make changes, modules must be forked, modified, and the new version must be maintained. This effort to maintain backward compatibility can be very painful.

Many modules also rely on other, downstream modules. If you want to tweak a resource that you created with a module, you have to edit the module. If you don’t own the module, you have to fork it. If you do own the module, your tweaks will be applied everywhere the module was ever used - not just to the resource you wanted to tweak! As discussed earlier, modules get forked all the time - creating a maintenance nightmare.

In practice, most developers google “tf module <cloud services here>”, and find a module that fits their needs. This introduces significant security concerns, and only further complicates maintenance.

Let’s look at an example of a Terraform module, and see what might go wrong.

Example of a Terraform module

Let’s consider a simple example where you want to create an AWS EC2 instance.

main.tf
Copy
resource "aws_instance" "this" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  key_name               = var.key_name
  subnet_id              = var.subnet_id
  vpc_security_group_ids = var.security_group_ids

  tags = {
    Name = "ExampleInstance"
  }
}

output "instance_id" {
  value = aws_instance.this.id
}

output "private_dns_name" {
  value = aws_instance.this.private_dns
}

A developer might pass in the following variables:

var.tf
Copy
region = "us-west-2"
instance_type = "t2.micro"
ami_id = "ami-xxxxxxxxxx"
key_name = "my-key-pair"
subnet_id = "subnet-12345678"
security_group_ids = ["sg-12345678"]

Resulting in the following Terraform being applied:

main.tf
Copy
resource "aws_instance" "this" {
  ami                    = "ami-xxxxxxxxxx"
  instance_type          = "t2.micro"
  key_name               = "my-key-pair"
  subnet_id              = "subnet-12345678"
  vpc_security_group_ids = ["sg-12345678"]

  tags = {
    Name = "ExampleInstance"
  }
}

As you can imagine, as the number of services involved increases, the complexity of the resulting Terraform code will only increase. Modules do a great job of abstracting away that complexity and giving developers the ability to simply input parameters and apply.

However, consider that developers may not fully have context for each parameter that they are deciding on. This, combined with the fact that most modules are third-party created, means that organizations are committing code that they don’t fully understand every day.

Look at this list of examples, that illustrate how many different iterations of configuration there might be for deploying an EC2 instance. There are different modules for deploying an EC2 instance with various flavors:

  • Setting availability zones
  • Spot instances vs reserved
  • Mounting an EBS block volume
  • Setting IAM roles
  • Dealing with networking

These all require different inputs for a variety of configuration parameters that developers are probably not familiar with. Unfortunately, modules don’t give developers much context, rules, or policies that keep them on track. Building that system yourself would be a huge undertaking and maintenance burden on most security and DevOps teams.

Resourcely vs Terraform modules

Resourcely is a configuration engine for generating properly configured Terraform. It features two primary concepts: blueprints, and guardrails.

Blueprints

Blueprints are similar to Terraform modules: templated deployment patterns that make it easier for developers to deploy their code. There are key differences between Blueprints and Terraform modules. Resourcely blueprints have:

  • A UI
  • Templating quality-of-life features (pick lists, tags, data types, etc.)
  • Build-in metadata for major cloud services
  • Integrated change management support

Unlike Terraform modules, Resourcely exposes the resulting generated Terraform (where modules do not), meaning that resources can be changed without recreating an entire blueprint.

Blueprints are also exposed via a UI, and can be built or edited with Resourcely Foundry: a guided experience for creating them. This makes for a simple, intuitive deployment experience for individuals who may not even be familiar with Terraform.

There are some cases in which Terraform modules may be a better fit for your use case. Let’s say you have a very well defined infrastructure setup in mind, with no required deviations at all. With Terraform, you could make sweeping changes with little effort.

Sometimes the “lock-in” effect is a feature, not a bug. At large companies with curated module libraries, central infrastructure teams can use modules to make broad changes efficiently. Modules also act like poor-man’s guardrails. Users of the module can only change what the module author lets them - everything else is hard-coded.

Guardrails

Resourcely guardrails are policies that safeguard your commits from causing harm. Written with the Really policy language, they can be customized to your organization’s standards. Examples of other legacy policy languages are Sentinel and Rego/OPA.

Guardrails are applied to PRs, keeping your organization safe from accidental or non-compliant configuration. Consider a scenario where a developer is looking to deploy a service combination they are unfamiliar with. If they were to deploy an unsafe configuration (let’s say, an S3 bucket that can be read by anyone), that could mean disaster. With guardrails, this kind of accidental deployment can be prevented.

Terraform modules and Resourcely guardrails can be used together without needing to use blueprints - meaning that you can keep your developers safe, even if they are using existing Terraform modules that you don’t want to change.

Creating blueprints from modules

Many IaC practitioners rely heavily on Terraform modules for existing deployments. Luckily, Terraform modules and Resourcely blueprints can coexist peacefully. Resourcely supports importing all Terraform modules, which means that you aren’t throwing away your existing investment.

If you are already heavily invested in Terraform modules and don’t want to change their behavior, there are many benefits to turning Terraform modules into Resourcely blueprints:

  • apply guardrails to your existing modules as they are applied.
  • a catalog for discoverability of modules
  • an interface for connecting modules together
  • helpful context, type hinting, and linting: giving developers the help they need to make the right configuration decisions
  • Guardrails that limit inputs and offer exception approval workflows as part of your existing CI

✨ Note that imported Terraform modules are still subject to the lock-in problem. Resourcely can still apply guardrails and other Resourcely features to them, but cloud resources created by a Terraform module are still associated with and locked to that module. ✨

Conclusion

Terraform modules are a popular method for deploying cloud infrastructure. However, there are significant drawbacks to using them:

  • Maintenance burden and ever-growing “kitchen sink” modules
  • Limited flexibility
  • Hides the innards of Terraform
    • Appears positive, but forces you to fork and maintain new modules
  • Significant dependency management
  • Need to be forked to include properties that aren’t in that module already

Resourcely is a configuration engine that, in most cases, is a great replacement for Terraform modules. It also includes policy enforcement functionality that can work with Terraform modules standalone, or with Resourcely’s blueprints.

Ready to get started?

Set up a time to talk to our team to get started with Resourcely.

Get in touch

More posts

View all

Talk to a Human

See Resourcely in action and learn how it can help you secure and manage your cloud infrastructure today!