Terraform for Network Infrastructure as Code: A Complete Guide
Terraform has revolutionized how organizations manage network infrastructure by treating it as code. This comprehensive guide explores how to use Terraform for network automation, from basic concepts to advanced networking scenarios.
What is Infrastructure as Code (IaC)?
Infrastructure as Code is the practice of managing and provisioning computing infrastructure through machine-readable definition files rather than physical hardware configuration or interactive configuration tools. Terraform is one of the most popular IaC tools, offering:
- Declarative Configuration: Define desired state rather than procedural steps
- Version Control: Track infrastructure changes in Git
- Automation: Eliminate manual configuration processes
- Consistency: Ensure identical environments across deployments
- Scalability: Manage complex infrastructure efficiently
Getting Started with Terraform
Installation
# Download and install Terraform
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt update && sudo apt install terraform
# Verify installation
terraform version
Basic Project Structure
network-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── providers.tf
└── modules/
├── vpc/
├── subnets/
└── security/
Core Concepts
Providers
Providers are plugins that Terraform uses to interact with cloud providers, SaaS providers, and other APIs.
# providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "aws" {
region = var.aws_region
}
provider "azurerm" {
features {}
}
Variables
Define input variables for your configuration:
# variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
Resources
Resources are the infrastructure objects that Terraform manages:
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
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]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.environment}-public-${count.index + 1}"
Environment = var.environment
}
}
Network Infrastructure Examples
VPC and Subnet Configuration
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, {
Name = "${var.name}-vpc"
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index + 1}"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnets[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.name}-private-${count.index + 1}"
})
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(var.tags, {
Name = "${var.name}-igw"
})
}
resource "aws_nat_gateway" "this" {
count = length(var.public_subnets)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.name}-nat-${count.index + 1}"
})
}
resource "aws_eip" "nat" {
count = length(var.public_subnets)
vpc = true
tags = merge(var.tags, {
Name = "${var.name}-eip-${count.index + 1}"
})
}
Security Groups and Network ACLs
# modules/security/main.tf
resource "aws_security_group" "web" {
name_prefix = "${var.name}-web-"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.allowed_ssh_cidr
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.name}-web-sg"
})
}
resource "aws_security_group" "database" {
name_prefix = "${var.name}-db-"
vpc_id = var.vpc_id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(var.tags, {
Name = "${var.name}-db-sg"
})
}
Load Balancer Configuration
# modules/alb/main.tf
resource "aws_lb" "this" {
name = "${var.name}-alb"
internal = var.internal
load_balancer_type = "application"
security_groups = var.security_groups
subnets = var.subnets
enable_deletion_protection = var.enable_deletion_protection
tags = merge(var.tags, {
Name = "${var.name}-alb"
})
}
resource "aws_lb_target_group" "this" {
count = length(var.target_groups)
name = "${var.name}-tg-${count.index}"
port = var.target_groups[count.index].port
protocol = var.target_groups[count.index].protocol
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = var.target_groups[count.index].health_check_path
port = "traffic-port"
protocol = var.target_groups[count.index].protocol
timeout = 5
unhealthy_threshold = 2
}
tags = merge(var.tags, {
Name = "${var.name}-tg-${count.index}"
})
}
resource "aws_lb_listener" "this" {
count = length(var.listeners)
load_balancer_arn = aws_lb.this.arn
port = var.listeners[count.index].port
protocol = var.listeners[count.index].protocol
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this[var.listeners[count.index].target_group_index].arn
}
}
Advanced Networking Scenarios
Multi-Region Deployment
# multi-region/main.tf
module "vpc_us_west" {
source = "../modules/vpc"
providers = {
aws = aws.us-west-2
}
name = "us-west-2"
vpc_cidr = "10.1.0.0/16"
public_subnets = ["10.1.1.0/24", "10.1.2.0/24"]
private_subnets = ["10.1.10.0/24", "10.1.11.0/24"]
availability_zones = ["us-west-2a", "us-west-2b"]
}
module "vpc_us_east" {
source = "../modules/vpc"
providers = {
aws = aws.us-east-1
}
name = "us-east-1"
vpc_cidr = "10.2.0.0/16"
public_subnets = ["10.2.1.0/24", "10.2.2.0/24"]
private_subnets = ["10.2.10.0/24", "10.2.11.0/24"]
availability_zones = ["us-east-1a", "us-east-1b"]
}
# VPC Peering
resource "aws_vpc_peering_connection" "us_west_to_us_east" {
provider = aws.us-west-2
vpc_id = module.vpc_us_west.vpc_id
peer_vpc_id = module.vpc_us_east.vpc_id
peer_region = "us-east-1"
tags = {
Name = "us-west-2-to-us-east-1"
}
}
Transit Gateway Configuration
# modules/transit-gateway/main.tf
resource "aws_ec2_transit_gateway" "this" {
description = "${var.name} Transit Gateway"
default_route_table_association = "enable"
default_route_table_propagation = "enable"
tags = merge(var.tags, {
Name = "${var.name}-tgw"
})
}
resource "aws_ec2_transit_gateway_vpc_attachment" "this" {
count = length(var.vpc_attachments)
subnet_ids = var.vpc_attachments[count.index].subnet_ids
transit_gateway_id = aws_ec2_transit_gateway.this.id
vpc_id = var.vpc_attachments[count.index].vpc_id
tags = merge(var.tags, {
Name = "${var.name}-tgw-attachment-${count.index}"
})
}
resource "aws_ec2_transit_gateway_route_table" "this" {
count = length(var.route_tables)
transit_gateway_id = aws_ec2_transit_gateway.this.id
tags = merge(var.tags, {
Name = "${var.name}-tgw-rt-${count.index}"
})
}
Best Practices
1. Use Modules for Reusability
# modules/network/main.tf
module "vpc" {
source = "../vpc"
name = var.name
vpc_cidr = var.vpc_cidr
public_subnets = var.public_subnets
private_subnets = var.private_subnets
availability_zones = var.availability_zones
}
module "security" {
source = "../security"
name = var.name
vpc_id = module.vpc.vpc_id
allowed_ssh_cidr = var.allowed_ssh_cidr
}
2. Implement State Management
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "network/terraform.tfstate"
region = "us-west-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
3. Use Data Sources for Dynamic Configuration
# data.tf
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "latest_amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
4. Implement Proper Tagging
# locals.tf
locals {
common_tags = {
Environment = var.environment
Project = var.project
Owner = var.owner
ManagedBy = "Terraform"
}
}
CI/CD Integration
GitLab CI/CD Pipeline
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
variables:
TF_ROOT: ${CI_PROJECT_DIR}/terraform
validate:
stage: validate
script:
- cd $TF_ROOT
- terraform init
- terraform validate
- terraform fmt -check
plan:
stage: plan
script:
- cd $TF_ROOT
- terraform init
- terraform plan -out=plan.tfplan
artifacts:
paths:
- $TF_ROOT/plan.tfplan
expire_in: 1 week
apply:
stage: apply
script:
- cd $TF_ROOT
- terraform init
- terraform apply plan.tfplan
when: manual
only:
- main
GitHub Actions Workflow
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan
if: github.event_name == 'pull_request'
Monitoring and Validation
Output Configuration
# outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = aws_subnet.private[*].id
}
output "load_balancer_dns" {
description = "The DNS name of the load balancer"
value = aws_lb.main.dns_name
}
Validation Rules
# validation.tf
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "Must be a valid CIDR block."
}
}
variable "subnet_cidrs" {
description = "List of subnet CIDR blocks"
type = list(string)
validation {
condition = alltrue([
for cidr in var.subnet_cidrs : can(cidrhost(cidr, 0))
])
error_message = "All subnet CIDR blocks must be valid."
}
}
Troubleshooting Common Issues
1. State Management Issues
# Refresh state
terraform refresh
# Import existing resources
terraform import aws_vpc.main vpc-12345678
# Move resources
terraform state mv aws_subnet.old aws_subnet.new
2. Dependency Issues
# Use depends_on for explicit dependencies
resource "aws_instance" "web" {
ami = data.aws_ami.latest.id
instance_type = "t3.micro"
depends_on = [aws_security_group.web]
}
3. Variable Validation
variable "instance_type" {
description = "EC2 instance type"
type = string
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
}
}
Conclusion
Terraform provides a powerful and flexible platform for managing network infrastructure as code. By following the practices outlined in this guide, you can build robust, scalable, and maintainable network infrastructure.
Key takeaways: - Use modules for reusability and maintainability - Implement proper state management - Follow naming conventions and tagging strategies - Integrate with CI/CD pipelines - Implement validation and testing - Document your infrastructure code
Additional Resources
This guide provides a comprehensive overview of using Terraform for network infrastructure as code. For more advanced topics, check out our other articles on specific Terraform modules and best practices.