Skip to content

10 · Terraform IaC

官方文档:https://developer.hashicorp.com/terraform/docs Registry(模块 & Provider):https://registry.terraform.io/ AWS Provider:https://registry.terraform.io/providers/hashicorp/aws/latest/docs Cloudflare Provider:https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs


1. 项目结构(生产推荐)

terraform/
├── environments/
│   ├── prod/
│   │   ├── main.tf          # 调用模块
│   │   ├── variables.tf     # 环境变量
│   │   ├── outputs.tf       # 输出值
│   │   └── backend.tf       # Remote State 配置
│   └── staging/
│       └── ...
├── modules/
│   ├── vpc/                 # VPC 模块
│   ├── ec2-asg/             # Auto Scaling 模块
│   ├── rds/                 # 数据库模块
│   ├── cloudflare-zone/     # CF Zone 模块(新客户复用)
│   └── igaming-stack/       # 完整 iGaming 环境
└── scripts/
    └── bootstrap.sh         # 首次初始化脚本

2. 基础语法

变量系统

# variables.tf
variable "environment" {
  description = "部署环境"
  type        = string
  default     = "staging"
  validation {
    condition     = contains(["prod", "staging", "dev"], var.environment)
    error_message = "环境必须是 prod、staging 或 dev"
  }
}

variable "db_password" {
  description = "数据库密码"
  type        = string
  sensitive   = true   # 不在日志中显示
}

variable "tags" {
  type = map(string)
  default = {
    Project     = "igaming"
    ManagedBy   = "terraform"
  }
}

# 传入方式
# 1. terraform.tfvars 文件(不要提交到 git)
# 2. 环境变量:TF_VAR_db_password=xxx
# 3. 命令行:terraform apply -var="environment=prod"

本地变量 & 输出

# locals.tf
locals {
  common_tags = merge(var.tags, {
    Environment = var.environment
    Timestamp   = timestamp()
  })

  name_prefix = "${var.project}-${var.environment}"
}

# outputs.tf
output "alb_dns_name" {
  description = "ALB DNS 地址"
  value       = aws_lb.main.dns_name
}

output "rds_endpoint" {
  description = "数据库连接地址"
  value       = aws_rds_cluster.main.endpoint
  sensitive   = true
}

3. State 管理(团队协作必须)

S3 Remote State

# backend.tf
terraform {
  backend "s3" {
    bucket         = "igaming-terraform-state"
    key            = "prod/main.tfstate"
    region         = "ap-southeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"  # 防止并发冲突
  }
}

初始化 State 存储

# 创建 S3 桶和 DynamoDB 表(只需做一次)
aws s3api create-bucket \
  --bucket igaming-terraform-state \
  --region ap-southeast-1 \
  --create-bucket-configuration LocationConstraint=ap-southeast-1

aws s3api put-bucket-versioning \
  --bucket igaming-terraform-state \
  --versioning-configuration Status=Enabled

aws dynamodb create-table \
  --table-name terraform-state-lock \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

4. 模块化(新客户快速开通)

Cloudflare Zone 模块

# modules/cloudflare-zone/main.tf
variable "domain" {}
variable "origin_ip" {}
variable "waf_rules" { default = true }

resource "cloudflare_zone" "main" {
  zone = var.domain
  plan = "pro"
  type = "full"
}

resource "cloudflare_record" "root" {
  zone_id = cloudflare_zone.main.id
  name    = "@"
  value   = var.origin_ip
  type    = "A"
  proxied = true
}

resource "cloudflare_record" "www" {
  zone_id = cloudflare_zone.main.id
  name    = "www"
  value   = var.origin_ip
  type    = "A"
  proxied = true
}

# 标准 WAF 规则
resource "cloudflare_ruleset" "waf" {
  count       = var.waf_rules ? 1 : 0
  zone_id     = cloudflare_zone.main.id
  name        = "Standard iGaming WAF"
  kind        = "zone"
  phase       = "http_request_firewall_custom"

  rules {
    action      = "block"
    description = "Block empty UA and scrapers"
    enabled     = true
    expression  = "(http.user_agent eq \"\") or (cf.threat_score gt 50)"
  }
}

