模块组合与高级模式

掌握模块组合技巧和高级设计模式。

一、模块组合策略

1.1 单一职责模块

原则: 每个模块只做一件事,做好一件事。

# ✅ 好的设计:单一职责
module "vpc" {
  source = "./modules/vpc"
  # 只负责网络
}

module "database" {
  source = "./modules/database"
  # 只负责数据库
}

module "application" {
  source = "./modules/application"
  # 只负责应用
}

# ❌ 不好的设计:一个模块做所有事
module "everything" {
  source = "./modules/all-in-one"
  # VPC + 数据库 + 应用 + 监控...
}

1.2 模块分层

三层架构:

┌─────────────────────────────────────┐
│     Application Layer (L3)          │  业务逻辑
│  ┌──────────┐  ┌──────────┐        │
│  │ Web App  │  │  API     │        │
│  └──────────┘  └──────────┘        │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     Infrastructure Layer (L2)       │  基础服务
│  ┌──────┐  ┌──────┐  ┌──────┐     │
│  │ EKS  │  │ RDS  │  │  S3  │     │
│  └──────┘  └──────┘  └──────┘     │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│     Foundation Layer (L1)           │  网络基础
│  ┌──────────────────────┐          │
│  │        VPC           │          │
│  └──────────────────────┘          │
└─────────────────────────────────────┘

实现:

# L1: 基础层
module "foundation" {
  source = "./modules/foundation"
  
  vpc_cidr           = "10.0.0.0/16"
  availability_zones = ["us-west-2a", "us-west-2b"]
}

# L2: 基础设施层
module "infrastructure" {
  source = "./modules/infrastructure"
  
  vpc_id     = module.foundation.vpc_id
  subnet_ids = module.foundation.private_subnet_ids
}

# L3: 应用层
module "application" {
  source = "./modules/application"
  
  vpc_id            = module.foundation.vpc_id
  subnet_ids        = module.foundation.private_subnet_ids
  database_endpoint = module.infrastructure.database_endpoint
}

1.3 模块组合模式

垂直组合(Vertical Composition):

# 高层模块调用低层模块
module "complete_stack" {
  source = "./modules/complete-stack"
  
  # 内部调用多个子模块
  # - vpc
  # - database
  # - application
}

水平组合(Horizontal Composition):

# 根模块组合多个独立模块
module "vpc" {
  source = "./modules/vpc"
}

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

module "application" {
  source      = "./modules/application"
  vpc_id      = module.vpc.vpc_id
  db_endpoint = module.database.endpoint
}

二、高级模块模式

2.1 Wrapper 模块

目的: 为第三方模块提供定制化封装。

# modules/vpc-wrapper/main.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"
  
  # 标准化配置
  name = "${var.project_name}-${var.environment}-vpc"
  cidr = var.vpc_cidr
  
  # 企业标准
  enable_dns_hostnames = true
  enable_dns_support   = true
  enable_nat_gateway   = var.environment == "prod"
  
  # 统一标签
  tags = merge(
    var.tags,
    {
      ManagedBy   = "Terraform"
      Module      = "vpc-wrapper"
      Environment = var.environment
    }
  )
  
  # 其他参数传递
  azs             = var.availability_zones
  private_subnets = var.private_subnet_cidrs
  public_subnets  = var.public_subnet_cidrs
}

# 输出代理
output "vpc_id" {
  value = module.vpc.vpc_id
}

output "private_subnet_ids" {
  value = module.vpc.private_subnets
}

2.2 Factory 模块

目的: 根据配置创建不同类型的资源。

# modules/environment-factory/main.tf
locals {
  environments = {
    dev = {
      instance_type    = "t3.micro"
      instance_count   = 1
      enable_backup    = false
      enable_monitoring = false
    }
    staging = {
      instance_type    = "t3.small"
      instance_count   = 2
      enable_backup    = true
      enable_monitoring = true
    }
    prod = {
      instance_type    = "t3.large"
      instance_count   = 3
      enable_backup    = true
      enable_monitoring = true
    }
  }
  
  env_config = local.environments[var.environment]
}

module "vpc" {
  source = "./modules/vpc"
  
  vpc_name = "${var.project_name}-${var.environment}"
  # ... 其他配置
}

module "application" {
  source = "./modules/application"
  
  instance_type  = local.env_config.instance_type
  instance_count = local.env_config.instance_count
  # ... 其他配置
}

module "monitoring" {
  count  = local.env_config.enable_monitoring ? 1 : 0
  source = "./modules/monitoring"
  # ... 配置
}

使用:

module "dev_env" {
  source = "./modules/environment-factory"
  
  project_name = "myapp"
  environment  = "dev"
}

module "prod_env" {
  source = "./modules/environment-factory"
  
  project_name = "myapp"
  environment  = "prod"
}

2.3 Composite 模块

目的: 组合多个相关模块形成完整解决方案。

# modules/web-application/main.tf

# 网络层
module "network" {
  source = "../vpc"
  
