🏗️ Terraform 入门
云资源声明式管理、State 管理、Module 复用——独立开发者的最小 Terraform 集合
🤔 为什么用 Terraform
如果你只有 1 台 VPS,不需要 Terraform。但如果你有 3+ 台服务器、DNS 记录、CDN 配置、SSL 证书……手动管理就是定时炸弹。Terraform 让你用代码描述"我要什么",它自动算出"怎么做到"。
| 场景 | 手动操作 | Terraform |
| 新服务器 | 登录控制台 → 点击 → 等待 → 配置 | terraform apply |
| DNS 记录 | 登录 DNS 面板 → 手动添加 | 代码里加 3 行 |
| 灾难恢复 | 手动重建一切(可能遗漏配置) | terraform apply(完全一致) |
| 变更审计 | 没有记录 | Git 历史即审计日志 |
📐 最小 Terraform 项目
# 目录结构
infra/
├── main.tf # 主配置
├── variables.tf # 变量定义
├── outputs.tf # 输出
├── providers.tf # Provider 配置
├── terraform.tfvars # 变量值(不提交 Git)
├── backend.tf # State 存储
└── modules/ # 可复用模块
└── vps/
├── main.tf
├── variables.tf
└── outputs.tf
# providers.tf
terraform {
required_version = ">= 1.7"
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "digitalocean" {
token = var.do_token
}
provider "cloudflare" {
api_token = var.cloudflare_token
}
# backend.tf — State 存储在 S3(不要存本地!)
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "infra/terraform.tfstate"
region = "us-east-1"
}
}
# variables.tf
variable "do_token" {
type = string
sensitive = true
}
variable "cloudflare_token" {
type = string
sensitive = true
}
variable "domain" {
type = string
default = "example.com"
}
variable "ssh_key_fingerprint" {
type = string
}
variable "server_size" {
type = string
default = "s-2vcpu-4gb"
}
# terraform.tfvars(加入 .gitignore!)
do_token = "dop_v1_..."
cloudflare_token = "eyJhbGci..."
ssh_key_fingerprint = "ab:cd:ef..."
🖥️ 主配置:VPS + DNS + 防火墙
# main.tf
# ==========================================
# SSH Key
# ==========================================
data "digitalocean_ssh_key" "main" {
name = "my-laptop"
}
# ==========================================
# VPS
# ==========================================
resource "digitalocean_droplet" "main" {
name = "prod-server"
image = "ubuntu-24-04-x64"
region = "sgp1" # 新加坡
size = var.server_size
ssh_keys = [data.digitalocean_ssh_key.main.id]
# Cloud-init 初始化
user_data = templatefile("cloud-init.yml.tpl", {
hostname = "prod-server"
})
tags = ["production", "terraform"]
}
# ==========================================
# 防火墙
# ==========================================
resource "digitalocean_firewall" "main" {
name = "prod-firewall"
droplet_ids = [digitalocean_droplet.main.id]
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["YOUR_IP/32"] # 只允许你的 IP
}
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
# ==========================================
# DNS 记录
# ==========================================
resource "cloudflare_record" "root" {
zone_id = var.cloudflare_zone_id
name = "@"
value = digitalocean_droplet.main.ipv4_address
type = "A"
proxied = true # 开启 Cloudflare CDN
}
resource "cloudflare_record" "app" {
zone_id = var.cloudflare_zone_id
name = "app"
value = digitalocean_droplet.main.ipv4_address
type = "A"
proxied = true
}
resource "cloudflare_record" "api" {
zone_id = var.cloudflare_zone_id
name = "api"
value = digitalocean_droplet.main.ipv4_address
type = "A"
proxied = true
}
# ==========================================
# 浮动 IP(用于故障转移)
# ==========================================
resource "digitalocean_floating_ip" "main" {
region = digitalocean_droplet.main.region
droplet_id = digitalocean_droplet.main.id
}
# outputs.tf
output "server_ip" {
value = digitalocean_droplet.main.ipv4_address
}
output "floating_ip" {
value = digitalocean_floating_ip.main.ip_address
}
output "server_id" {
value = digitalocean_droplet.main.id
}
📦 Module 复用
# modules/vps/main.tf — 可复用的 VPS 模块
variable "name" { type = string }
variable "region" { type = string }
variable "size" { type = string }
variable "ssh_key_ids" { type = list(string) }
variable "tags" { type = list(string) }
resource "digitalocean_droplet" "this" {
name = var.name
image = "ubuntu-24-04-x64"
region = var.region
size = var.size
ssh_keys = var.ssh_key_ids
tags = var.tags
}
output "ip" {
value = resource.digitalocean_droplet.this.ipv4_address
}
output "id" {
value = resource.digitalocean_droplet.this.id
}
# 使用模块
module "prod" {
source = "./modules/vps"
name = "prod-server"
region = "sgp1"
size = "s-2vcpu-4gb"
ssh_key_ids = [data.digitalocean_ssh_key.main.id]
tags = ["production"]
}
module "staging" {
source = "./modules/vps"
name = "staging-server"
region = "sgp1"
size = "s-1vcpu-2gb"
ssh_key_ids = [data.digitalocean_ssh_key.main.id]
tags = ["staging"]
}
🔄 State 管理
# State 是 Terraform 的核心——它记录了"当前基础设施长什么样"
# ⚠️ State 最佳实践
# 1. 永远存远程(S3/DO Spaces/TF Cloud),不存本地
# 2. 启用 State Locking(防止并发 apply)
# 3. 加密存储(sensitive 数据在 State 中明文)
# 4. State 文件加入 .gitignore
# 远程 State 配置
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "infra/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-lock" # State Locking
}
}
# 常用命令
terraform init # 初始化 + 下载 provider
terraform plan # 预览变更(不执行)
terraform apply # 执行变更
terraform destroy # 删除所有资源
terraform state list # 查看当前 State 中的资源
terraform state show # 查看某个资源的详细信息
terraform output # 查看输出值
# Import 已有资源
terraform import digitalocean_droplet.main 12345678
💡 独立开发者的 Terraform 原则
1. 从小开始——只管 DNS 和 VPS,不要一口气 IaC 化一切 2. terraform plan 是你最好的朋友——每次 apply 前先 plan 3. State 文件比代码更重要——丢了 State = 失去对基础设施的控制 4. 不要在 CI 里自动 apply——人看 plan 确认后再 apply 5. 用 tfe 或 cloud backend 如果你能接受 SaaS——比自建 S3+DynamoDB 简单得多。
🔗 相关专题
⚙️ Ansible 入门 · 🔒 SSL & 域名 · 🐳 Docker Compose · 🏗️ 返回首页