terraform
Terraform Modules: Building Reusable Infrastructure Components
Master Terraform modules to create reusable, maintainable infrastructure components. Learn about module creation, versioning, and best practices for infrastructure as code.
March 15, 2024
Admin KC
5 min read
Introduction to Terraform Modules
Terraform modules are containers for multiple resources that are used together. They help you organize and reuse your Terraform code across different projects and environments.
Module Basics
Module Structure
module-name/ ├── main.tf # Main module configuration ├── variables.tf # Input variables ├── outputs.tf # Output values ├── versions.tf # Version constraints └── README.md # Documentation
Creating a Basic Module
# modules/vpc/main.tf resource "aws_vpc" "main" { cidr_block = var.vpc_cidr tags = { Name = var.vpc_name Environment = var.environment } } resource "aws_subnet" "public" { count = length(var.public_subnets) vpc_id = aws_vpc.main.id cidr_block = var.public_subnets[count.index] tags = { Name = "${var.vpc_name}-public-${count.index + 1}" } }
Module Variables
# modules/vpc/variables.tf variable "vpc_cidr" { description = "CIDR block for VPC" type = string } variable "vpc_name" { description = "Name tag for VPC" type = string } variable "environment" { description = "Environment tag" type = string } variable "public_subnets" { description = "List of public subnet CIDR blocks" type = list(string) }
Module Outputs
# modules/vpc/outputs.tf output "vpc_id" { description = "ID of the created VPC" value = aws_vpc.main.id } output "public_subnet_ids" { description = "List of public subnet IDs" value = aws_subnet.public[*].id }
Using Modules
Local Module Usage
module "vpc" { source = "./modules/vpc" vpc_cidr = "10.0.0.0/16" vpc_name = "my-vpc" environment = "production" public_subnets = ["10.0.1.0/24", "10.0.2.0/24"] }
Remote Module Usage
module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" version = "3.15.1" bucket = "my-unique-bucket" acl = "private" versioning = { enabled = true } }
Advanced Module Concepts
Module Composition
# modules/web-app/main.tf module "vpc" { source = "../vpc" vpc_cidr = var.vpc_cidr vpc_name = var.vpc_name environment = var.environment public_subnets = var.public_subnets } module "ec2_instance" { source = "../ec2-instance" subnet_id = module.vpc.public_subnet_ids[0] instance_type = var.instance_type environment = var.environment }
Dynamic Block Usage
# modules/security-group/main.tf resource "aws_security_group" "this" { name = var.name description = var.description vpc_id = var.vpc_id dynamic "ingress" { for_each = var.ingress_rules content { from_port = ingress.value.from_port to_port = ingress.value.to_port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks } } }
Conditional Creation
# modules/rds/main.tf resource "aws_db_instance" "this" { count = var.create_database ? 1 : 0 identifier = var.identifier engine = var.engine instance_class = var.instance_class allocated_storage = var.allocated_storage # ... other configuration ... }
Module Best Practices
1. Version Constraints
# versions.tf terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } }
2. Documentation
# README.md # VPC Module This module creates a VPC with public and private subnets. ## Usage ```hcl module "vpc" { source = "./modules/vpc" vpc_cidr = "10.0.0.0/16" vpc_name = "my-vpc" }
Inputs
| Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | vpc_cidr | CIDR block for VPC | string | n/a | yes | | vpc_name | Name tag for VPC | string | n/a | yes |
Outputs
| Name | Description | |------|-------------| | vpc_id | ID of the created VPC |
### 3. Input Validation ```hcl # variables.tf variable "environment" { description = "Environment name" type = string validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be one of: dev, staging, prod." } }
Testing Modules
1. Basic Testing
# examples/complete/main.tf module "vpc" { source = "../../" vpc_cidr = "10.0.0.0/16" vpc_name = "example-vpc" environment = "dev" public_subnets = ["10.0.1.0/24", "10.0.2.0/24"] } output "vpc_id" { value = module.vpc.vpc_id }
2. Automated Testing
# test/vpc_test.go package test import ( "testing" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" ) func TestVPCModule(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/complete", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) vpcID := terraform.Output(t, terraformOptions, "vpc_id") assert.NotEmpty(t, vpcID) }
Working with Module Registries
Publishing to Registry
# modules/vpc/main.tf terraform { required_version = ">= 1.0.0" } # Add semantic versioning tags in Git # git tag -a "v1.0.0" -m "Initial stable release" # git push origin v1.0.0
Module Versioning
module "vpc" { source = "app.terraform.io/example-corp/vpc/aws" version = "~> 2.0.0" # Allows 2.0.x but not 2.1.0 vpc_cidr = "10.0.0.0/16" }
Advanced Module Patterns
1. Provider Aliasing
# modules/multi-region/main.tf provider "aws" { alias = "us_east_1" region = "us-east-1" } provider "aws" { alias = "us_west_2" region = "us-west-2" } resource "aws_vpc" "east" { provider = aws.us_east_1 cidr_block = var.east_vpc_cidr } resource "aws_vpc" "west" { provider = aws.us_west_2 cidr_block = var.west_vpc_cidr }
2. Module Templates
# modules/template/main.tf locals { common_tags = { Environment = var.environment Project = var.project_name Terraform = "true" } } resource "aws_vpc" "main" { cidr_block = var.vpc_cidr tags = merge(local.common_tags, { Name = "${var.project_name}-vpc" }) }
Conclusion
Terraform modules are essential for:
- Code reusability
- Maintainability
- Standardization
- Version control
- Team collaboration
Next Steps
- Create your first module
- Implement testing strategies
- Publish to a module registry
- Explore advanced patterns
- Integrate with CI/CD
Resources
Terraform
IaC
DevOps
Cloud
Modules