面向独立开发者的基础设施即代码:Terraform、Pulumi,以及何时 Shell 脚本足够
你有一个 VPS、一个域名,以及一个刚开始获得真实流量的副项目。凌晨 2 点出问题了,你通过 SSH 登录,手动修复,然后意识到你不知道上次改了什么。三个月后,你需要搭建一个暂存环境,却发现你的生产服务器是一个”雪花服务器”——一个独特的人工制品,由数十个未记录的调整组成,这些调整只存在于那台机器上,别处无处可寻。
这是独立开发者第一次遇到基础设施即代码的时刻:不是从平台工程团队借鉴的理论最佳实践,而是真正的个人需求。问题不在于是否采用 IaC,而在于你实际需要多少。
当你就是整个团队时,为什么 IaC 很重要
基础设施即代码的标准论点围绕协作展开——版本控制、代码审查、团队间的共同理解。对于独立开发者,去掉所有这些,论点仍然成立,但出于完全不同的原因:你的记忆并不可靠。
你基础设施的声明性描述作为活文档存在。它记录的不仅是存在什么,还有你的意图。当你六个月后回到一个项目,一个 Terraform 文件会准确告诉你配置了哪些 DNS 记录、实施了哪些防火墙规则以及哪些云资源正在运行。你的 bash 历史记录不会。
还有灾难恢复的角度。独立开发者很少会考虑这个问题,直到他们需要时。如果你的 VPS 提供商出现故障,或者你不小心销毁了一个云服务器,重建一切需要多长时间?有了合适的 IaC,答案是几分钟。没有的话,答案就是”我花多长时间才能记得我做了什么”。
但好处伴随着真正的成本:学习曲线、额外的工具、状态管理开销,以及过度工程的诱惑。诀窍是在你的实际情况下,找到合适抽象级别的合适工具。
Terraform:行业标准
Terraform 仍然是最广泛采用的 IaC 工具,理由充分。它使用 HashiCorp 配置语言 (HCL) 的声明性模型与大多数人思考基础设施的方式清晰对应:描述你想要的,然后让工具弄清楚如何实现。
一个面向独立开发者的最小化 Terraform 配置可能如下所示:
resource "digitalocean_droplet" "web" {
image = "ubuntu-24-04-x64"
name = "web-production"
region = "nyc3"
size = "s-1vcpu-1gb"
ssh_keys = [digitalocean_ssh_key.main.fingerprint]
}
resource "cloudflare_record" "web" {
zone_id = var.cloudflare_zone_id
name = "app"
content = digitalocean_droplet.web.ipv4_address
type = "A"
proxied = true
}
即使你从未接触过 Terraform,这也是可读的。一个 droplet 被创建,一个 DNS 记录指向它。运行 terraform apply,两个资源就存在了。运行 terraform destroy,它们就消失了。
独立使用的优势: HCL 简单到可以在一个周末学会。提供者生态系统非常庞大 — 几乎每个云服务都有 Terraform 提供者。plan/apply 工作流程让你在变更发生前可以预览,当你是唯一能发现错误的人时,这非常宝贵。
摩擦点: HCL 不是一种编程语言。一旦你需要条件逻辑、对动态数据的循环或超出基本插值的字符串操作,你就开始与语法作斗争。Terraform 的类型系统很简单,复杂的配置常常感觉像是在绕着语言工作,而不是与语言协作。
Pulumi:编写真实代码
Pulumi 采取了根本不同的方法:不是使用领域特定语言,而是用 TypeScript、Python、Go 或 C# 编写基础设施定义。对于已经习惯用代码思考的开发者来说,这感觉自然得多。
import * as digitalocean from "@pulumi/digitalocean";
import * as cloudflare from "@pulumi/cloudflare";
const droplet = new digitalocean.Droplet("web", {
image: "ubuntu-24-04-x64",
name: "web-production",
region: "nyc3",
size: "s-1vcpu-1gb",
});
new cloudflare.Record("web", {
zoneId: config.require("cloudflareZoneId"),
name: "app",
content: droplet.ipv4Address,
type: "A",
proxied: true,
});
结果与 Terraform 示例功能相同。但因为这是 TypeScript,你获得完整的 IDE 支持、类型检查、编写函数和抽象的能力,以及对整个 npm 生态系统的访问。需要在部署期间从 API 获取某些内容?导入 axios。需要复杂的字符串格式化?只需编写 JavaScript。
对于独立开发者,Pulumi 的优势是认知整合。如果你已经为应用程序编写 TypeScript,用同一种语言编写基础设施意味着减少一次上下文切换。你可以在应用程序和基础设施代码之间共享类型。你可以使用已经熟悉的相同测试框架为基础设施编写单元测试。
缺点是复杂性。Pulumi 需要运行时,有自己的状态管理服务(尽管你可以自托管状态后端),其文档虽然在改进,但不如 Terraform 成熟。当出现问题时,调试涉及你的代码和 Pulumi 的内部引擎,这可能不够透明。
CDKTF:折中方案
Terraform 云开发工具包 (CDKTF) 试图将 Terraform 的提供者生态系统与通用编程语言相结合。你编写 TypeScript(或 Python、Java、C#、Go),然后 CDKTF 将其合成为 Terraform JSON,再通过标准 Terraform 引擎应用。
这在理论上很有吸引力:你可以获得 Terraform 经实战检验的提供者和状态管理,同时拥有真实编程语言的表现力。在实践中,CDKTF 添加了一个编译层,可能会在你编写的内容和 Terraform 执行的内容之间引入细微的不匹配。错误消息有时会引用生成的 JSON 而不是你的源代码,使调试变得令人沮丧。
对于独立开发者,如果你已经投入 Terraform 生态系统但觉得 HCL 限制太多,CDKTF 值得考虑。否则,对于小规模基础设施,额外的抽象层很少能证明其合理性。
工具比较
| 标准 | Terraform (HCL) | Pulumi | CDKTF | Shell 脚本 | Ansible |
|---|---|---|---|---|---|
| 语言 | HCL (DSL) | TS, Python, Go, C# | TS, Python, Go, C#, Java | Bash / Zsh | YAML + Jinja2 |
| 学习曲线 | 低-中 | 中 | 中-高 | 低 | 中 |
| 状态管理 | 内置(本地/远程) | Pulumi Cloud 或自托管 | Terraform 后端 | 无 | 无(无代理) |
| 提供者生态系统 | 优秀 | 良好(增长中) | 优秀(使用 TF 提供者) | 不适用(原始 API 调用) | 良好(模块) |
| 幂等性 | 是 | 是 | 是 | 需要手动处理 | 是 |
| 独立开发者最佳用途… | 管理 3+ 个云资源 | 你希望使用一种语言处理所有事情 | HCL 感觉过于限制 | 1-2 个简单资源 | 资源配置后的服务器配置 |
| 成本(独立使用) | 免费 | 提供免费层级 | 免费 | 免费 | 免费 |
| 偏差检测 | 是(terraform plan) |
是(pulumi preview) |
是(通过 Terraform) | 无 | 部分 |
何时 Shell 脚本真正足够
这里有一个观点可能会引起基础设施即代码(IaC)纯粹主义者的反对:有时候,shell脚本是正确的解决方案。
如果你的整个基础设施只是一台你一次性配置并偶尔维护的VPS,那么使用Terraform或Pulumi的开销可能会超过其带来的好处。一个编写良好的shell脚本,通过调用云服务提供商的CLI,可以用50行代码配置服务器、设置DNS和配置防火墙。它不是幂等的,不跟踪状态,也不会处理漂移检测——但如果你每年重建服务器时只运行一次,这些缺点就无关紧要了。
当出现以下任何一种情况时,shell脚本就变得不够用了:
- 你需要跨多个服务提供商管理资源(VPS + DNS + CDN + 对象存储)
- 你需要定期重现你的基础设施(暂存环境、客户项目)
- 你的基础设施有必须按顺序创建的依赖关系
- 你希望在应用更改前预览变更
- 你曾因手动更改导致服务中断而吃过苦头
对于大多数独立开发者来说,实际的门槛是:一旦你管理的相互关联的云资源超过三个左右,投资一个合适的IaC工具就开始回本了。
没有团队的状态管理
对于刚接触IaC的开发者来说,状态是最容易被误解的概念。Terraform的状态文件是一个JSON文档,将你的配置映射到实际资源。如果丢失它,Terraform就不再知道它管理的是什么。如果损坏它,你将面临痛苦的恢复过程。
对于独立开发者,状态管理的选择与团队有所不同:
本地状态配合版本控制:最简单的方法。将你的terraform.tfstate文件保存在私有Git仓库中。从技术上讲这不推荐,因为状态文件可能包含敏感信息,但对于使用私有仓库且状态中没有敏感凭证的独立开发者来说,这是可行的。如果你处理任何敏感信息,可以使用git-crypt或sops对文件进行加密。
使用S3或等效服务的远程状态:将你的状态存储在启用了版本控制的对象存储桶中。这种方法更健壮——你可以通过桶版本控制获得自动备份,并且如果本地机器损坏,也能消除丢失状态的风险。对于个人使用,Backblaze B2或Cloudflare R2桶几乎不花钱,同时能提供适当的状态锁定功能。
Pulumi Cloud免费套餐:如果你使用Pulumi,他们的托管服务会为你管理状态。免费套餐可以轻松满足独立开发者的需求。这完全消除了你对状态管理的担忧,代价是依赖第三方服务。
无论你选择哪种方法,规则都很简单:状态必须备份,并且绝不能在公共仓库中分享。
Ansible 用于配置 vs. Terraform 用于预配置
一个常见的混淆来源是预配置和配置之间的界限。Terraform 创建基础设施:服务器、网络、DNS 记录、负载均衡器。Ansible 配置在该基础设施上运行的内容:安装软件包、部署应用程序、管理 systemd 服务、编辑配置文件。
这些工具是互补的,而非竞争关系。一个实用的独立开发者工作流程如下所示:
- Terraform 预配置一台 VPS,设置 DNS 记录,在提供商级别配置防火墙规则,并创建您需要的任何对象存储桶或托管数据库。
- Ansible 通过 SSH 连接到预配置的服务器,安装 Docker,配置 Nginx 或 Caddy,设置 SSL 证书,部署您的应用程序,并管理持续的配置更改。
您可以使用 Terraform 的 remote-exec 预配置器在服务器创建后运行命令,但这是有意限制的。HashiCorp 明确建议,对于基本引导之外的所有操作,应使用专用的配置管理工具。
对于认为这两种工具都过度的独立开发者,Ansible 单独可以处理相当多的任务。它可以通过自己的模块生态系统预配置云资源(尽管不如 Terraform 优雅),并且其无代理、基于 SSH 的方法意味着您的服务器上无需安装任何东西。如果您只打算学习一个工具,Ansible 更广泛的应用性使其成为一个强有力的选择。
过度工程的代价
基础设施即代码生态系统有一种向复杂性的引力。阅读足够的博客文章,您会说服自己,您的双服务器项目需要 Terraform 工作区、带有 DynamoDB 锁定的远程状态、基础设施变更的 CI/CD 管道、自动漂移检测,以及为您自研 API 定制的提供商。
这是过度工程,它给独立开发者带来了真实的成本:
- 花在工具而非产品上的时间。 每小时配置您的 IaC 管道的时间,都是没有花在用户真正关心的应用程序上的时间。
- 维护负担。 Terraform 提供商会更新,API 会变更,您精心制作的配置会出现弃用警告,每次运行计划时都会提醒您。
- 抽象债务。 过度抽象的基础设施代码,在您离开数月后回来时,会变得更难理解。一个扁平、重复的 Terraform 文件比深度嵌套的模块层次结构更容易理解。
解药是实用主义。从最小可行的 IaC 开始,只有在实际问题迫使您时才扩展。
实用的入门模式:VPS + DNS + SSL
对于准备采用基础设施即代码的独立开发者,这里有一个具体的起点,涵盖了最常见的设置:
项目结构:
infra/
main.tf # VPS + DNS 资源
variables.tf # 提供者令牌、域名、区域
outputs.tf # IP 地址、主机名
terraform.tfvars # 实际值(已 git 忽略)
ansible/
playbook.yml # 服务器配置
inventory.ini # 从 Terraform 输出生成
Terraform 管理的内容:
- 一个带有您首选操作系统镜像的 VPS 实例
- 将您的域名指向 VPS 的 DNS A 记录
- 电子邮件的 DNS 记录(MX、SPF、DKIM,如适用)
- 仅允许端口 22、80、443 的云防火墙
- 向云提供商注册 SSH 密钥
Ansible 管理的内容:
- 系统更新和无人值守升级
- 通过 Let’s Encrypt 自动提供 SSL 的 Caddy 或 Nginx
- Docker 和 Docker Compose 安装
- 通过 Docker Compose 进行应用程序部署
- 基本加固:fail2ban、仅 SSH 密钥认证、防火墙规则
这种模式可以处理 80% 的独立开发者基础设施需求。它在五分钟内完成配置,可重现,并且总配置足够小,可以记在脑子里。只有当您的项目需要时,才从这里扩展。
做出决定
独立开发者关于基础设施即代码的问题,最终归结为一种针对您个人情况的成本效益分析。这是一个决策框架:
使用 shell 脚本,如果您有一台很少重建的服务器,并且您能够接受未记录的手动更改所带来的风险。
使用 Terraform,如果您管理多个云资源,想要变更预览,并且倾向于使用文档完善的专注工具。
使用 Pulumi,如果您希望基础设施与应用程序使用同一种语言定义,并且能够接受较小的社区规模。
单独使用 Ansible,如果您的基础设施简单但服务器配置复杂,或者您想要一个同时用于配置和部署的工具。
一起使用 Terraform + Ansible,如果您希望在配置和部署之间有清晰的分离,并计划管理可能增长的基础设施。
最糟糕的选择是根本不做选择——继续 SSH 到服务器并手动更改,同时告诉自己稍后会记录。您不会稍后记录。选择覆盖您当前需求的最简单工具,将您的配置提交到私有仓库,然后继续构建真正重要的事情。
Michael Sun 为独立开发者构建基础设施并进行写作。他当前的技术栈使用 Terraform 进行资源预配置,Ansible 进行配置管理,偶尔使用 shell 脚本处理中间的所有任务。