  vpc_name             = var.app_name
  vpc_cidr             = var.vpc_cidr
  availability_zones   = var.availability_zones
  public_subnet_cidrs  = var.public_subnet_cidrs
  private_subnet_cidrs = var.private_subnet_cidrs
}

# 安全层
module "security" {
  source = "../security-groups"
  
  vpc_id   = module.network.vpc_id
  app_name = var.app_name
}

# 数据库层
module "database" {
  source = "../rds"
  
  db_name            = var.app_name
  vpc_id             = module.network.vpc_id
  subnet_ids         = module.network.private_subnet_ids
  security_group_ids = [module.security.database_sg_id]
  instance_class     = var.db_instance_class
}

# 应用层
module "application" {
  source = "../ecs-service"
  
  app_name           = var.app_name
  vpc_id             = module.network.vpc_id
  subnet_ids         = module.network.private_subnet_ids
  security_group_ids = [module.security.app_sg_id]
  database_url       = module.database.connection_string
}

# 负载均衡
module "load_balancer" {
  source = "../alb"
  
  app_name           = var.app_name
  vpc_id             = module.network.vpc_id
  subnet_ids         = module.network.public_subnet_ids
  security_group_ids = [module.security.alb_sg_id]
  target_group_arn   = module.application.target_group_arn
}

# 监控
module "monitoring" {
  source = "../cloudwatch"
  
  app_name = var.app_name
  resources = {
    alb_arn = module.load_balancer.alb_arn
    db_id   = module.database.db_instance_id
    ecs_cluster = module.application.cluster_name
  }
}

使用:

module "my_web_app" {
  source = "./modules/web-application"
  
  app_name           = "myapp"
  vpc_cidr           = "10.0.0.0/16"
  availability_zones = ["us-west-2a", "us-west-2b"]
  db_instance_class  = "db.t3.micro"
}

output "application_url" {
  value = module.my_web_app.load_balancer_dns
}

2.4 Multi-Provider 模块

目的: 同时管理多个云平台资源。

# modules/multi-cloud-dns/main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

# AWS Route53
resource "aws_route53_zone" "main" {
  count = var.use_aws ? 1 : 0
  name  = var.domain_name
}

resource "aws_route53_record" "app" {
  count   = var.use_aws ? 1 : 0
  zone_id = aws_route53_zone.main[0].zone_id
  name    = var.subdomain
  type    = "CNAME"
  ttl     = 300
  records = [var.target_dns]
}

# Cloudflare DNS
resource "cloudflare_zone" "main" {
  count = var.use_cloudflare ? 1 : 0
  zone  = var.domain_name
}

resource "cloudflare_record" "app" {
  count   = var.use_cloudflare ? 1 : 0
  zone_id = cloudflare_zone.main[0].id
  name    = var.subdomain
  value   = var.target_dns
  type    = "CNAME"
  proxied = true
}

三、模块间通信

3.1 显式依赖

module "database" {
  source = "./modules/database"
  # ... 配置
}

module "application" {
  source = "./modules/application"
  
  # 显式依赖:通过输出传递
  db_endpoint = module.database.endpoint
  
  # 确保数据库先创建
  depends_on = [module.database]
}

3.2 隐式依赖

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

module "application" {
  source = "./modules/application"
  
  # 隐式依赖:通过引用 VPC 输出
  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
}

3.3 数据传递模式

向下传递(Pass-down):

variable "common_tags" {
  type = map(string)
}

module "vpc" {
  source = "./modules/vpc"
  tags   = var.common_tags
}

module "database" {
  source = "./modules/database"
  tags   = var.common_tags
}

向上传递(Bubble-up):

module "application" {
  source = "./modules/application"
}

output "app_url" {
  value = module.application.load_balancer_dns
}

横向传递(Sibling):

module "database" {
  source = "./modules/database"
}

module "application" {
  source      = "./modules/application"
  db_endpoint = module.database.endpoint  # 从兄弟模块获取
}

四、模块配置模式

4.1 配置对象模式

# modules/eks-cluster/variables.tf
variable "cluster_config" {
  description = "EKS 集群配置"
  type = object({
    version            = string
    endpoint_private   = bool
    endpoint_public    = bool
    log_types          = list(string)
    addon_versions     = map(string)
  })
  
  default = {
    version            = "1.28"
    endpoint_private   = true
    endpoint_public    = false
    log_types          = ["api", "audit"]
    addon_versions     = {
      coredns            = "v1.10.1-eksbuild.2"
      kube-proxy         = "v1.28.1-eksbuild.1"
      vpc-cni            = "v1.14.1-eksbuild.1"
    }
  }
}

# 使用
module "eks" {
  source = "./modules/eks-cluster"
  
  cluster_config = {
    version          = "1.29"
    endpoint_private = true
    endpoint_public  = true
    log_types        = ["api", "audit", "authenticator"]
    addon_versions = {
      coredns    = "v1.10.1-eksbuild.2"
      kube-proxy = "v1.28.1-eksbuild.1"
      vpc-cni    = "v1.15.0-eksbuild.1"
    }
  }
}

