SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-07

Ansible 與 Terraform 怎麼選?差異與使用時機

Terraform 建立 VPS,Ansible 設定伺服器。了解兩者的真正分工、為何 provisioner 會造成問題、交接指令,以及何時只需要 Ansible。

用一句話比較 Ansible 與 Terraform

Ansible 與 Terraform 並不是兩個執行相同工作的工具,兩者之間也不是二選一。Terraform 宣告應存在的基礎架構,包括伺服器、磁碟、網路與 DNS 記錄。Ansible 宣告現有機器內部應達成的狀態,包括套件、使用者、設定檔與執行中的服務。Terraform 建立 VPS。Ansible 將該 VPS 設定為網頁伺服器。

兩者都是宣告式工具,也都屬於基礎架構即程式碼(IaC)。真正的差異在於兩者記錄的內容不同。Terraform 會寫入 state file,將程式碼中的每個資源對應到其透過 API 建立的實際物件,因此能判斷刪除 5 行設定代表必須銷毀 1 台伺服器。Ansible 不會在執行之間保留狀態。它透過 SSH 連線、檢查機器,並只修改與 playbook 不相符的項目。

這項差異也說明了本指南其餘內容,包括為何將兩種工作混用在同一個工具中會導致問題。

Terraform 實際上做了什麼

Terraform 會透過 provider plugin 呼叫 API。provider 的 registry 頁面會定義可使用的 resource type,因此,在一台主機上的伺服器,與在另一台主機上的伺服器,可能是不同的 resource name,也會使用不同的引數。

resource "cloud_server" "web" {
  name  = "web1"
  image = "ubuntu-24.04"
  type  = "small"
}

output "web_ip" {
  value = cloud_server.web.ipv4_address
}

cloud_server 替換為 provider 文件中定義的 resource type。output 區塊是本指南的重點,因為它決定位址如何離開 Terraform。

terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplan

terraform init 會下載 provider,並寫入 lock file。terraform plan 會列出程式碼與 state file 之間的差異,最後顯示類似 Plan: 1 to add, 0 to change, 0 to destroy. 的一行。每次都要讀取這一行。有些引數無法就地變更,plan 會在該屬性旁以 # forces replacement 標示,後面接著 1 to add, 0 to change, 1 to destroy。套用這個 plan 會刪除伺服器,並建立一台全新的空白伺服器。這就是使用者遺失原以為安全的資料的原因。

將 plan 儲存至檔案後套用該檔案,而不是直接執行 terraform apply,可確保實際執行的內容就是你審查過的內容。在這兩個指令之間,其他人可能已經變更基礎架構。

terraform.tfstate 就是記憶。遺失它後,Terraform 不再知道那些伺服器屬於你,因此下一次 apply 會嘗試建立重複的伺服器。只要執行指令的人超過一人,就應立即將它儲存在 remote backend,因為兩人同時套用會產生以下結果:

Error: Error acquiring the state lock

OpenTofu 是 Terraform 的 fork,使用相同的指令與檔案格式。截至 2026 年 7 月,本指南的所有內容都能運作,只要輸入 tofu,而不是 terraform

Ansible 實際執行的內容

Ansible 不需要 agent 或 API。它會建立 SSH 連線,將小型 Python module 複製到目標主機、執行該 module,然後刪除它。只要能透過 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: true
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.yml

ping module 會在開始偵錯 playbook 前,先驗證 SSH、Python 與 sudo。正常結果為 web1 | SUCCESS => {"ping": "pong"}--check --diff 執行是 Ansible 最接近執行計畫的功能:它會回報可能變更的內容,但不會實際套用變更。不過,依賴前置 task 的 task 在 check mode 中可能回報錯誤,因為前置變更實際上並未發生。

每次執行結束時,都會顯示類似 ok=6 changed=2 unreachable=0 failed=0 的摘要。連續執行相同的 playbook 2 次。第 2 次執行應回報 changed=0。如果某個 task 每次執行都回報 changed,表示它不具冪等性。這通常是 commandshell task,原本應改用真正的 module。若你剛接觸這項技術,請從在單一 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 refused

Ansible 則有相反的誘因。Cloud modules 可以建立伺服器,對少量機器而言這種方式可行。但你會失去相依性圖與 state file。Ansible 會正常建立資源;但若從 playbook 刪除該 task,資源仍會持續執行並產生費用,因為沒有任何記錄指出它曾由 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.yml

terraform output -raw 會輸出單一值,不含引號或 JSON 外層包裝。這正適合放在 shell substitution 中。若有多台伺服器,請使用 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/infra
ansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.yml

