Skip to content

💾 Module Design

Level: Core Solves: Thiết kế Terraform modules tái sử dụng, maintainable, và scalable cho enterprise

🎯 Mục tiêu (Outcomes)

Sau khi áp dụng kiến thức trong trang này, bạn sẽ có khả năng:

  • Thiết kế Module Interface với variables và outputs rõ ràng
  • Áp dụng Module Composition đúng cách
  • Sử dụng Semantic Versioning cho modules
  • Viết Documentation chuẩn cho modules
  • Tránh Anti-patterns thường gặp
  • Test Modules trước khi deploy

Khi nào dùng

PatternUse CaseLý do
Shared modulesReusable infraDRY, consistency
CompositionComplex systemsSeparation of concerns
Registry modulesTeam-wide standardsCentral management
Local modulesProject-specificQuick iteration

Khi nào KHÔNG dùng

PatternVấn đềThay thế
Module cho 1 resourceOverkillInline resource
"God" moduleToo complexSplit modules
No versioningBreaking changesSemVer
Copy-paste modulesDriftRegistry/Git source

⚠️ Cảnh báo từ Raizo

"Team có 100 projects, mỗi project copy-paste VPC code. Security fix cần update tất cả manually. 3 tuần để patch. Modules + versioning giải quyết trong 1 ngày."

Tại sao cần Modules?

💡 Giáo sư Tom

Copy-paste Terraform code là technical debt. Modules là cách DRY (Don't Repeat Yourself) trong IaC. Một module tốt giống như một function tốt - single responsibility, clear interface, và well-tested.

Vấn đề với Copy-Paste

┌─────────────────────────────────────────────────────────────────┐
│                    COPY-PASTE CHAOS                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Project A          Project B          Project C                │
│  ┌─────────┐        ┌─────────┐        ┌─────────┐              │
│  │ vpc.tf  │        │ vpc.tf  │        │ vpc.tf  │              │
│  │ (v1)    │        │ (v1.1)  │        │ (v1.2)  │              │
│  └─────────┘        └─────────┘        └─────────┘              │
│                                                                 │
│  PROBLEMS:                                                      │
│  ❌ Security fix needed? Update ALL projects manually           │
│  ❌ Different versions = different behaviors                    │
│  ❌ No single source of truth                                   │
│  ❌ Knowledge scattered across projects                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Module Solution

┌─────────────────────────────────────────────────────────────────┐
│                    MODULE SOLUTION                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌─────────────────┐                          │
│                    │   VPC Module    │                          │
│                    │   (versioned)   │                          │
│                    │   v2.1.0        │                          │
│                    └────────┬────────┘                          │
│                             │                                   │
│           ┌─────────────────┼─────────────────┐                 │
│           ▼                 ▼                 ▼                 │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐            │
│  │  Project A  │   │  Project B  │   │  Project C  │            │
│  │  uses v2.1  │   │  uses v2.1  │   │  uses v2.0  │            │
│  └─────────────┘   └─────────────┘   └─────────────┘            │
│                                                                 │
│  BENEFITS:                                                      │
│  ✅ Single source of truth                                      │
│  ✅ Version control for infrastructure                          │
│  ✅ Security fixes propagate via version bumps                  │
│  ✅ Consistent patterns across organization                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Module Structure

Standard Module Layout

modules/vpc/
├── main.tf           # Primary resources
├── variables.tf      # Input variables (interface)
├── outputs.tf        # Output values (interface)
├── versions.tf       # Provider requirements
├── locals.tf         # Local values
├── README.md         # Documentation
├── examples/         # Usage examples
│   ├── simple/
│   └── complete/
└── tests/            # Module tests
    └── vpc_test.go

Module Interface Design

hcl
# variables.tf - Module inputs (the "API")
variable "name" {
  description = "Name prefix for all resources"
  type        = string
  
  validation {
    condition     = length(var.name) <= 20
    error_message = "Name must be 20 characters or less."
  }
}

variable "cidr_block" {
  description = "CIDR block for VPC"
  type        = string
  default     = "10.0.0.0/16"
  
  validation {
    condition     = can(cidrhost(var.cidr_block, 0))
    error_message = "Must be a valid CIDR block."
  }
}

variable "availability_zones" {
  description = "List of AZs to use"
  type        = list(string)
  default     = []
}

variable "enable_nat_gateway" {
  description = "Enable NAT Gateway for private subnets"
  type        = bool
  default     = true
}

variable "single_nat_gateway" {
  description = "Use single NAT Gateway (cost saving for non-prod)"
  type        = bool
  default     = false
}

variable "tags" {
  description = "Tags to apply to all resources"
  type        = map(string)
  default     = {}
}
hcl
# outputs.tf - Module outputs (the "return values")
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "IDs of public subnets"
  value       = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  description = "IDs of private subnets"
  value       = aws_subnet.private[*].id
}

