terraform-module-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Terraform Module Design Skill

Terraform模块设计技能

Table of Contents

目录

How to ImplementStep-by-Step | Examples
实现指南分步教程 | 示例
帮助资源要求 | 相关链接

Purpose

目标

Master module design for creating reusable, testable, well-documented infrastructure components. Learn when to modularize, module structure patterns, input/output design, and composition strategies.
掌握模块设计方法,以创建可复用、可测试、文档完善的基础设施组件。学习何时进行模块化、模块结构模式、输入/输出设计以及组合策略。

When to Use

适用场景

Use this skill when you need to:
  • Create reusable infrastructure patterns - Build modules for repeated resource groups
  • Encapsulate complex configurations - Simplify multi-resource setups
  • Standardize across projects - Ensure consistency in infrastructure
  • Organize code for maintainability - Structure large Terraform projects
  • Build composable systems - Combine modules to create larger architectures
  • Version infrastructure components - Publish and version modules
When NOT to use:
  • Single one-off resources
  • Resources used only once
  • No variation between uses
Trigger Phrases:
  • "Create reusable Terraform module"
  • "Design module structure"
  • "Define module inputs and outputs"
  • "Compose multiple modules"
  • "Version Terraform module"
在以下场景中使用本技能:
  • 创建可复用的基础设施模式 - 为重复出现的资源组构建模块
  • 封装复杂配置 - 简化多资源部署配置
  • 跨项目标准化 - 确保基础设施的一致性
  • 整理代码提升可维护性 - 构建大型Terraform项目的结构
  • 构建可组合系统 - 组合多个模块以创建更大型的架构
  • 为基础设施组件版本化 - 发布并管理模块版本
不适用场景:
  • 单一一次性资源
  • 仅使用一次的资源
  • 不同使用场景无差异的资源
触发关键词:
  • "创建可复用Terraform模块"
  • "设计模块结构"
  • "定义模块输入和输出"
  • "组合多个模块"
  • "为Terraform模块版本化"

Quick Start

快速入门

Create a reusable Pub/Sub module in 5 minutes:
bash
undefined
5分钟创建一个可复用的Pub/Sub模块:
bash
undefined

1. Create module structure

1. Create module structure

mkdir -p modules/pubsub-topic cd modules/pubsub-topic
mkdir -p modules/pubsub-topic cd modules/pubsub-topic

2. Create files

2. Create files

cat > main.tf << 'EOF' resource "google_pubsub_topic" "topic" { name = var.topic_name message_retention_duration = "${var.retention_days * 86400}s" labels = var.labels }
resource "google_pubsub_subscription" "subscription" { name = "${var.topic_name}-sub" topic = google_pubsub_topic.topic.name
dead_letter_policy { dead_letter_topic = google_pubsub_topic.dlq.id max_delivery_attempts = var.max_delivery_attempts } }
resource "google_pubsub_topic" "dlq" { name = "${var.topic_name}-dlq" } EOF
cat > variables.tf << 'EOF' variable "topic_name" { type = string description = "Name of the Pub/Sub topic" }
variable "retention_days" { type = number default = 7 description = "Message retention in days" }
variable "max_delivery_attempts" { type = number default = 5 description = "Max delivery attempts before DLQ" }
variable "labels" { type = map(string) default = {} description = "Resource labels" } EOF
cat > outputs.tf << 'EOF' output "topic_name" { value = google_pubsub_topic.topic.name description = "Name of the Pub/Sub topic" }
output "subscription_name" { value = google_pubsub_subscription.subscription.name description = "Name of the subscription" } EOF
cat > main.tf << 'EOF' resource "google_pubsub_topic" "topic" { name = var.topic_name message_retention_duration = "${var.retention_days * 86400}s" labels = var.labels }
resource "google_pubsub_subscription" "subscription" { name = "${var.topic_name}-sub" topic = google_pubsub_topic.topic.name
dead_letter_policy { dead_letter_topic = google_pubsub_topic.dlq.id max_delivery_attempts = var.max_delivery_attempts } }
resource "google_pubsub_topic" "dlq" { name = "${var.topic_name}-dlq" } EOF
cat > variables.tf << 'EOF' variable "topic_name" { type = string description = "Name of the Pub/Sub topic" }
variable "retention_days" { type = number default = 7 description = "Message retention in days" }
variable "max_delivery_attempts" { type = number default = 5 description = "Max delivery attempts before DLQ" }
variable "labels" { type = map(string) default = {} description = "Resource labels" } EOF
cat > outputs.tf << 'EOF' output "topic_name" { value = google_pubsub_topic.topic.name description = "Name of the Pub/Sub topic" }
output "subscription_name" { value = google_pubsub_subscription.subscription.name description = "Name of the subscription" } EOF

