Ansible hay Terraform: bạn cần công cụ nào?
Terraform tạo VPS, Ansible cấu hình máy. Xem ranh giới thực tế, lý do provisioner gây lỗi, lệnh bàn giao và khi nào chỉ cần Ansible.
Ansible và Terraform trong một câu
Ansible và Terraform không phải là lựa chọn giữa hai công cụ làm cùng một việc. Terraform khai báo hạ tầng nào tồn tại: server, disk, network, bản ghi DNS. Ansible khai báo trạng thái cần có bên trong một máy đã tồn tại: package, user, file cấu hình, service đang chạy. Terraform tạo VPS. Ansible biến VPS đó thành web server.
Cả hai đều mang tính khai báo và đều được gọi là infrastructure as code (IaC). Điểm khác biệt thực sự là cách chúng ghi nhớ trạng thái. Terraform ghi một state file ánh xạ từng resource trong code của bạn với object thực mà nó đã tạo thông qua API. Nhờ đó, Terraform biết việc xóa 5 dòng có nghĩa là phải xóa 1 server. Ansible không ghi nhớ gì giữa các lần chạy. Nó kết nối qua SSH, kiểm tra máy và chỉ thay đổi những gì chưa khớp với playbook.
Chỉ một khác biệt này đã giải thích các nội dung còn lại trong hướng dẫn, bao gồm lý do việc gộp hai nhiệm vụ này vào cùng một công cụ thường dẫn đến lỗi.
Terraform thực sự làm gì
Terraform gọi API thông qua một provider plugin. Trang registry của provider định nghĩa các loại resource mà bạn có thể khai báo. Vì vậy, server trên một host và server trên một host khác là các tên resource khác nhau, với các tham số khác nhau.
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}Thay cloud_server bằng loại resource được provider của bạn ghi rõ trong tài liệu. Block output là phần quan trọng nhất trong hướng dẫn này, vì nó xác định cách địa chỉ được công khai ra Internet từ Terraform.
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanterraform init tải provider xuống và ghi một lock file. terraform plan in ra khác biệt giữa code của bạn và state file, kết thúc bằng một dòng như Plan: 1 to add, 0 to change, 0 to destroy.. Hãy đọc dòng đó mỗi lần. Một số tham số không thể thay đổi trực tiếp trên resource hiện tại. Plan sẽ cho biết điều này bằng # forces replacement bên cạnh attribute, sau đó là 1 to add, 0 to change, 1 to destroy. Khi áp dụng plan đó, Terraform sẽ xóa server và tạo một server mới trống. Đây là cách người dùng làm mất dữ liệu mà họ tưởng đã an toàn.
Lưu plan vào file rồi áp dụng file đó, thay vì chạy terraform apply trực tiếp, bảo đảm rằng plan bạn đã review chính là plan được chạy. Giữa hai lệnh, người khác có thể đã thay đổi hạ tầng.
terraform.tfstate là bộ nhớ trạng thái. Nếu làm mất nó, Terraform không còn biết những server đó thuộc về bạn, nên lần apply tiếp theo sẽ cố tạo các bản sao. Hãy lưu state trong một remote backend ngay khi có nhiều hơn một người chạy các lệnh này, vì hai người apply cùng lúc sẽ tạo ra kết quả sau:
Error: Error acquiring the state lockOpenTofu là một fork của Terraform, sử dụng cùng các lệnh và cùng định dạng file. Tính đến July 2026, mọi nội dung trong hướng dẫn này đều hoạt động nếu bạn nhập tofu thay cho terraform.
Ansible thực sự làm gì
Ansible không cần agent và cũng không cần API. Nó mở kết nối SSH, sao chép một module Python nhỏ lên máy đích, chạy module đó rồi xóa đi. Bất kỳ hệ thống nào bạn có thể truy cập bằng SSH và mật khẩu sudo đều có thể được Ansible cấu hình.
- 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.ymlModule ping kiểm tra SSH, Python và sudo trước khi bạn bắt đầu debug playbook. Kết quả hợp lệ là web1 | SUCCESS => {"ping": "pong"}. Lệnh chạy --check --diff là thứ gần với một kế hoạch nhất trong Ansible: lệnh báo những gì sẽ thay đổi mà không thực sự thay đổi hệ thống. Tuy nhiên, các task phụ thuộc vào những task trước đó có thể báo sai trong check mode vì thay đổi của task trước chưa thực sự được thực hiện.
Mỗi lần chạy kết thúc bằng một bản tổng kết như ok=6 changed=2 unreachable=0 failed=0. Hãy chạy cùng playbook 2 lần. Lần chạy thứ hai phải báo changed=0. Một task báo changed trong mọi lần chạy là task không idempotent. Thông thường đó là task command hoặc shell, trong khi đáng ra phải dùng một module thực sự. Nếu bạn chưa quen với nội dung này, hãy bắt đầu bằng playbook Ansible đầu tiên trên một VPS đơn rồi phát triển tiếp từ đó.
Hai công cụ giao nhau ở đâu và xung đột ở đâu
Terraform có thể chạy lệnh trên server mới bằng provisioner remote-exec. Tài liệu chính thức của HashiCorp gọi provisioner là phương án cuối cùng. Có lý do chính đáng cho việc này.
Provisioner chỉ chạy khi resource được tạo. Nếu sửa script, server hiện có không thay đổi gì, vì theo góc nhìn của Terraform, resource đã khớp với code. Các bước của provisioner không bao giờ xuất hiện trong terraform plan, nên khi review bạn không thấy dấu vết nào của chúng. Nếu script lỗi, Terraform đánh dấu resource là tainted. Lần apply tiếp theo sẽ xóa rồi tạo lại một server vốn có thể vẫn hoạt động bình thường.
Lỗi cũng xảy ra vào thời điểm không phù hợp. Provider báo server đã được tạo ngay khi API trả về kết quả thành công, trong khi operating system vẫn đang boot và sshd vẫn chưa listening.
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible có một hướng sử dụng dễ gây nhầm lẫn ngược lại. Các cloud module có thể tạo server, và cách này phù hợp khi chỉ quản lý một vài máy. Nhưng bạn mất dependency graph và state file. Ansible vẫn sẽ tạo resource, nhưng nếu xóa task khỏi playbook thì resource đó vẫn chạy và vẫn phát sinh chi phí, vì không có gì ghi nhận rằng resource đó thuộc quyền quản lý của bạn.
Quy tắc rút ra là: để Terraform quản lý các object do API tạo và xóa, còn Ansible quản lý mọi thứ bên trong operating system đã boot xong.
Bàn giao, đã hoàn tất
Bàn giao là một ranh giới, không phải một phần tích hợp. Terraform hoàn tất, xuất một địa chỉ rồi dừng. Ansible bắt đầu từ địa chỉ đó.
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 in một giá trị không có dấu ngoặc kép và không có lớp bọc JSON. Đây là định dạng cần dùng trong phép thay thế shell. Với nhiều server, hãy dùng terraform output -json rồi tạo inventory từ kết quả đó, vì -raw chỉ xử lý một chuỗi, số hoặc boolean.
Nên giữ bước ping ở giữa hai công cụ. Bước này giúp phân biệt "Terraform trả về sai địa chỉ" với "playbook của tôi có lỗi". Hai vấn đề này trông giống hệt nhau khi playbook là thứ đầu tiên tác động đến máy mới.
Đọc Terraform state làm inventory cho Ansible
Nếu không muốn viết file inventory, collection cloud.terraform có thể đọc trực tiếp state.
ansible-galaxy collection install cloud.terraformTạo terraform.yml bên cạnh playbook:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.ymlTrước khi phụ thuộc vào plugin này, cần biết 2 điều. Plugin chạy terraform show trên project_path, vì vậy thư mục đó phải được initialize trước; nếu không, plugin sẽ fail. Plugin cũng không tự tạo host từ các resource server của bạn. Nó đọc các resource ansible_host và ansible_group, được khai báo trong mã Terraform bằng Ansible provider. Sẽ không có gì xuất hiện trong ansible-inventory --graph cho đến khi bạn thêm các resource đó.
File inventory được generate dạng plain text dễ debug hơn và hoạt động với mọi provider. Plugin phát huy hiệu quả khi inventory đã có nhiều hơn vài máy và việc chỉnh sửa thủ công bắt đầu gây ra lỗi typo. Đây cũng là lúc quản lý nhiều máy chủ Linux từ một máy control trở thành một quy trình làm việc thực sự thay vì chỉ là thói quen.
Bạn có thực sự cần Terraform không?
Hầu hết người đọc phần này chưa cần Terraform, ít nhất là hiện tại. Terraform đáng dùng khi việc tạo và hủy hạ tầng là một tác vụ lặp lại. Nếu bạn tạo một VPS qua control panel và dự định giữ nó trong hai năm, Terraform chỉ mô tả một việc xảy ra một lần, đồng thời thêm một state file mà bạn không được để mất.
Hãy dùng Terraform khi bạn thường xuyên dựng lại môi trường, khi staging phải khớp chính xác với production, khi nhiều người cùng thay đổi hạ tầng và bạn muốn có một plan có thể review trước khi bất kỳ thành phần nào bị xóa, hoặc khi phạm vi quản lý không chỉ là server mà còn gồm DNS record, load balancer và firewall rule nằm trong provider API.
Chỉ dùng Ansible khi server tồn tại lâu dài và số lượng ít, đồng thời câu hỏi hằng ngày là "server này đã được cấu hình đúng chưa" thay vì "server này có tồn tại không". Một playbook duy nhất để harden server mới bao quát phần việc tương tự mười phút đầu tiên trên một VPS mới, với lợi thế là nó chạy theo cùng một cách trên server tiếp theo.
Thứ tự học cũng xuất phát từ đó. Ansible bắt đầu mang lại hiệu quả từ server đầu tiên bạn sở hữu. Terraform bắt đầu mang lại hiệu quả từ môi trường thứ ba bạn dựng lại.
Điểm nào dễ hỏng khi bàn giao
Máy chủ chưa sẵn sàng. Terraform chạy thành công, nhưng Ansible lỗi ngay lập tức.
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 trả về địa chỉ trước khi sshd bắt đầu listening. Hãy chờ cổng thay vì thêm thời gian sleep cố định. Ansible có ansible.builtin.wait_for_connection dành đúng cho việc này; hãy chạy nó ở task đầu tiên của play.
Host key đã thay đổi. Bạn đã xóa rồi tạo lại máy chủ, và máy chủ mới phản hồi tại cùng địa chỉ nhưng dùng key mới.
Host key verification failed.Xóa entry cũ bằng ssh-keygen -R 203.0.113.10. Việc này xảy ra liên tục khi Terraform thực hiện rebuild, nên nên hạn chế rebuild trên các máy đang lưu dữ liệu.
Sudo lỗi. fatal: [web1]: FAILED! => {"msg": "Missing sudo password"} có nghĩa là become: true cần mật khẩu trên host đó. Hãy cấu hình sudo không cần mật khẩu cho deploy user, hoặc truyền --ask-become-pass.
Terraform muốn destroy thứ bạn không hề thay đổi. Plan hiển thị các thay đổi bạn chưa từng viết trong code. Điều đó có nghĩa là hạ tầng thực tế đã drift khỏi code, thường do ai đó thay đổi một setting trong web panel của provider. Chạy terraform plan -refresh-only để chỉ xem riêng phần khác biệt đó, rồi quyết định code hay resource đang chạy mới là phần sai. Không bao giờ apply một plan có thao tác destroy mà bạn không thể giải thích từng dòng.
Ansible báo changed ở mọi lần chạy. Một task shell không có guard creates hoặc when sẽ luôn chạy. Đây không chỉ là vấn đề hiển thị, vì bạn không còn có thể dùng changed=0 làm tín hiệu cho biết máy chủ đã ở đúng trạng thái mong muốn.
FAQ
Terraform có thể thay thế Ansible không?
Không thể thay thế việc cấu hình bên trong máy chủ. Terraform có thể gọi script bằng provisioner remote-exec, nhưng các script đó chỉ chạy khi tạo resource, không bao giờ xuất hiện trong terraform plan, và sẽ đánh dấu resource là bị lỗi khi chúng thất bại. Khi đó, lần apply tiếp theo sẽ lên lịch xóa rồi tạo lại resource. Terraform không có module tương đương để kiểm tra nginx đã được cài chưa và không làm gì nếu đã cài. Dùng Terraform để tạo máy chủ, rồi chuyển sang Ansible.
Ansible có thể thay thế Terraform không?
Có, nếu bạn chỉ có một số ít máy chủ chạy lâu dài. Ansible có các cloud module để tạo máy chủ. Nếu bạn đặt mua 2 VPS rồi giữ chúng, như vậy là đủ. Nhưng bạn sẽ mất state file và dependency graph. Nếu xóa một task khỏi playbook, resource vẫn tiếp tục chạy và tiếp tục phát sinh chi phí, vì Ansible không ghi nhận rằng nó đã tạo resource đó. Terraform sẽ lập kế hoạch xóa resource.
Nên học công cụ nào trước?
Học Ansible trước nếu hiện bạn đang quản lý máy chủ. Công cụ này mang lại hiệu quả ngay từ máy đầu tiên, chỉ cần SSH, và kỹ năng này áp dụng được cho cả máy chủ bạn tự đặt mua. Terraform phát huy hiệu quả về sau, khi bạn thường xuyên dựng lại môi trường hoặc quản lý các resource của provider ngoài máy chủ, chẳng hạn như DNS record và firewall rule.
Làm cách nào để truyền IP của máy chủ mới từ Terraform sang Ansible?
Khai báo output trong mã Terraform, rồi đọc giá trị đó sau khi apply. terraform output -raw web_ip in ra giá trị thuần để dùng trong shell substitution, còn terraform output -json trả về tất cả output cùng lúc khi có nhiều host. Ghi giá trị đó vào inventory file, hoặc cài collection cloud.terraform rồi trỏ ansible-inventory -i terraform.yml --graph đến thư mục project.
Vì sao playbook của tôi fail ngay sau khi Terraform hoàn tất?
Provider báo máy chủ đã được tạo ngay khi API của provider trả về trạng thái đó, trong khi operating system vẫn đang boot. Vì vậy, SSH bị từ chối trong vài giây đầu. Lỗi là UNREACHABLE! với Connection refused. Đặt ansible.builtin.wait_for_connection làm task đầu tiên trong play thay vì đoán một khoảng thời gian sleep, vì thời gian boot thay đổi tùy image và plan.