4.2 Feature Flags 模式

# modules/application/variables.tf
variable "features" {
  description = "功能开关"
  type = object({
    enable_monitoring    = bool
    enable_backup        = bool
    enable_autoscaling   = bool
    enable_cdn           = bool
  })
  
  default = {
    enable_monitoring  = false
    enable_backup      = false
    enable_autoscaling = false
    enable_cdn         = false
  }
}

# modules/application/main.tf
module "monitoring" {
  count  = var.features.enable_monitoring ? 1 : 0
  source = "./modules/monitoring"
  # ... 配置
}

module "backup" {
  count  = var.features.enable_backup ? 1 : 0
  source = "./modules/backup"
  # ... 配置
}

resource "aws_autoscaling_group" "app" {
  count = var.features.enable_autoscaling ? 1 : 0
  # ... 配置
}

4.3 环境配置模式

# environments/dev.tfvars
environment = "dev"

cluster_config = {
  node_count    = 2
  instance_type = "t3.small"
  disk_size     = 20
}

features = {
  enable_monitoring  = false
  enable_backup      = false
  enable_autoscaling = false
}

# environments/prod.tfvars
environment = "prod"

cluster_config = {
  node_count    = 5
  instance_type = "t3.large"
  disk_size     = 100
}

features = {
  enable_monitoring  = true
  enable_backup      = true
  enable_autoscaling = true
}

五、模块测试

5.1 单元测试

使用 Terratest(Go):

// 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) {
    t.Parallel()
    
    terraformOptions := &terraform.Options{
        TerraformDir: "../modules/vpc",
        Vars: map[string]interface{}{
            "vpc_name": "test-vpc",
            "vpc_cidr": "10.0.0.0/16",
            "availability_zones": []string{"us-west-2a", "us-west-2b"},
        },
    }
    
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    
    // 验证输出
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
    
    subnetIds := terraform.OutputList(t, terraformOptions, "public_subnet_ids")
    assert.Equal(t, 2, len(subnetIds))
}

5.2 集成测试

func TestCompleteStack(t *testing.T) {
    t.Parallel()
    
    terraformOptions := &terraform.Options{
        TerraformDir: "../examples/complete",
    }
    
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    
    // 测试应用是否可访问
    appUrl := terraform.Output(t, terraformOptions, "application_url")
    http_helper.HttpGetWithRetry(
        t,
        fmt.Sprintf("http://%s/health", appUrl),
        nil,
        200,
        "OK",
        30,
        10*time.Second,
    )
}

5.3 合规性测试

使用 Open Policy Agent (OPA):

# policy/vpc.rego
package terraform.vpc

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_vpc"
    not resource.change.after.enable_dns_hostnames
    
    msg := sprintf(
        "VPC %s must have DNS hostnames enabled",
        [resource.address]
    )
}

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_subnet"
    resource.change.after.map_public_ip_on_launch == true
    
    msg := sprintf(
        "Subnet %s should not auto-assign public IPs",
        [resource.address]
    )
}

测试:

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
opa eval -d policy/ -i plan.json "data.terraform.vpc.deny"

六、性能优化

6.1 减少 API 调用

# ❌ 每次都查询 AMI
data "aws_ami" "ubuntu" {
  most_recent = true
  # ... 过滤条件
}

resource "aws_instance" "app" {
  count = 10
  ami   = data.aws_ami.ubuntu.id  # 查询一次,使用多次
}

# ✅ 使用变量传递
variable "ami_id" {
  type = string
}

resource "aws_instance" "app" {
  count = 10
  ami   = var.ami_id
}

6.2 并行化执行

# Terraform 自动并行化独立资源
resource "aws_subnet" "public" {
  count = 3  # 3 个子网并行创建
  # ...
}

# 使用 -parallelism 控制并发数
# terraform apply -parallelism=20

6.3 缓存和复用

# 使用 locals 缓存计算结果
locals {
  subnet_cidrs = cidrsubnets(var.vpc_cidr, 8, 8, 8, 8)
  
  common_tags = merge(
    var.tags,
    {
      ManagedBy = "Terraform"
      Timestamp = timestamp()
    }
  )
}

resource "aws_subnet" "public" {
  count      = 4
  cidr_block = local.subnet_cidrs[count.index]
  tags       = local.common_tags
}

小结

模块组合和高级模式的关键要点:

  • 组合策略:单一职责、分层设计、垂直/水平组合
  • 设计模式:Wrapper、Factory、Composite、Multi-Provider
  • 通信模式:显式/隐式依赖、数据传递
  • 配置模式:配置对象、Feature Flags、环境配置
  • 测试策略:单元测试、集成测试、合规性测试
  • 性能优化:减少 API 调用、并行化、缓存

掌握这些模式可以构建更灵活、可维护的基础设施代码!