🏗️ 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. 用 tfecloud backend 如果你能接受 SaaS——比自建 S3+DynamoDB 简单得多。

🔗 相关专题

⚙️ Ansible 入门 · 🔒 SSL & 域名 · 🐳 Docker Compose · 🏗️ 返回首页