3. Use module

3. Use module

cd ../.. cat > main.tf << 'EOF' module "incoming_charges" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-incoming" retention_days = 7 labels = { environment = "production" } } EOF
cd ../.. cat > main.tf << 'EOF' module "incoming_charges" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-incoming" retention_days = 7 labels = { environment = "production" } } EOF

4. Deploy

4. Deploy

terraform init terraform apply
undefined
terraform init terraform apply
undefined

Instructions

操作步骤

Step 1: Decide When to Create a Module

步骤1:确定是否需要创建模块

Create a Module When:
  • ✅ Pattern repeats multiple times in your code
  • ✅ Encapsulates complex resource group (5+ related resources)
  • ✅ Has configurable inputs that vary per use
  • ✅ Produces clear outputs for other modules to consume
Don't Create a Module For:
  • ❌ Single one-off resources
  • ❌ Resources used only once
  • ❌ No variation between uses
  • ❌ Over-engineering simple setup
Example: Pub/Sub topic + subscription + DLQ + IAM (4 resources, repeats twice) → Perfect candidate for a module!
应创建模块的场景:
  • ✅ 模式在代码中多次重复出现
  • ✅ 封装了复杂的资源组(5个及以上相关资源)
  • ✅ 具有可配置的输入,且不同使用场景有差异
  • ✅ 能生成清晰的输出供其他模块调用
不应创建模块的场景:
  • ❌ 单一一次性资源
  • ❌ 仅使用一次的资源
  • ❌ 不同使用场景无差异的资源
  • ❌ 对简单配置过度设计
示例: Pub/Sub主题 + 订阅 + DLQ + IAM(4个资源,重复出现两次) → 非常适合创建模块!

Step 2: Understand Module Structure

步骤2:理解模块结构

Standard Module Layout:
modules/pubsub-topic/
├── main.tf           # Resources
├── variables.tf      # Input variables
├── outputs.tf        # Output values
├── versions.tf       # Provider requirements
└── README.md         # Documentation
Root Module (your main configuration):
terraform/
├── main.tf          # Provider, variables
├── iam.tf           # IAM resources
├── pubsub.tf        # Pub/Sub resources
├── modules/         # Local modules
│   └── pubsub-topic/
├── .terraform.lock.hcl
└── terraform.tfvars
Module Paths:
hcl
undefined
标准模块布局:
modules/pubsub-topic/
├── main.tf           # Resources
├── variables.tf      # Input variables
├── outputs.tf        # Output values
├── versions.tf       # Provider requirements
└── README.md         # Documentation
根模块(主配置文件):
terraform/
├── main.tf          # Provider, variables
├── iam.tf           # IAM resources
├── pubsub.tf        # Pub/Sub resources
├── modules/         # Local modules
│   └── pubsub-topic/
├── .terraform.lock.hcl
└── terraform.tfvars
模块路径:
hcl
undefined

Local module

Local module

module "incoming" { source = "./modules/pubsub-topic" }
module "incoming" { source = "./modules/pubsub-topic" }

Remote module (GitHub)

Remote module (GitHub)

module "incoming" { source = "github.com/org/terraform-modules/pubsub-topic" version = "~> 1.0" }
module "incoming" { source = "github.com/org/terraform-modules/pubsub-topic" version = "~> 1.0" }

Terraform Registry

Terraform Registry

