Ansible 与 Terraform:您需要哪个?
Terraform 创建 VPS,Ansible 配置服务器。本文解释两者的真正分工、为什么 provisioner 会与两种工具冲突、交接命令,以及何时只需要 Ansible。
Ansible 与 Terraform 的一句话比较
Ansible 与 Terraform 并不是两个执行相同任务的工具,二者不能简单二选一。Terraform 声明基础设施应包含什么:服务器、磁盘、网络和 DNS 记录。Ansible 声明现有计算机内部应处于什么状态:软件包、用户、配置文件和运行中的服务。Terraform 创建 VPS。Ansible 将 VPS 配置为 Web 服务器。
二者都是声明式工具,也都属于基础设施即代码(IaC)。真正的区别在于它们记录的内容不同。Terraform 会写入状态文件,将代码中的每个资源映射到它通过 API 创建的实际对象,因此可以判断删除 5 行配置意味着必须销毁 1 台服务器。Ansible 不会在两次运行之间保存状态。它通过 SSH 连接到计算机,检查当前状态,并且只修改与 playbook 不匹配的部分。
这一点差异解释了本指南中的其他内容,也说明了为什么将这两类任务混用一个工具会导致问题。
Terraform 实际执行的操作
Terraform 通过 provider 插件调用 API。provider 的 registry 页面定义了可以编写的资源类型。因此,一台主机上的服务器和另一台主机上的服务器,可能是名称和参数都不同的资源。
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}将 cloud_server 替换为 provider 文档中定义的资源类型。output 代码块是本指南的重点,因为它决定地址如何离开 Terraform。
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanterraform init 会下载 provider 并写入锁定文件。terraform plan 会输出代码与状态文件之间的差异,最后显示类似 Plan: 1 to add, 0 to change, 0 to destroy. 的行。每次都要阅读这一行。某些参数无法原地修改,计划会在属性旁显示 # forces replacement,后面跟着 1 to add, 0 to change, 1 to destroy。应用此计划会删除服务器并创建一个新的空服务器。这样会导致人们丢失原以为安全的数据。
将计划保存到文件,然后应用该文件,而不是直接运行 terraform apply,可以确保实际执行的内容就是已审核的内容。在这两个命令之间,其他人可能已经修改了基础设施。
terraform.tfstate 保存状态信息。丢失它后,Terraform 将不知道这些服务器属于您,下一次 apply 会尝试创建重复资源。只要有多个人运行这些命令,就应立即将其保存在远程 backend 中,因为两个人同时 apply 会产生以下结果:
Error: Error acquiring the state lockOpenTofu 是 Terraform 的一个 fork,使用相同的命令和文件格式。截至 2026 年 7 月,本指南中的所有内容都适用,只需输入 tofu 而不是 terraform。
Ansible 实际执行的操作
Ansible 无需安装 agent,也不需要 API。它会建立 SSH 连接,将一个小型 Python 模块复制到目标主机,执行该模块,然后将其删除。只要能通过 SSH 访问,并使用 sudo 密码进行操作,Ansible 就能对其进行配置。
- name: Base web server
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running at boot
ansible.builtin.service:
name: nginx
state: started
enabled: trueansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.ymlping 模块会在开始调试 playbook 之前验证 SSH、Python 和 sudo。正常结果应为 web1 | SUCCESS => {"ping": "pong"}。--check --diff 运行是 Ansible 最接近执行计划的功能:它会报告将要发生的更改,但不会实际修改系统。不过,依赖前置任务的任务在 check mode 下可能报告不准确,因为前置更改实际上没有执行。
每次运行结束时,都会显示类似 ok=6 changed=2 unreachable=0 failed=0 的摘要。连续运行同一个 playbook 两次。第二次运行应报告 changed=0。如果某个任务每次运行都报告 changed,则说明它不是幂等的。通常这是一个 command 或 shell 任务,本应使用真正的模块。如果这是你首次接触 Ansible,请从在单台 VPS 上编写第一个 Ansible playbook开始,再逐步扩展。
两种工具的功能重叠与冲突
Terraform 可以使用 remote-exec provisioner 在新服务器上运行命令。HashiCorp 自己的文档将 provisioner 称为最后手段。这有充分的理由。
provisioner 只在资源创建时运行。修改脚本后,现有服务器不会发生任何变化,因为在 Terraform 看来,该资源已经与代码一致。provisioner 执行的步骤不会出现在 terraform plan 中,因此代码审查时看不到这些步骤。如果脚本失败,Terraform 会将资源标记为 tainted;下一次 apply 会销毁并重建服务器,而该服务器很可能本身没有问题。
失败发生的时机也不理想。API 报告服务器创建完成后,provider 就会立即将其报告为已创建,但此时操作系统仍在启动,sshd 还没有开始监听。
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible 则容易让人走向另一个极端。云模块可以创建服务器,对于少量机器来说,这种方式可行。但这样会失去依赖关系图和状态文件。Ansible 会正常创建资源,但如果从 playbook 中删除该任务,资源仍会继续运行并产生费用,因为没有任何记录表明该资源曾由 Ansible 管理。
由此可以得出一条规则:让 Terraform 管理由 API 创建和销毁的对象,让 Ansible 管理已启动操作系统内部的所有内容。
交接要明确
交接点是边界,不是集成。Terraform 完成资源创建后,输出地址并停止。Ansible 从该地址开始工作。
terraform apply -auto-approve
terraform output -raw web_ip
printf '[web]\n%s ansible_user=root\n' "$(terraform output -raw web_ip)" > inventory.ini
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.ymlterraform output -raw 输出单个值,不带引号,也不带 JSON 包装。这正适合在 shell 替换中使用。对于多台服务器,请使用 terraform output -json 并据此生成 inventory,因为 -raw 只能处理单个字符串、数字或布尔值。
两个工具之间保留 ping 这一步很有价值。它可以区分“Terraform 提供了错误地址”和“我的 playbook 存在问题”。如果 playbook 是第一个访问新服务器的组件,这两个问题看起来完全相同。
将 Terraform 状态读取为 Ansible inventory
如果您完全不想编写 inventory 文件,cloud.terraform 集合可以直接读取状态。
ansible-galaxy collection install cloud.terraform将 terraform.yml 写入 playbook 旁边:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.yml在依赖它之前,需要了解两点。该插件会针对 project_path 运行 terraform show,因此该目录必须已完成初始化,否则插件会失败。该插件也不会从您的服务器资源中自动生成主机:它读取 ansible_host 和 ansible_group 资源,而这些资源需要使用 Ansible provider 在 Terraform 代码中声明。在添加这些资源之前,ansible-inventory --graph 中不会出现任何内容。
普通的生成式 inventory 文件更容易调试,并且适用于任何 provider。当 inventory 中的机器数量超过少数几台,手动编辑开始产生拼写错误时,该插件才更有价值。这通常也是从一台控制机管理多台 Linux 服务器从习惯转变为实际工作流的阶段。
您是否真的需要 Terraform?
阅读本文的大多数人目前还不需要。只有在创建和销毁基础设施本身成为重复任务时,Terraform 才值得使用。如果您通过控制面板订购了一个 VPS,并计划使用两年,那么 Terraform 描述的是一次性发生的操作,却会增加一个必须妥善保管的状态文件。
在以下情况下可以考虑使用 Terraform:您经常重建环境;staging 必须与 production 完全一致;多人会修改基础设施,而您希望在删除任何内容前先查看可审查的执行计划;或者您管理的不只是服务器,还包括由 provider API 管理的 DNS 记录、负载均衡器和防火墙规则。
如果服务器长期运行且数量较少,并且日常关注的问题是“这台服务器的配置是否正确”,而不是“这台服务器是否存在”,则仅使用 Ansible 即可。一个用于加固新服务器的 playbook,涵盖的内容与新 VPS 上的前十分钟相同,而且下一台服务器也可以用相同方式执行。
学习顺序由此确定。Ansible 在您拥有第一台服务器时就能带来收益。Terraform 则会在您重建第三个环境时开始体现价值。
交接过程中会出现哪些问题
服务器尚未就绪。 Terraform 执行成功,但 Ansible 立即失败。
fatal: [web1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.10 port 22: Connection refused", "unreachable": true}API 在 sshd 开始监听之前就返回了地址。应等待端口就绪,而不是添加固定的休眠时间。Ansible 提供了 ansible.builtin.wait_for_connection,正适合将其作为 play 的第一个任务运行。
主机密钥发生变化。 您销毁并重新创建了服务器,新服务器使用相同地址响应,但密钥已更换。
Host key verification failed.使用 ssh-keygen -R 203.0.113.10 删除过期条目。Terraform 开始负责重建后,这种情况会频繁发生。因此,对于存储数据的机器,应尽量少执行重建。
Sudo 失败。 fatal: [web1]: FAILED! => {"msg": "Missing sudo password"} 表示 become: true 在该主机上需要密码。可以为部署用户配置免密码 sudo,或者传递 --ask-become-pass。
Terraform 要销毁您未修改的资源。 计划显示了您从未编写的更改。这表示实际基础设施已偏离代码,通常是因为有人在 provider 的 Web 控制面板中修改了设置。运行 terraform plan -refresh-only 单独查看差异,然后判断应以代码还是实时资源为准。无法逐行解释的破坏性计划,绝不要执行。
Ansible 每次运行都报告 changed。 不带 creates 或 when 条件的 shell 任务会无条件运行。这不只是显示问题,因为此时您无法再使用 changed=0 判断服务器是否已达到指定状态。
FAQ
Terraform 可以替代 Ansible 吗?
不能替代服务器内部的配置管理。Terraform 可以通过 remote-exec provisioner 调用脚本,但这些脚本只会在资源创建时运行,不会出现在 terraform plan 中;如果脚本失败,资源会被标记为 tainted,下一次 apply 时会计划销毁并重建。Terraform 没有类似模块的机制,可以检查 nginx 是否已经安装,并在已安装时跳过操作。应使用 Terraform 创建机器,然后交给 Ansible 管理。
Ansible 可以替代 Terraform 吗?
对于少量长期运行的服务器,可以。Ansible 提供了创建服务器的云模块。如果你订购两个 VPS 实例并长期保留它们,这已经足够。但你会失去状态文件和依赖关系图:从 playbook 中删除一个任务后,资源仍会继续运行并产生费用,因为 Ansible 从未记录它创建过该资源。Terraform 则会计划销毁该资源。
我应该先学习哪一个?
如果你目前负责管理服务器,应先学习 Ansible。它在第一台机器上就能发挥作用,只需要 SSH;即使服务器是手动订购的,这项技能也同样适用。Terraform 要到你需要反复重建环境,或管理服务器以外的 provider 资源(例如 DNS 记录和防火墙规则)时,才能体现更大价值。
如何将 Terraform 创建的新服务器 IP 传递给 Ansible?
在 Terraform 代码中声明 output,然后在 apply 后读取它。terraform output -raw web_ip 会输出不带其他内容的值,便于在 shell 替换中使用;当有多个主机时,terraform output -json 会一次性输出所有输出值。将这些值写入 inventory 文件,或者安装 cloud.terraform collection,并将 ansible-inventory -i terraform.yml --graph 指向项目目录。
为什么 playbook 在 Terraform 完成后立即失败?
provider 在其 API 报告服务器已创建后,就会将服务器标记为已创建,但此时操作系统仍在启动,因此 SSH 在最初几秒会被拒绝。该错误是 UNREACHABLE!,并带有 Connection refused。应将 ansible.builtin.wait_for_connection 作为 play 中的第一个任务,而不是自行猜测 sleep 时长,因为启动时间会随镜像和计划而变化。