output "zone_id" { value = cloudflare_zone.main.id }
output "nameservers" { value = cloudflare_zone.main.name_servers }

调用模块(新客户开通)

# environments/prod/clients.tf
module "client_brand1" {
  source    = "../../modules/cloudflare-zone"
  domain    = "client1-sportsbet.com"
  origin_ip = "1.2.3.4"
}

module "client_brand2" {
  source    = "../../modules/cloudflare-zone"
  domain    = "client2-casino.com"
  origin_ip = "1.2.3.4"
}

# 批量输出客户的 NS 记录(告知客户去域名商修改)
output "client_nameservers" {
  value = {
    client1 = module.client_brand1.nameservers
    client2 = module.client_brand2.nameservers
  }
}

5. 完整工作流

# 初始化(首次或添加新 Provider)
terraform init

# 格式化(提交前必做)
terraform fmt -recursive

# 验证语法
terraform validate

# 预览变更(重要!生产操作前必看)
terraform plan -out=tfplan

# 应用变更
terraform apply tfplan

# 查看当前状态
terraform show
terraform state list

# 导入已有资源
terraform import aws_instance.web i-0123456789abcdef0

# 删除资源(谨慎!)
terraform destroy -target=aws_instance.test

6. CI/CD 集成(GitHub Actions)

# .github/workflows/terraform.yml
name: Terraform

on:
  pull_request:
    paths: ['terraform/**']
  push:
    branches: [main]
    paths: ['terraform/**']

jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.7.0"

      - name: Configure AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::ACCOUNT:role/github-terraform
          aws-region: ap-southeast-1

      - run: terraform init
        working-directory: terraform/environments/prod

      - run: terraform plan -no-color
        working-directory: terraform/environments/prod
        if: github.event_name == 'pull_request'

      - run: terraform apply -auto-approve
        working-directory: terraform/environments/prod
        if: github.ref == 'refs/heads/main'

7. 重要注意事项

# .gitignore(必须!防止密钥泄露)
*.tfstate
*.tfstate.backup
.terraform/
*.tfvars          # 包含密码等敏感信息
*.tfvars.json
crash.log
override.tf

# 查看计划中有哪些资源会被销毁(危险操作)
terraform plan | grep "will be destroyed"

# 锁定 Provider 版本(防止意外升级)
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.30"  # 只允许 patch 版本升级
    }
  }
  required_version = ">= 1.6"
}

官方文档 & 学习资源

资源 链接
Terraform 官方文档 https://developer.hashicorp.com/terraform/docs
HashiCorp Learn 教程 https://developer.hashicorp.com/terraform/tutorials
Terraform Associate 考试 https://developer.hashicorp.com/terraform/tutorials/certification-003
AWS Provider 文档 https://registry.terraform.io/providers/hashicorp/aws/latest/docs
CF Provider 文档 https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs
Terraform AWS 模块库 https://registry.terraform.io/namespaces/terraform-aws-modules
OpenTofu(开源分支) https://opentofu.org/docs/
tflint 规则检查 https://github.com/terraform-linters/tflint
checkov IaC 安全扫描 https://www.checkov.io/1.Welcome/Quick%20Start.html

常见问题 & 坑

Q: terraform plan 提示 state lock? A: 上次操作异常中断留下了锁,先检查是否有其他人在操作,确认无人后:terraform force-unlock <LOCK_ID>

Q: 导入已有资源后 plan 显示大量变更? A: 导入只导入 state,不生成代码;需手动补全 Terraform 代码,使其与实际资源配置完全一致,再运行 plan 确认无差异

Q: 模块版本更新导致大量资源重建? A: 升级模块前认真阅读 CHANGELOG;用 terraform plan 确认影响范围;破坏性变更分多步骤操作


最后更新:2025-04