output "nat_gateway_ips" {
  description = "Public IPs of NAT Gateways"
  value       = aws_eip.nat[*].public_ip
}

output "vpc_cidr_block" {
  description = "CIDR block of the VPC"
  value       = aws_vpc.main.cidr_block
}

Module Composition

Root Module Calling Child Modules

hcl
# environments/prod/main.tf

module "vpc" {
  source = "../../modules/vpc"
  
  name               = "prod"
  cidr_block         = "10.0.0.0/16"
  availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
  enable_nat_gateway = true
  single_nat_gateway = false  # HA for production
  
  tags = local.common_tags
}

module "eks" {
  source = "../../modules/eks"
  
  cluster_name    = "prod-cluster"
  vpc_id          = module.vpc.vpc_id
  subnet_ids      = module.vpc.private_subnet_ids
  
  tags = local.common_tags
}

module "rds" {
  source = "../../modules/rds"
  
  identifier     = "prod-db"
  vpc_id         = module.vpc.vpc_id
  subnet_ids     = module.vpc.private_subnet_ids
  
  tags = local.common_tags
}

Module Composition Pattern

Module Versioning

Semantic Versioning

MAJOR.MINOR.PATCH

v2.1.3
│ │ │
│ │ └── Patch: Bug fixes, no interface changes
│ └──── Minor: New features, backward compatible
└────── Major: Breaking changes

Version Constraints

hcl
# Exact version (most restrictive)
module "vpc" {
  source  = "company/vpc/aws"
  version = "2.1.3"
}

# Pessimistic constraint (recommended)
module "vpc" {
  source  = "company/vpc/aws"
  version = "~> 2.1"  # >= 2.1.0, < 3.0.0
}

# Range constraint
module "vpc" {
  source  = "company/vpc/aws"
  version = ">= 2.0, < 3.0"
}

Module Sources

hcl
# Local path
module "vpc" {
  source = "../../modules/vpc"
}

# Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
}

# GitHub
module "vpc" {
  source = "github.com/company/terraform-modules//vpc?ref=v2.1.0"
}

# S3 (private modules)
module "vpc" {
  source = "s3::https://s3-us-east-1.amazonaws.com/company-modules/vpc.zip"
}

# Git over SSH
module "vpc" {
  source = "git@github.com:company/terraform-modules.git//vpc?ref=v2.1.0"
}

Module Best Practices

1. Single Responsibility

hcl
# ❌ BAD: Module does too much
module "infrastructure" {
  source = "./modules/everything"
  # Creates VPC, EKS, RDS, S3, IAM, CloudWatch...
}

# ✅ GOOD: Focused modules
module "vpc" {
  source = "./modules/vpc"
}

module "eks" {
  source = "./modules/eks"
  vpc_id = module.vpc.vpc_id
}

2. Sensible Defaults

hcl
# ✅ GOOD: Secure defaults, override when needed
variable "enable_encryption" {
  description = "Enable encryption at rest"
  type        = bool
  default     = true  # Secure by default
}

variable "public_access" {
  description = "Allow public access"
  type        = bool
  default     = false  # Private by default
}

3. Input Validation

hcl
variable "environment" {
  description = "Environment name"
  type        = string
  
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  
  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Only t3 instance types are allowed."
  }
}

4. Conditional Resources

hcl
# Create NAT Gateway only if enabled
resource "aws_nat_gateway" "main" {
  count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
  
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
}

5. Dynamic Blocks

hcl
resource "aws_security_group" "main" {
  name   = var.name
  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
    }
  }
}

Module Documentation

README Template

markdown
# VPC Module

Creates a VPC with public and private subnets across multiple AZs.

## Usage

```hcl
module "vpc" {
  source = "company/vpc/aws"
  version = "2.1.0"
  
  name       = "production"
  cidr_block = "10.0.0.0/16"
  
  availability_zones = ["us-east-1a", "us-east-1b"]
  enable_nat_gateway = true
}

Requirements

NameVersion
terraform>= 1.0
aws>= 5.0

Inputs

NameDescriptionTypeDefaultRequired
nameName prefixstring-yes
cidr_blockVPC CIDRstring"10.0.0.0/16"no

Outputs

NameDescription
vpc_idID of the VPC
public_subnet_idsIDs of public subnets

## Anti-Patterns to Avoid

### ❌ Hardcoded Values

```hcl
# BAD
resource "aws_instance" "web" {
  ami           = "ami-0123456789"  # Hardcoded AMI
  instance_type = "t3.micro"        # Hardcoded type
  subnet_id     = "subnet-abc123"   # Hardcoded subnet
}

# GOOD
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id
}

Provider in Module

hcl
# BAD - Provider in module
# modules/vpc/main.tf
provider "aws" {
  region = "us-east-1"
}

# GOOD - Provider in root module only
# environments/prod/providers.tf
provider "aws" {
  region = "us-east-1"
}