module "incoming" { source = "hashicorp/vault/aws" version = "~> 0.2" }
undefined
module "incoming" { source = "hashicorp/vault/aws" version = "~> 0.2" }
undefined

Step 3: Design Module Inputs (Variables)

步骤3:设计模块输入(变量)

Principles:
  • Keep inputs simple and intuitive
  • Use descriptive names
  • Provide sensible defaults
  • Validate constraints
Example - Pub/Sub Module:
hcl
undefined
设计原则:
  • 保持输入简单直观
  • 使用描述性名称
  • 提供合理的默认值
  • 验证输入约束
示例 - Pub/Sub模块:
hcl
undefined

variables.tf

variables.tf

variable "topic_name" { description = "Name of the Pub/Sub topic" type = string
validation { condition = length(var.topic_name) > 0 && length(var.topic_name) <= 255 error_message = "Topic name must be 1-255 characters" } }
variable "retention_days" { description = "Days to retain messages (0 = unlimited)" type = number default = 7
validation { condition = var.retention_days >= 0 && var.retention_days <= 365 error_message = "Retention must be 0-365 days" } }
variable "labels" { description = "Resource labels" type = map(string) default = {}
validation { condition = alltrue([for k, v in var.labels : length(k) > 0 && length(v) > 0]) error_message = "Labels must have non-empty keys and values" } }
variable "enable_dlq" { description = "Enable Dead Letter Queue" type = bool default = true }

**Input Design Patterns**:
```hcl
variable "topic_name" { description = "Name of the Pub/Sub topic" type = string
validation { condition = length(var.topic_name) > 0 && length(var.topic_name) <= 255 error_message = "Topic name must be 1-255 characters" } }
variable "retention_days" { description = "Days to retain messages (0 = unlimited)" type = number default = 7
validation { condition = var.retention_days >= 0 && var.retention_days <= 365 error_message = "Retention must be 0-365 days" } }
variable "labels" { description = "Resource labels" type = map(string) default = {}
validation { condition = alltrue([for k, v in var.labels : length(k) > 0 && length(v) > 0]) error_message = "Labels must have non-empty keys and values" } }
variable "enable_dlq" { description = "Enable Dead Letter Queue" type = bool default = true }

**输入设计模式**:
```hcl

Simple inputs

Simple inputs

variable "name" { type = string }
variable "name" { type = string }

With defaults

With defaults

variable "replica_count" { type = number; default = 3 }
variable "replica_count" { type = number; default = 3 }

Lists

Lists

variable "allowed_ips" { type = list(string); default = [] }
variable "allowed_ips" { type = list(string); default = [] }

Maps (configuration objects)

Maps (configuration objects)

variable "config" { type = map(object({ retention_days = number dlq_enabled = bool })) }
variable "config" { type = map(object({ retention_days = number dlq_enabled = bool })) }

Flexible object

Flexible object

variable "topic_config" { type = object({ retention_days = number enable_dlq = bool max_retries = number }) default = { retention_days = 7 enable_dlq = true max_retries = 5 } }
undefined
variable "topic_config" { type = object({ retention_days = number enable_dlq = bool max_retries = number }) default = { retention_days = 7 enable_dlq = true max_retries = 5 } }
undefined

Step 4: Design Module Outputs

步骤4:设计模块输出

Principles:
  • Export only necessary values
  • Use descriptive names
  • Document what each output is
  • Mark sensitive outputs
Example - Pub/Sub Module:
hcl
undefined
设计原则:
  • 仅导出必要的值
  • 使用描述性名称
  • 文档化每个输出的含义
  • 标记敏感输出
示例 - Pub/Sub模块:
hcl
undefined

outputs.tf

outputs.tf

output "topic_id" { description = "Topic resource ID" value = google_pubsub_topic.topic.id }
output "topic_name" { description = "Topic name" value = google_pubsub_topic.topic.name }
output "subscription_name" { description = "Subscription name" value = google_pubsub_subscription.subscription.name }
output "dlq_topic_name" { description = "Dead Letter Queue topic name" value = google_pubsub_topic.dlq.name }
output "topic_id" { description = "Topic resource ID" value = google_pubsub_topic.topic.id }
output "topic_name" { description = "Topic name" value = google_pubsub_topic.topic.name }
output "subscription_name" { description = "Subscription name" value = google_pubsub_subscription.subscription.name }
output "dlq_topic_name" { description = "Dead Letter Queue topic name" value = google_pubsub_topic.dlq.name }

