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