Terraform on DigitalOcean 2026 — 基础设施代码化指南
Terraform 是 HashiCorp 的开源基础设施代码化 (IaC) 工具,可让您将整个 DigitalOcean 基础设施(服务器、网络、数据库、负载均衡器)定义为声明式代码文件,而不是通过点击 Control Panel。无论您是独自工作还是与团队合作管理大规模基础设施,Terraform 都能显著减少手动设置时间、为基础设施启用版本控制,并使灾难恢复如同运行 terraform apply 一样简单。本指南将介绍如何设置官方 DigitalOcean 提供商、创建真实资源(Droplets、VPC、托管数据库、负载均衡器)、管理状态文件,以及防止常见错误的团队最佳实践。
目录
什么是 Terraform 以及为什么与云一起使用它
Terraform 是 HashiCorp 的基础设施代码化 (IaC) 工具,可让您将基础设施(服务器、网络、数据库等)编写为 HCL (HashiCorp Configuration Language) 配置文件,而不是逐个点击 DigitalOcean Control Panel。它以声明式方式工作:您声明最终基础设施应该是什么样的,Terraform 计算需要创建、修改或删除什么内容以匹配您的声明。这与必须手动编写每一步的命令式脚本不同。对于使用 DigitalOcean 的团队来说,好处是显而易见的。首先是可重现性:您的 .tf 文件提交到 Git 后成为整个基础设施的唯一事实来源。团队中的任何人都可以克隆存储库并运行 terraform apply 来每次都相同地重新创建相同的环境(开发、分阶段或生产)。其次是版本控制:每个基础设施更改都被追踪为一个提交,可通过 pull request 审查,并像任何代码更改一样可回滚。第三是减少人为错误:手动点击创建 Droplet 容易出现诸如忘记 SSH 密钥或防火墙规则的错误,但 Terraform 保证每次应用都得到相同的正确结果。第四是灾难恢复:如果 Droplet 或负载均衡器意外删除或整个区域出现故障,您可以将相同的 .tf 文件应用于另一个区域,比手动设置快得多地恢复系统。Terraform 通过名为 digitalocean/digitalocean 的官方提供商支持 DigitalOcean,该提供商发布在 Terraform Registry 上并涵盖几乎所有主要服务:Droplets、VPC、负载均衡器、托管数据库、卷、防火墙、域名/DNS 和 Kubernetes。这意味着您可以使用一个工具管理整个基础设施堆栈,而不必在 CLI (doctl)、API 和网络仪表板之间切换。
- 声明式配置:声明您期望的结果,而不是像 shell 脚本那样的分步命令
- 像代码一样的版本控制 — 提交、差异、通过 pull request 进行代码审查
- 可重现:克隆存储库并运行
terraform apply以每次都获得相同的环境 - 单个提供商 (
digitalocean/digitalocean) 管理 Droplets、VPC、数据库、负载均衡器等 - 降低人为错误风险并加快您需要快速重建时的灾难恢复
安装 Terraform 和 DigitalOcean 提供商
值得强调的是——在本地计算机上安装 Terraform 很直接,具体取决于平台。在 macOS 上,最简单的方法是使用 Homebrew,命令为 brew tap hashicorp/tap && brew install hashicorp/tap/terraform。在支持 snap 的 Linux 发行版上,使用 sudo snap install terraform --classic,或添加 HashiCorp apt 存储库并通过 apt-get install terraform 安装。在 Windows 上,Chocolatey 提供 choco install terraform。安装后,通过 terraform version 验证二进制文件是否正常工作。接下来,在配置文件中声明 DigitalOcean 提供商,告诉 Terraform 从 Terraform Registry 下载哪个插件。创建一个类似 providers.tf 的文件并添加此块:terraform {\n required_providers {\n digitalocean = {\n source = \"digitalocean/digitalocean\"\n version = \"~> 2.0\"\n }\n }\n}\n\nprovider \"digitalocean\" {\n token = var.do_token\n} 使用 ~> 2.0 固定版本可防止在下一次 terraform init 时意外引入具有破坏性变更的次要更新。关键步骤是保护您的 API 令牌:永远不要在 .tf 文件中硬编码它 — 它们通常会提交到 Git。从 cloud.digitalocean.com/account/api/tokens 生成个人访问令牌(具有读/写权限)并将其声明为变量:variable \"do_token\" {\n type = string\n sensitive = true\n} 通过环境变量 export TF_VAR_do_token=\"dop_v1_xxxxx\" 在运行时传递令牌,或将其存储在 terraform.tfvars 中添加到 .gitignore,这样它永远不会到达存储库。最后,运行 terraform init 以将提供商插件下载到 .terraform/ 文件夹中,您就可以开始了。
- macOS:
brew install hashicorp/tap/terraform/ Linux:snap install terraform --classic - 安装后始终使用
terraform version验证 - 在编写提供商块之前从 cloud.digitalocean.com/account/api/tokens 创建个人访问令牌
- 永远不要在 .tf 文件中硬编码令牌 — 改为使用具有
sensitive = true的变量 - 运行
terraform init以下载提供商插件,每次启动新项目时都要先做这个
使用 .tf 创建 Droplet — 真实示例
提供商准备好后,编写一个资源块来创建实际的 Droplet。此示例创建一个 s-1vcpu-1gb Droplet(1 GiB RAM、1 vCPU、25 GB SSD、$6/月)在 sgp1 区域(新加坡),这为泰国用户提供最低延迟:resource \"digitalocean_droplet\" \"web\" {\n image = \"ubuntu-24-04-x64\"\n name = \"web-01\"\n region = \"sgp1\"\n size = \"s-1vcpu-1gb\"\n ssh_keys = [var.ssh_fingerprint]\n tags = [\"web\", \"production\"]\n} image 字段使用操作系统镜像 slug(例如 ubuntu-24-04-x64),而 size 使用 Droplet 计划 slug — 通过 doctl compute size list 或提供商文档查找这些。对于更强大的工作负载,如 4 GiB RAM/2 vCPU/80 GB SSD,价格为 $24/月,只需将 size 更改为 s-2vcpu-4gb,无需触碰其他任何内容。ssh_keys 字段引用已上传到 DigitalOcean 账户的 SSH 密钥指纹(通过仪表板或 digitalocean_ssh_key 资源),因此启动后立即可以登录,无需密码。编写配置后,遵循三步流程:首先运行 terraform init 加载提供商(如果尚未完成),然后运行 terraform plan 显示 Terraform 计算的更改,而不实际应用 — 注意查看 + create 条目。在继续之前,每次都仔细阅读计划。最后,运行 terraform apply,输入 yes 确认,Terraform 调用 DigitalOcean API 创建 Droplet 并返回其公共 IP。要在应用成功后自动显示 IP,将输出块添加到 outputs.tf 等文件:output \"web_ip\" {\n value = digitalocean_droplet.web.ipv4_address\n} 现在每次成功应用都会在屏幕上显示 IP,无需打开仪表板。
image = \"ubuntu-24-04-x64\" 和 size = \"s-1vcpu-1gb\" ($6/月,1 GiB RAM/1 vCPU/25 GB SSD)image = \"ubuntu-24-04-x64\"和size = \"s-1vcpu-1gb\"($6/月,1 GiB RAM/1 vCPU/25 GB SSD)- 选择
region = \"sgp1\"(新加坡) 以获得对泰国用户的最低延迟 - 通过预上传密钥的指纹引用
ssh_keys— 无需密码登录
通过 Terraform 管理 VPC、数据库、负载均衡器
Terraform 不仅限于 Droplets — 它涵盖几乎所有 DigitalOcean 服务,允许您在同一文件中声明网络、数据库和负载均衡器,并通过引用将它们链接在一起。从 VPC 开始,这是 DigitalOcean 提供的免费私有网络,每个实例不收费,非常适合分离环境(开发/生产),不允许交叉访问:resource \"digitalocean_vpc\" \"prod\" {\n name = \"prod-vpc\"\n region = \"sgp1\"\n} 通过添加 vpc_uuid = digitalocean_vpc.prod.id 到 digitalocean_droplet 块立即将 Droplet 附加到此 VPC — 这是 Terraform 的关键优势:一个资源可以直接引用另一个资源的属性,无需手动复制 ID。对于基本层的托管 PostgreSQL 数据库(1 vCPU/1 GiB RAM,$15.15/月)编写:resource \"digitalocean_database_cluster\" \"pg\" {\n name = \"prod-postgres\"\n engine = \"pg\"\n version = \"16\"\n size = \"db-s-1vcpu-1gb\"\n region = \"sgp1\"\n node_count = 1\n private_network_uuid = digitalocean_vpc.prod.id\n} 将 private_network_uuid 设置为相同的 VPC 意味着数据库没有公共 IP,仅从该同一 VPC 中的资源访问 — 这是推荐的安全做法。对于起价 $12/月 的负载均衡器,它在带标签的 Droplets 之间分发流量:resource \"digitalocean_loadbalancer\" \"web_lb\" {\n name = \"web-lb\"\n region = \"sgp1\"\n droplet_tag = \"web\"\n\n forwarding_rule {\n entry_port = 443\n entry_protocol = \"https\"\n target_port = 80\n target_protocol = \"http\"\n }\n\n healthcheck {\n port = 80\n protocol = \"http\"\n }\n} 使用 droplet_tag 而不是单个 Droplet ID 意味着当您向上扩展并添加带有相同标签的新 Droplets(通过多个 digitalocean_droplet 资源或 count)时,负载均衡器会自动将它们注册,无需编辑负载均衡器配置本身。
- VPC 是免费和无限的 — 在其他资源之前使用
digitalocean_vpc创建单独的开发/生产网络 - 通过类似
vpc_uuid = digitalocean_vpc.prod.id的引用跨资源相互链接 - 托管 PostgreSQL 基本层起价为 $15.15/月 (1 vCPU/1 GiB RAM) 通过
digitalocean_database_cluster
状态文件和 terraform plan/apply/destroy
Terraform 的核心是状态文件,一个名为 terraform.tfstate 的 JSON 文件,在您第一次应用后 Terraform 自动创建。它将您在 .tf 文件中声明的资源映射到它们在 DigitalOcean 上的实际 ID。每个 terraform plan 或 terraform apply 比较三个状态:您的 .tf 配置、状态文件和 DigitalOcean 上的实时状态(通过 API 刷新)以确定要创建/更新/删除的内容。日常工作的三个主要命令是 terraform plan,一个干运行,显示更改而不应用它们 — 完美用于代码审查,然后再合并 pull request;terraform apply,执行计划并更新状态文件;以及 terraform destroy,删除状态中的所有资源。Destroy 很危险,因为它实际上删除东西 — 仅在测试环境或真正停用项目时使用它,始终先运行 terraform plan -destroy 确认。关键警告是永远不要将 terraform.tfstate 提交到 Git — 它以纯文本形式存储每个资源属性,包括数据库密码等秘密。将 terraform.tfstate 和 terraform.tfstate.backup 都添加到 .gitignore 中,不例外。对于团队工作流,本地存储状态是有问题的,因为其他团队成员看不到最新状态,冒着重复或冲突应用的风险。使用远程后端,如 DigitalOcean Spaces (S3 兼容):terraform {\n backend \"s3\" {\n endpoints = { s3 = \"https://sgp1.digitaloceanspaces.com\" }\n bucket = \"team-terraform-state\"\n key = \"prod/terraform.tfstate\"\n region = \"us-east-1\"\n skip_credentials_validation = true\n skip_region_validation = true\n }\n} 有了远程后端,整个团队从单个共享状态文件应用,消除了机器之间的状态漂移。
terraform plan= 干运行预览,没有任何实际更改,应用前始终审查terraform apply= 执行计划并更新状态文件terraform destroy= 删除所有状态管理的资源 — 谨慎使用
团队最佳实践指南
随着项目增长为多个环境或多个团队成员,几个做法可以保持您的 Terraform 代码库可管理和安全。首先,按环境分离状态,这样开发/分阶段/生产不共享一个状态文件。使用 Terraform Workspaces (terraform workspace new production) 或分割为目录,使用不同的后端密钥,如 key = \"dev/terraform.tfstate\" vs. key = \"prod/terraform.tfstate\" — 文件夹方法通常更清晰并减少了意外的跨环境应用。其次,使用模块来消除重复。如果您有一个重复出现的模式,例如 \"Droplet + 防火墙 + 保留 IP\",将其包装为模块并使用不同的变量调用它:module \"api_server\" {\n source = \"./modules/droplet-stack\"\n name = \"api\"\n size = \"s-2vcpu-4gb\"\n region = \"sgp1\"\n} 现在修复该模式一次可更新使用该模块的每个服务,无需复制粘贴。第三,仔细处理秘密。永远不要在提交到存储库的 .tf 或 .tfvars 文件中存储 API 令牌或数据库密码。在 CI/CD 管道中通过环境变量或使用专用秘密管理器传递它们。始终在秘密变量和输出上设置 sensitive = true,这样它们就不会泄露到 terraform plan 或 CI 日志中。第四,集成到 CI/CD 管道中,而不是让每个人从他们的笔记本电脑应用。配置 pull requests 以自动运行 terraform plan 并将结果作为评论发布以供团队审查,然后仅在通过 CI 作业(具有应用权限)合并到主分支后应用。这确保所有基础设施更改都像应用程序代码一样进行代码审查,防止绕过团队监督的单独应用。最后,对每个资源强制执行命名约定和标签 — 标签如 tags = [\"env:production\", \"team:backend\"] 在项目扩展到许多资源时大大简化了跟踪、成本分配和负载均衡器 droplet_tag 过滤。
- 使用 Terraform Workspaces 或基于文件夹的后端密钥按环境分离状态
- 将重复模式包装到模块中,而不是复制粘贴资源块
- 永远不要向存储库提交秘密 — 使用环境变量/秘密管理器并设置
sensitive = true
常见错误及其修复方法
让我们意外的一点是:在生产中使用 Terraform 与 DigitalOcean 时,某些问题反复出现,值得提前解决。首先是状态漂移:当某人通过仪表板或 doctl 直接修改资源(例如调整 Droplet 大小或修改防火墙规则)而不是通过 Terraform,状态文件变得过时。症状包括 terraform plan 显示您在 .tf 文件中未触碰的差异。使用 terraform state show digitalocean_droplet.web 检查当前状态并与实时进行比较。要同步状态与现实而不重新应用,运行 terraform apply -refresh-only (或在较旧版本上 terraform refresh)。更好的是,通过建立团队规则来防止漂移:仅通过 .tf 文件修改 Terraform 管理的资源,永远不要通过仪表板。其次是提供商认证失败,出现 \"Unable to authenticate you\" 或 \"401 Unauthorized\" 之类的错误。常见原因包括令牌过期或被撤销、令牌具有读只权限而配置需要写权限,或环境变量名称不匹配 — 例如,声明 variable \"do_token\" 但设置 export TF_VAR_token=... (缺少 do_ 前缀)。修复:生成具有读/写权限的新个人访问令牌,并仔细检查 TF_VAR_ 前缀与您的变量名称完全匹配。第三是依赖项排序:通常 Terraform 从资源引用(如 vpc_uuid = digitalocean_vpc.prod.id)自动检测顺序,但某些资源缺少直接属性链接,而一个必须在另一个之前完成。明确使用 depends_on:depends_on = [digitalocean_firewall.web_fw] 强制顺序。第四是导入不匹配:成功运行 terraform import 将现有资源导入状态后,运行 terraform plan 仍显示差异,因为您的 .tf 代码与实时属性不匹配(标签、区域、大小不匹配)。用 terraform show 检查实际状态,然后编辑 .tf 字段直到 terraform plan 报告 \"No changes\"。
- 状态漂移:用
terraform state show验证并用terraform apply -refresh-only同步 - 认证错误 401:检查
TF_VAR_do_token命名是否与您的变量匹配并且令牌具有读/写权限 - 当资源必须排序但缺少直接属性引用时使用
depends_on - 在
terraform import后,与terraform show比较直到计划显示 No changes
最佳实践(进阶)
除了环境分离和模块之外,当有多个团队成员和并发应用时,多个做法可以在规模上保持 Terraform on DigitalOcean 的稳定。首先,状态锁定:DigitalOcean Spaces 上的 S3 兼容后端缺少 AWS S3 + DynamoDB 提供的本地锁定,意味着两个人可以同时应用并损坏状态。通过强制执行仅 CI 应用与严格的并发控制来缓解 — 设置 GitHub Actions concurrency: group: terraform-prod 以排队作业,防止并行运行。永远不要让开发人员从笔记本电脑自由应用。其次,使用 Terraform 创建的 .terraform.lock.hcl 文件在 terraform init 后锁定提供商版本。与 terraform.tfstate 不同,此文件应提交到 Git — 它固定提供商校验和,所以每个团队成员和 CI 运行完全相同的提供商版本,防止来自新次要版本的惊喜。第三,在应用前强制执行质量检查:运行 terraform fmt -check 验证格式、terraform validate 检查语法和类型正确性,并可选择向管道添加开源工具(如 tflint 或 tfsec)进行最佳实践和安全扫描(例如,捕捉私有资源上意外的公共访问)。第四,从一开始清晰地构造您的存储库。将可重用模块与特定环境的代码分离:infra/\n modules/\n droplet-stack/\n envs/\n dev/\n main.tf\n backend.tf\n prod/\n main.tf\n backend.tf 每个环境都有自己的 backend.tf 指向不同的状态文件(如之前讨论的)并以不同的变量值调用共享模块。为每个变量添加描述,这样队友就能理解目的而无需阅读代码:variable \"droplet_size\" {\n type = string\n description = \"Droplet size slug e.g. s-1vcpu-1gb\"\n default = \"s-1vcpu-1gb\"\n} 这种结构让您可以扩展到许多环境或服务而无需复制粘贴,并大大降低了通过错误应用到错误环境的风险。
- S3 兼容后端在 Spaces 上缺少本地状态锁定 — 使用严格并发强制仅 CI 应用以防止损坏
- 提交
.terraform.lock.hcl到 Git 以跨团队锁定提供商版本 - 在每次应用前运行
terraform fmt -check和terraform validate,添加 tflint/tfsec 进行更深层检查
常见问题(FAQ)
terraform import digitalocean_droplet.web DROPLET_ID 将现有资源导入状态。您必须写一个 .tf 文件,其属性与实时资源匹配 — Terraform 不会自动生成整个配置(尽管较新版本有部分导入块生成)。terraform plan 查看当前状态与配置,修复潜在问题,然后再次应用 — 无需删除或重置状态。terraform import 恢复每个资源,如果您有很多这样做会很耗时。这就是为什么具有版本控制的远程后端(例如启用对象版本控制的 DigitalOcean Spaces)从第一天开始很重要 — 如果需要,您可以立即恢复先前的状态快照。