Sensitive output

Sensitive output

output "configuration" { description = "Complete module configuration" value = { topic_name = google_pubsub_topic.topic.name dlq_name = google_pubsub_topic.dlq.name } sensitive = true }

**Output Best Practices**:
```hcl
output "configuration" { description = "Complete module configuration" value = { topic_name = google_pubsub_topic.topic.name dlq_name = google_pubsub_topic.dlq.name } sensitive = true }

**输出最佳实践**:
```hcl

✅ GOOD: Specific, documented

✅ GOOD: Specific, documented

output "topic_id" { description = "Google resource ID of the topic" value = google_pubsub_topic.topic.id }
output "topic_id" { description = "Google resource ID of the topic" value = google_pubsub_topic.topic.id }

❌ BAD: Vague, undocumented

❌ BAD: Vague, undocumented

output "id" { value = google_pubsub_topic.topic.id }
output "id" { value = google_pubsub_topic.topic.id }

✅ GOOD: Sensible defaults to avoid null values

✅ GOOD: Sensible defaults to avoid null values

output "labels" { description = "Applied labels" value = merge(var.labels, { module = "pubsub" }) }
undefined
output "labels" { description = "Applied labels" value = merge(var.labels, { module = "pubsub" }) }
undefined

Step 5: Create Module Documentation

步骤5:创建模块文档

README.md Format:
markdown
undefined
README.md格式:
markdown
undefined

PubSub Topic Module

PubSub Topic Module

Creates a Pub/Sub topic with optional Dead Letter Queue.
Creates a Pub/Sub topic with optional Dead Letter Queue.

Usage

Usage

hcl
module "incoming_charges" {
  source = "./modules/pubsub-topic"

  topic_name       = "charges-incoming"
  retention_days   = 7
  enable_dlq       = true
}
hcl
module "incoming_charges" {
  source = "./modules/pubsub-topic"

  topic_name       = "charges-incoming"
  retention_days   = 7
  enable_dlq       = true
}

Arguments

Arguments

  • topic_name
    (required): Name of the topic
  • retention_days
    (optional): Message retention in days (default: 7)
  • enable_dlq
    (optional): Enable Dead Letter Queue (default: true)
  • labels
    (optional): Resource labels (default: {})
  • topic_name
    (required): Name of the topic
  • retention_days
    (optional): Message retention in days (default: 7)
  • enable_dlq
    (optional): Enable Dead Letter Queue (default: true)
  • labels
    (optional): Resource labels (default: {})

Outputs

Outputs

  • topic_id
    : Topic resource ID
  • topic_name
    : Topic name
  • subscription_name
    : Subscription name
  • dlq_topic_name
    : Dead Letter Queue topic name
  • topic_id
    : Topic resource ID
  • topic_name
    : Topic name
  • subscription_name
    : Subscription name
  • dlq_topic_name
    : Dead Letter Queue topic name

Example with Custom Configuration

Example with Custom Configuration

hcl
module "replies" {
  source = "./modules/pubsub-topic"

  topic_name     = "supplier-charges-replies"
  retention_days = 3
  enable_dlq     = true

  labels = {
    environment = "production"
    team        = "charges"
  }
}

output "replies_topic" {
  value = module.replies.topic_name
}
undefined
hcl
module "replies" {
  source = "./modules/pubsub-topic"

  topic_name     = "supplier-charges-replies"
  retention_days = 3
  enable_dlq     = true

  labels = {
    environment = "production"
    team        = "charges"
  }
}

output "replies_topic" {
  value = module.replies.topic_name
}
undefined

Step 6: Compose Modules

步骤6:组合模块

Module Composition: Combining modules to build larger systems.
hcl
undefined
模块组合: 结合多个模块构建更大型的系统。
hcl
undefined

