Ansible 与 Terraform 如何选择?区别与协作方式
Terraform 创建 VPS,Ansible 配置服务器。本文解释状态文件与 SSH 的关键差异、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 的注册表页面会定义可编写的资源类型。因此,一台主机上的服务器和另一台主机上的服务器,可能对应不同的资源名称和不同的参数。
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 会尝试创建重复的服务器。只要有多人运行这些命令,就应立即将它保存在远程后端中,因为两个人同时 apply 会产生以下结果:
Error: Error acquiring the state lockOpenTofu 是 Terraform 的一个分支,使用相同的命令和文件格式。截至 July 2026,本指南中的所有内容都适用,只需将 tofu 替换为 terraform。
Ansible 的实际工作方式
Ansible 不需要代理或 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 最接近执行计划的功能:它会报告将要发生的更改,但不会实际修改系统。不过,依赖前置任务的任务在检查模式下可能报告不准确,因为前置任务的更改实际上并未执行。
每次运行结束时,都会生成类似 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 collection 可以直接读取状态。
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 资源,而这些资源需要您在 Terraform 代码中使用 Ansible provider 声明。在添加这些资源之前,ansible-inventory --graph 中不会出现任何内容。
直接生成的 inventory 文件更容易调试,并且适用于任何 provider。当 inventory 中的机器超过少数几台、手动编辑开始产生拼写错误时,该插件才更有价值;这也正是从一台控制机管理多台 Linux 服务器从习惯变成实际工作流的阶段。
确实需要 Terraform 吗?
大多数阅读本文的人暂时不需要。只有在创建和销毁基础设施本身是一项重复任务时,Terraform 才值得投入。如果您通过控制面板订购了一个 VPS,并计划使用两年,那么 Terraform 只描述了一次性操作,还会引入一个不能丢失的状态文件。
在以下情况下可以使用 Terraform:您经常重建环境;staging 必须与 production 完全一致;多人会修改基础设施,而您希望在删除任何内容前先查看可审查的执行计划;或者您管理的对象不只是服务器,还包括通过服务商 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 开始监听之前就返回了地址。应等待端口就绪,而不是添加固定的 sleep。Ansible 提供了 ansible.builtin.wait_for_connection,正是用于此目的;请将其作为 play 的第一个任务执行。当同一个 playbook 的目标从一台新服务器变为一个主机组时,应提前确定一台主机持续无法访问时应如何处理,因为 Ansible 会将该主机从后续执行中移除,而 recap 行是它唯一报告这一情况的位置。
主机密钥已更改。 你销毁并重新创建了服务器,新服务器使用同一地址响应,但密钥已更换。
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 要销毁你未修改的资源。 计划显示了你从未编写的更改。这表示实际基础设施已偏离代码,通常是因为有人在提供商的 Web 控制面板中修改了设置。运行 terraform plan -refresh-only 单独查看这一差异,然后判断错误在代码还是在线资源。无法逐行解释的破坏性计划,绝不要执行 apply。
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 时长,因为启动时间会随镜像和执行计划而变化。