在依賴此功能前,請先了解兩點。此 plugin 會針對 project_path 執行 terraform show,因此該目錄必須先完成初始化,否則 plugin 會失敗。它也不會根據伺服器資源自行建立主機:它會讀取 ansible_hostansible_group 資源,而這些資源必須使用 Ansible provider 在 Terraform 程式碼中宣告。在加入這些資源前,ansible-inventory --graph 不會出現任何內容。

純文字的生成 inventory 檔案較容易除錯,而且可搭配任何 provider 使用。當 inventory 已經成長到超過幾台機器,手動編輯開始產生拼字錯誤時,plugin 才能發揮效益。這也是從單一控制機器管理多台 Linux 伺服器從習慣轉變為實際工作流程的時機。

你真的需要 Terraform 嗎?

大多數閱讀本文的人目前不需要,至少現在還不需要。當建立與銷毀基礎架構本身已成為重複性工作時,Terraform 才能發揮成本效益。如果你透過控制面板訂購 1 台 VPS,並打算保留 2 年,Terraform 描述的只是一次性工作,卻會額外產生一個不可遺失的 state file。

在以下情況下,應考慮使用 Terraform:你經常重建環境;staging 必須與 production 完全一致;多人會修改基礎架構,而你希望在刪除任何內容前先取得可供審查的 plan;或你管理的對象不只伺服器,還包括由 provider API 管理的 DNS records、load balancers 與 firewall rules。

當伺服器使用期限長、數量少,而且每天需要回答的是「這台機器的設定是否正確」,而不是「這台機器是否存在」時,單獨使用 Ansible 即可。用來強化新伺服器的單一 playbook,涵蓋的內容與 新 VPS 的前 10 分鐘相同,而且下一台伺服器也能以相同方式執行。

學習順序也由此決定。你擁有第 1 台伺服器時,Ansible 就能開始帶來效益。當你第 3 次重建環境時,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 的第一個工作。

主機金鑰已變更。 您刪除並重新建立伺服器後,新伺服器使用相同位址回應,但採用新的金鑰。

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。 沒有 createswhen 條件的 shell 工作會無條件執行。這不只是外觀問題,因為這表示您無法再使用 changed=0 判斷伺服器是否已達到指定狀態。

FAQ

Terraform 可以取代 Ansible 嗎?

不能取代伺服器內部的設定管理。Terraform 可以使用 remote-exec provisioner 呼叫指令碼,但這些指令碼只會在資源建立時執行,不會出現在 terraform plan 中;若執行失敗,Terraform 會將資源標記為 tainted,並在下次 apply 時安排銷毀後重建。Terraform 沒有相當於 module 的機制,可檢查 nginx 是否已安裝,若已安裝便不執行任何動作。使用 Terraform 建立機器後,再交由 Ansible 處理。

Ansible 可以取代 Terraform 嗎?

如果只有少量需要長期運作的伺服器,可以。Ansible 提供可建立伺服器的 cloud modules;如果你訂購 2 個 VPS 並持續使用,這樣就足夠了。但你會失去 state file 和 dependency graph:從 playbook 移除 task 後,資源仍會持續運作並產生費用,因為 Ansible 從未記錄自己建立了該資源。Terraform 則會規劃銷毀該資源。

我應該先學哪一個?

如果你目前已管理伺服器,先學 Ansible。第一台機器就能看到效益,只需要 SSH,而且這項技能也適用於你手動訂購的伺服器。Terraform 的效益較晚出現,適合需要反覆重建環境,或需要管理伺服器以外的 provider 資源時使用,例如 DNS 記錄和防火牆規則。

如何將 Terraform 建立的新伺服器 IP 傳給 Ansible?

在 Terraform 程式碼中宣告 output,然後在 apply 後讀取它。terraform output -raw web_ip 會輸出不含其他文字的值,方便用於 shell substitution;當有多台主機時,terraform output -json 會一次提供所有 output。將輸出寫入 inventory file,或安裝 cloud.terraform collection,並將 ansible-inventory -i terraform.yml --graph 指向專案目錄。

為什麼 Terraform 完成後,我的 playbook 立即失敗?

provider 在 API 回報伺服器已建立時,就會將伺服器標示為已建立;此時作業系統可能仍在開機,因此 SSH 在最初幾秒會被拒絕。錯誤是 UNREACHABLE!,並搭配 Connection refused。將 ansible.builtin.wait_for_connection 設為 play 的第一個 task,不要自行猜測 sleep 時間,因為開機時間會依 image 和 plan 而有所不同。