main.tf - Compose multiple modules

main.tf - Compose multiple modules

module "incoming_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-incoming" retention_days = 7 }
module "replies_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-replies" retention_days = 3 }
module "dlq_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-dlq" enable_dlq = false # Don't need DLQ for DLQ! }
module "incoming_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-incoming" retention_days = 7 }
module "replies_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-replies" retention_days = 3 }
module "dlq_pubsub" { source = "./modules/pubsub-topic"
topic_name = "supplier-charges-dlq" enable_dlq = false # Don't need DLQ for DLQ! }

Use outputs from one module as inputs to another

Use outputs from one module as inputs to another

module "iam_bindings" { source = "./modules/pubsub-iam"
topics = [ module.incoming_pubsub.topic_name, module.replies_pubsub.topic_name, ] }
module "iam_bindings" { source = "./modules/pubsub-iam"
topics = [ module.incoming_pubsub.topic_name, module.replies_pubsub.topic_name, ] }

Export composed outputs

Export composed outputs

output "topics" { value = { incoming = module.incoming_pubsub.topic_name replies = module.replies_pubsub.topic_name } }
undefined
output "topics" { value = { incoming = module.incoming_pubsub.topic_name replies = module.replies_pubsub.topic_name } }
undefined

Step 7: Version and Maintain Modules

步骤7:版本化与维护模块

Module Versioning (for published modules):
bash
undefined
模块版本化(针对已发布的模块):
bash
undefined

git tag for versions

git tag for versions

git tag v1.0.0 git push origin v1.0.0
git tag v1.0.0 git push origin v1.0.0

In code

In code

module "pubsub" { source = "github.com/org/terraform-pubsub-module" version = "~> 1.0" }

**Semantic Versioning**:
- `1.0.0` - MAJOR.MINOR.PATCH
- `~> 1.0` - Allows 1.0.x, not 1.1.0
- `>= 1.0, < 2.0` - Allows any 1.x version

**Maintenance Checklist**:
- Update provider versions regularly
- Test module with new Terraform versions
- Document all breaking changes
- Provide migration guides
module "pubsub" { source = "github.com/org/terraform-pubsub-module" version = "~> 1.0" }

**语义化版本控制**:
- `1.0.0` - 主版本.次版本.修订版本
- `~> 1.0` - 允许1.0.x版本,不允许1.1.0及以上
- `>= 1.0, < 2.0` - 允许任何1.x版本

**维护清单**:
- 定期更新Provider版本
- 使用新版本Terraform测试模块
- 文档化所有破坏性变更
- 提供迁移指南

Examples

示例

Example 1: Simple Service Module

示例1:简单服务模块

hcl
undefined
hcl
undefined

modules/gke-service/main.tf

modules/gke-service/main.tf

resource "kubernetes_namespace" "service" { metadata { name = var.namespace } }
resource "kubernetes_deployment" "service" { metadata { namespace = kubernetes_namespace.service.metadata[0].name name = var.service_name }
spec { replicas = var.replicas
selector {
  match_labels = {
    app = var.service_name
  }
}

template {
  metadata {
    labels = {
      app = var.service_name
    }
  }

  spec {
    container {
      name  = var.service_name
      image = var.image

      env {
        name  = "PUBSUB_TOPIC"
        value = var.pubsub_topic
      }
    }
  }
}
} }
resource "kubernetes_namespace" "service" { metadata { name = var.namespace } }
resource "kubernetes_deployment" "service" { metadata { namespace = kubernetes_namespace.service.metadata[0].name name = var.service_name }
spec { replicas = var.replicas
selector {
  match_labels = {
    app = var.service_name
  }
}

template {
  metadata {
    labels = {
      app = var.service_name
    }
  }

  spec {
    container {
      name  = var.service_name
      image = var.image

      env {
        name  = "PUBSUB_TOPIC"
        value = var.pubsub_topic
      }
    }
  }
}
} }

modules/gke-service/variables.tf

modules/gke-service/variables.tf

variable "namespace" { type = string } variable "service_name" { type = string } variable "image" { type = string } variable "replicas" { type = number; default = 3 } variable "pubsub_topic" { type = string }
variable "namespace" { type = string } variable "service_name" { type = string } variable "image" { type = string } variable "replicas" { type = number; default = 3 } variable "pubsub_topic" { type = string }

modules/gke-service/outputs.tf

modules/gke-service/outputs.tf

output "namespace" { value = kubernetes_namespace.service.metadata[0].name } output "deployment_name" { value = kubernetes_deployment.service.metadata[0].name }
output "namespace" { value = kubernetes_namespace.service.metadata[0].name } output "deployment_name" { value = kubernetes_deployment.service.metadata[0].name }

Usage

Usage

module "charges_service" { source = "./modules/gke-service"
namespace = "production" service_name = "supplier-charges" image = "gcr.io/project/charges:1.0.0" replicas = 3 pubsub_topic = module.pubsub.topic_name }
undefined
module "charges_service" { source = "./modules/gke-service"
namespace = "production" service_name = "supplier-charges" image = "gcr.io/project/charges:1.0.0" replicas = 3 pubsub_topic = module.pubsub.topic_name }
undefined

Example 2: Composition Pattern

示例2:组合模式

hcl
undefined
hcl
undefined

main.tf - Compose multiple specialized modules

main.tf - Compose multiple specialized modules

module "pubsub" { source = "./modules/pubsub-topic"
topic_name = "charges" retention_days = 7 }
module "database" { source = "./modules/cloud-sql"
instance_name = "charges-db" database_name = "charges" }
module "gke_service" { source = "./modules/gke-service"
service_name = "charges-processor" image = "gcr.io/project/processor:1.0" pubsub_topic = module.pubsub.topic_name db_connection = module.database.connection_string }
module "pubsub" { source = "./modules/pubsub-topic"
topic_name = "charges" retention_days = 7 }
module "database" { source = "./modules/cloud-sql"
instance_name = "charges-db" database_name = "charges" }
module "gke_service" { source = "./modules/gke-service"
service_name = "charges-processor" image = "gcr.io/project/processor:1.0" pubsub_topic = module.pubsub.topic_name db_connection = module.database.connection_string }

Export all outputs

Export all outputs

output "infrastructure" { value = { pubsub_topic = module.pubsub.topic_name database_host = module.database.host service_name = module.gke_service.deployment_name } }
undefined
output "infrastructure" { value = { pubsub_topic = module.pubsub.topic_name database_host = module.database.host service_name = module.gke_service.deployment_name } }
undefined

Example 3: Dynamic Module Usage with for_each

示例3:使用for_each动态调用模块

hcl
undefined
hcl
undefined

main.tf

main.tf

locals { pubsub_topics = { "incoming" = { retention_days = 7 } "replies" = { retention_days = 3 } "events" = { retention_days = 14 } } }
module "topics" { for_each = local.pubsub_topics
source = "./modules/pubsub-topic"
topic_name = each.key retention_days = each.value.retention_days }
locals { pubsub_topics = { "incoming" = { retention_days = 7 } "replies" = { retention_days = 3 } "events" = { retention_days = 14 } } }
module "topics" { for_each = local.pubsub_topics
source = "./modules/pubsub-topic"
topic_name = each.key retention_days = each.value.retention_days }

Access module outputs

Access module outputs

output "topic_names" { value = { for name, module in module.topics : name => module.topic_name } }
undefined
output "topic_names" { value = { for name, module in module.topics : name => module.topic_name } }
undefined

Requirements

要求

  • Terraform 1.x+
  • Module should have clear input/output contracts
  • Good README documentation
  • Tested with typical use cases
  • Terraform 1.x+
  • 模块应具备清晰的输入/输出契约
  • 完善的README文档
  • 已通过典型用例测试

See Also

相关链接

  • terraform skill - General Terraform
  • terraform-gcp-integration - GCP patterns
  • terraform-state-management - State handling
  • terraform skill - 通用Terraform技能
  • terraform-gcp-integration - GCP集成模式
  • terraform-state-management - 状态管理