Overly Generic Modules

hcl
# BAD - Too many conditionals
variable "resource_type" {
  description = "Type of resource to create"
  # Creates different resources based on this...
}

## Best Practices Checklist

- [ ] Single responsibility per module
- [ ] Clear variable descriptions
- [ ] Input validation
- [ ] Sensible secure defaults
- [ ] Version constraints (SemVer)
- [ ] README with examples
- [ ] No providers in modules
- [ ] Outputs for composition

## ⚖️ Trade-offs

### Trade-off 1: Module Granularity

| Granularity | Reusability | Complexity |
|-------------|-------------|------------|
| **Fine** (1-3 resources) | High | Low per module, high overall |
| **Medium** (5-10 resources) | Balanced | Balanced |
| **Coarse** (20+ resources) | Low | High per module |

**Khuyến nghị**: Medium granularity - một module = một logical component.

---

### Trade-off 2: Module Sources

| Source | Speed | Security | Versioning |
|--------|-------|----------|------------|
| **Local path** | Fast | N/A | Git |
| **Git** | Medium | SSH keys | Tags |
| **Registry (public)** | Fast | Vetted | SemVer |
| **Registry (private)** | Fast | Controlled | SemVer |

---

### Trade-off 3: Flexibility vs Simplicity

| Approach | Flexibility | Complexity |
|----------|-------------|------------|
| **Many variables** | High | High |
| **Few variables** | Low | Low |
| **Object variables** | High | Medium |

## 🚨 Failure Modes

### Failure Mode 1: Breaking Change in Module

::: danger 🔥 Incident thực tế
*Module maintainer rename variable. 50 projects bị break. Rollback module version, fix all consumers, release properly.*
:::

| Cách phát hiện | Cách phòng tránh |
|----------------|------------------|
| Plan failures | SemVer đúng |
| Consumer errors | Deprecation warnings |
| Breaking at runtime | Test before release |

---

### Failure Mode 2: Unpinned Module Version

| Cách phát hiện | Cách phòng tránh |
|----------------|------------------|
| Random failures | Pin versions |
| Different behavior | Lock file |
| Init pulls new version | ≫ version constraint |

---

### Failure Mode 3: Module Drift

| Cách phát hiện | Cách phòng tránh |
|----------------|------------------|
| Copy-paste diverges | Single source |
| Different configs | Regular audit |
| Security not applied | Centralized modules |

## 🔐 Security Baseline

### Module Security Requirements

| Requirement | Implementation | Verification |
|-------------|----------------|---------------|
| **Secure defaults** | encryption=true, public=false | Code review |
| **No hardcoded secrets** | Variables only | Pre-commit scan |
| **Least privilege** | Minimal IAM | Policy review |
| **Validated inputs** | validation blocks | Module tests |

### Module Security Checklist

| Item | Status |
|------|--------|
| Secure default values | ☑ Required |
| No secrets in code | ☑ Required |
| Input validation | ☑ Required |
| Version pinned | ☑ Required |
| Source trusted | ☑ Required |

## 📊 Ops Readiness

### Metrics cần Monitoring

| Metric | Source | Alert Threshold |
|--------|--------|-----------------|
| Module version spread | Registry | > 3 major versions |
| Deprecated module usage | Audit | Any |
| Module test failures | CI | Any |
| Consumer count | Registry | Tracking only |

### Runbook Entry Points

| Tình huống | Runbook |
|------------|---------|
| Breaking change released | `runbook/module-rollback.md` |
| Module deprecation | `runbook/module-migration.md` |
| Security vuln in module | `runbook/module-security-patch.md` |
| Version conflict | `runbook/module-version-resolution.md` |

## ✅ Design Review Checklist

### Interface

- [ ] Variables well-typed
- [ ] Descriptions complete
- [ ] Validation in place
- [ ] Outputs documented

### Structure

- [ ] Single responsibility
- [ ] Standard layout
- [ ] README with examples
- [ ] Tests included

### Versioning

- [ ] SemVer followed
- [ ] Changelog maintained
- [ ] Breaking changes documented
- [ ] Migration guide for major versions

### Security

- [ ] Secure defaults
- [ ] No hardcoded values
- [ ] Input validation
- [ ] Minimal permissions

## 📎 Liên kết

- 📎 [IaC Fundamentals](/terraform/foundation/fundamentals) - Terraform basics
- 📎 [State Management](/terraform/foundation/state) - Module state considerations
- 📎 [Testing & CI/CD](/terraform/advanced/testing) - Testing modules
- 📎 [Multi-Cloud Patterns](/terraform/patterns/multi-cloud) - Cross-cloud module design
- 📎 [AWS VPC](/aws/core/networking) - AWS networking patterns
- 📎 [GCP VPC](/gcp/core/networking) - GCP networking patterns
- 📎 [Terraform Security](/terraform/security/security) - Security patterns