Ansible 與 Terraform 怎麼選?差異與使用時機
Terraform 負責建立 VPS,Ansible 負責設定伺服器。了解兩者的 state file 差異、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,因此,在一台主機上的 server 與另一台主機上的 server,可能是不同的 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 tfplanterraform 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 會刪除 server,並建立一台全新的空白 server。這就是人們遺失原以為安全的資料的原因。
將 plan 儲存至檔案,再套用該檔案,而不是直接執行單獨的 terraform apply,可確保實際執行的內容就是你檢查過的內容。在這兩個指令之間,其他人可能已變更基礎架構。
terraform.tfstate 就是記錄狀態的記憶。遺失它後,Terraform 將不知道那些 server 屬於你,因此下一次 apply 會嘗試建立重複的 server。只要執行指令的人超過 1 位,就應立即將它保存在 remote backend,因為兩人同時套用會產生以下結果:
Error: Error acquiring the state lockOpenTofu 是 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: 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 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 執行兩次。第二次執行應回報 changed=0。如果某個 task 每次執行都回報 changed,表示它不具冪等性。這通常是 command 或 shell task,而原本應使用真正的 module。如果這是你第一次接觸 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 則有相反的風險。Cloud modules 可以建立伺服器,管理少量機器時也能正常運作。但這會失去相依性圖與狀態檔案。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 substitution 中。若有多台伺服器,請使用 terraform output -json,再根據輸出建立 inventory,因為 -raw 只能處理單一字串、數字或布林值。
保留兩個工具之間的 ping 步驟很有價值。它能區分「Terraform 提供了錯誤的位址」與「playbook 有錯誤」。如果 playbook 是第一個接觸新伺服器的元件,這兩種問題看起來會完全相同。
將 Terraform state 讀取為 Ansible inventory
如果不想建立 inventory 檔案,cloud.terraform collection 可以直接讀取 state。
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使用前請先了解兩點。此 plugin 會針對 project_path 執行 terraform show,因此該目錄必須已完成初始化,否則 plugin 會失敗。它也不會從伺服器資源自動建立主機:它會讀取 ansible_host 與 ansible_group 資源;這些資源必須使用 Ansible provider 在 Terraform 程式碼中宣告。在加入這些資源前,ansible-inventory --graph 不會出現任何內容。
純文字的產生式 inventory 檔案較容易偵錯,也能搭配任何 provider 使用。當 inventory 擴充到超過少數幾台機器,手動編輯開始產生拼寫錯誤時,這個 plugin 才真正發揮效益;同一個階段,從單一控制機器管理多台 Linux 伺服器也會從習慣變成實際的工作流程。
真的需要 Terraform 嗎?
閱讀本文的大多數人目前不需要,至少現在還不需要。當建立與銷毀基礎架構本身成為重複性工作時,Terraform 才能發揮成本效益。如果你透過控制面板訂購一台 VPS,並打算保留兩年,Terraform 描述的是只會發生一次的工作,卻會新增一個不可遺失的狀態檔案。
在你經常重建環境、必須讓 staging 與 production 完全一致、多人會修改基礎架構且希望在刪除任何內容前先檢視計畫,或管理範圍已從伺服器擴大到 provider API 中的 DNS 記錄、負載平衡器與防火牆規則時,才應考慮使用 Terraform。
當伺服器的生命週期較長、數量較少,而且日常問題是「這台主機的設定是否正確」,而不是「這台主機是否存在」時,僅使用 Ansible 即可。單一 playbook 就能完成新伺服器的強化設定,涵蓋的內容與 新 VPS 上的前 10 分鐘相同,而且下一台伺服器也能以相同方式執行。
學習順序也由此決定。你擁有第一台伺服器時,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 的第一個工作。當同一份 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 想要刪除你未修改的資源。 計畫顯示你從未撰寫的變更,表示實際基礎架構已與程式碼不一致,通常是有人在 provider 的 Web 管理介面中變更了設定。執行 terraform plan -refresh-only,單獨查看這項差異,再判斷應修正程式碼或實際資源。無法逐行說明的破壞性計畫,絕不要套用。
Ansible 每次執行都回報 changed。 沒有 creates 或 when guard 的 shell 工作會無條件執行。這不只是表面問題,因為這表示你無法再使用 changed=0 判斷伺服器是否已達到指定狀態。
FAQ
Terraform 能取代 Ansible 嗎?
不能取代伺服器內部的組態管理。Terraform 可以使用 remote-exec provisioner 呼叫指令碼,但這些指令碼只會在建立資源時執行,永遠不會出現在 terraform plan 中;如果執行失敗,資源會被標記為 tainted,下一次 apply 時會排程銷毀並重建。Terraform 沒有相當於 module 的機制,能檢查 nginx 是否已安裝,並在已安裝時略過處理。請使用 Terraform 建立機器,再交由 Ansible 管理。
Ansible 能取代 Terraform 嗎?
如果只有少量且會長期運作的伺服器,可以。Ansible 提供可建立伺服器的 cloud modules;如果你訂購 2 個 VPS instance 並長期保留,這樣就足夠。你會失去 state file 與相依性圖:從 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 會一次提供所有輸出。請將輸出寫入 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 而異。