Ansible: Bỏ qua host unreachable đúng cách
Ansible phân biệt task failed và host unreachable. Dùng ignore_unreachable, serial, max_fail_percentage để tiếp tục play và biết host nào bị bỏ qua.
Host không thể kết nối không đồng nghĩa với task thất bại
Để bỏ qua các host không thể kết nối trong Ansible, bạn đặt ignore_unreachable: true, và tùy chọn này sẽ có hiệu lực. Điều quan trọng là biết khi nào nên dùng, vì Ansible xử lý hai vấn đề khác nhau theo hai cách khác nhau. Một task đã chạy trên host và trả về lỗi là failure. Một host mà Ansible hoàn toàn không thể kết nối là unreachable. ignore_errors chỉ xử lý trường hợp đầu tiên. ignore_unreachable chỉ xử lý trường hợp thứ hai.
Dưới đây là điểm khác biệt trong phần tổng kết play.
PLAY RECAP *********************************************************************
web1 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0Ansible đã kết nối đến web1 và chạy bảy task. web2 hiển thị unreachable=1 và failed=0, nghĩa là không có task nào chạy trên host đó. Ansible không thể thiết lập kết nối, nên đã loại host khỏi play và tiếp tục với các host còn lại. Nếu play đó đang cài đặt bản cập nhật bảo mật, một trong các server của bạn sẽ không có bản cập nhật này.
Nguyên nhân khiến host không thể kết nối
Không thể kết nối nghĩa là kết nối đã thất bại trước khi bất kỳ module nào truy cập đến host. Không có output của module để đọc, chỉ có lỗi kết nối. Lỗi xuất hiện ở task đầu tiên truy cập vào máy.
fatal: [web2]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.20 port 22: Connection refused", "unreachable": true}Trường msg chứa nguyên nhân thực sự. Đây là các lỗi bạn sẽ gặp:
Connection refused: kết nối TCP bị từ chối, nghĩa là không có tiến trình nào đang listen trên port đó. sshd đã dừng, hoặc SSH đã chuyển sang port khác nhưng inventory vẫn khai báo 22.Connection timed out: không có phản hồi nào. firewall đang drop packet, hoặc server đã tắt. Mỗi lần thử phải chờ hết connection timeout, mặc định là 10 giây.Host key verification failed.: key trong~/.ssh/known_hostskhông khớp với key mà server cung cấp. VPS được rebuild vẫn giữ địa chỉ IP nhưng nhận host key mới. Vì vậy lỗi này là bình thường sau khi cài lại, nhưng nghiêm trọng nếu xuất hiện vào thời điểm khác.Permission denied (publickey): SSH đã phản hồi nhưng từ chối key của bạn. Port vẫn hoạt động, nên đây là lỗi xác thực, thường doansible_usersai hoặc key chưa được load.Timeout (12s) waiting for privilege escalation prompt: kết nối đã thành công nhưngbecomechưa thành công. sudo đang chờ password nhưng không nhận được.
Thiếu Python interpreter là nguyên nhân nhiều người nghĩ sẽ có trong danh sách trên, nhưng không thuộc nhóm này. SSH đã kết nối thành công, nên host có thể truy cập được. Sau đó module không có môi trường để chạy:
fatal: [db1]: FAILED! => {"changed": false, "module_stdout": "/bin/sh: 1: /usr/bin/python3: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter", "rc": 127}Dòng đó cho biết FAILED! và phần recap tính lỗi này vào failed, nên ignore_unreachable sẽ không bao giờ truy cập đến host đó. Đặt ansible_python_interpreter cho host này hoặc cài python3 trên host.
Cách bỏ qua các host không thể kết nối trong một play
Ở cấp task, keyword này nằm cùng cấp với module:
- name: Read the package list, and do not stop if the host is down
ansible.builtin.command: dpkg -l
register: packages
changed_when: false
ignore_unreachable: trueỞ cấp play, nó đặt giá trị mặc định cho mọi task trong play. Một task riêng lẻ có thể đặt lại giá trị này:
- name: Opportunistic fleet maintenance
hosts: all
ignore_unreachable: true
tasks:
- name: This runs, cannot connect, and the play carries on
ansible.builtin.ping:
- name: This one still ends the play for a host that is down
ansible.builtin.ping:
ignore_unreachable: falseCần hiểu rõ thay đổi bên dưới. Khi đặt ignore_unreachable, host không còn bị loại khỏi play. Vì vậy, mọi task tiếp theo vẫn cố kết nối lại và tiếp tục thất bại theo cùng một cách. Mỗi lần thử đều chờ hết thời gian timeout kết nối, mặc định là 10 giây nếu bạn không thay đổi timeout trong ansible.cfg. Một play có 20 task chạy trên một server đã ngừng hoạt động sẽ làm thời gian chạy tăng khoảng 200 giây và thêm 20 dòng lỗi vào log.
Vì vậy, hãy kiểm tra một lần rồi loại host đó một cách rõ ràng:
- name: Opportunistic fleet maintenance
hosts: all
gather_facts: false
tasks:
- name: Check that the host answers before doing any work
ansible.builtin.ping:
register: reachable
ignore_unreachable: true
- name: End the play for this host if it never answered
ansible.builtin.meta: end_host
when: reachable.unreachable | default(false)
- name: Gather facts now that the connection is known good
ansible.builtin.setup:
- name: Refresh the package index
ansible.builtin.apt:
update_cache: true
become: trueNhư vậy, mỗi host không hoạt động chỉ có một lần thử kết nối thay vì một lần cho mỗi task. end_host, được thêm vào Ansible 2.8, kết thúc play trên host hiện tại mà không đánh dấu host đó là failed. Key unreachable chỉ xuất hiện trong registered result khi kết nối thất bại, nên default(false) giữ cho điều kiện hợp lệ trên mọi host đã phản hồi. Fact gathering bị tắt ở cấp play vì nếu không, task Gathering Facts ngầm định sẽ là task chạm vào kết nối bị hỏng; trong trường hợp này, bạn muốn chính lệnh ping của mình thực hiện việc đó.
ignore_unreachable là keyword dùng được ở cả cấp play và task. Hãy đặt nó trong playbook để người đọc dễ thấy, thay vì đặt bên trong role, vì nó quyết định run có thể bỏ qua những host nào. Phân tách giữa playbook và role giải thích layer nào nên quản lý một thiết lập như vậy.
Vì sao ignore_errors không phải công cụ phù hợp ở đây
Tài liệu Ansible nêu rõ giới hạn này. ignore_errors "chỉ hoạt động khi task có thể chạy và trả về giá trị 'failed'. Nó không khiến Ansible bỏ qua lỗi biến chưa được định nghĩa, lỗi kết nối, lỗi thực thi (ví dụ thiếu package) hoặc lỗi cú pháp."
Lỗi kết nối không bao giờ trở thành kết quả task có failed: true. Nó được ghi nhận dưới dạng một cờ riêng, và Ansible xử lý cờ đó trước: host được đưa vào danh sách unreachable rồi bị loại khỏi play. Đặt ignore_errors: true cho cả 12 task trong một play cũng không thay đổi điều này: nếu cổng SSH của host bị đóng, play vẫn dừng ở task đầu tiên. Đây là nhầm lẫn phổ biến nhất trong phần này. Bạn nên grep các playbook cũ để tìm trường hợp này, đặc biệt là những playbook được viết khi học cách viết playbook đầu tiên cho một VPS.
Gỡ lỗi trước khi bỏ qua
Việc suppression trở thành vĩnh viễn khiến fleet drift, vì host không ai truy cập được cũng là host không ai patch. Trước tiên, hãy thực hiện theo thứ tự này. Mọi command ở đây chỉ đọc dữ liệu.
ansible web2 -i inventory.ini -m ansible.builtin.ping -ochạy một module trên một host và in ra một dòng.- Thêm
-vvvvvào command đó. Ansible sẽ in ra toàn bộ ssh command mà nó tạo, bao gồm user đích, port, private key và các option được truyền vào. - Tự chạy ssh command đó với
-v. Nếu ssh thông thường không thể truy cập, lỗi nằm bên dưới Ansible và không có keyword nào trong playbook sửa được. - Đọc chuỗi
msgvà đối chiếu với danh sách ở trên.Connection refusedvàConnection timed outtrỏ đến 2 vị trí khác nhau: một ở SSH service và một trên network path. - Với
Host key verification failed., kiểm tra dữ liệu bạn đã lưu bằngssh-keygen -F web2.example.com. Nếu server đã được rebuild, xóa entry cũ bằngssh-keygen -R web2.example.comrồi chấp nhận key mới sau khi đối chiếu với provider console. Đặthost_key_checking = Falsetrongansible.cfgsẽ xóa lỗi này, nhưng đồng thời cũng bỏ luôn kiểm tra giúp phát hiện một máy khác đang trả lời trên địa chỉ đó. - Với
Permission denied (publickey), xác nhận Ansible cho rằng nó phải dùng gì.ansible-inventory -i inventory.ini --host web2in ra các biến đang có hiệu lực, bao gồmansible_uservàansible_port. - Nếu SSH hoạt động nhưng module không chạy, kiểm tra interpreter bằng
ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. Modulerawchạy command thông qua shell và không cần python trên target.
Chỉ sau các bước đó, việc bỏ qua host mới là một quyết định thay vì một thói quen.
Bản recap tính unreachable riêng, còn CI thường bỏ sót trạng thái này
ansible-playbook thoát với mã 0 khi thành công, mã 2 khi có ít nhất một host failed và mã 4 khi có ít nhất một host unreachable. Hai giá trị này là bit flag trong source, nên một lần chạy có cả host failed và host unreachable sẽ thoát với mã 6. Lệnh ansible trả về các mã tương tự. Các giá trị này đã được đối chiếu với source của ansible-core vào tháng 8 năm 2026.
Bây giờ đặt ignore_unreachable: true rồi chạy play gồm 7 task trên cùng host không phản hồi:
PLAY RECAP *********************************************************************
web1 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=7web2 báo unreachable=0 và 7 task ok, còn lần chạy thoát với mã 0. Khi đặt keyword này, Ansible tăng các counter ok và ignored cho host đó thay vì counter mà nó gọi là dark. Đây là counter dùng để điền vào cột unreachable. Các dòng màu đỏ UNREACHABLE! vẫn được in ra, nên log vẫn phản ánh đúng sự việc, nhưng recap và exit code thì không.
Một CI job chạy playbook rồi chỉ kiểm tra $? sẽ coi lần chạy đó là thành công. Phần summary không cho biết máy chưa từng được truy cập. Hãy tách kiểm tra khả năng kết nối thành một step riêng, chạy trước play:
ansible all -i inventory.ini -m ansible.builtin.ping -oLệnh này in một dòng cho mỗi host và thoát với mã 4 nếu có bất kỳ host nào unreachable. Nhờ đó pipeline có điều kiện để fail và log có tên các host gặp lỗi. ping cần một Python interpreter đang hoạt động trên target, nên kiểm tra được nhiều hơn chỉ kết nối. Đây thường là điều bạn cần. Sau đó chạy playbook với ignore_unreachable để các host đang hoạt động vẫn được áp dụng thay đổi.
any_errors_fatal và max_fail_percentage trong một batch
Hai keyword của play này quyết định điều gì xảy ra sau khi một phần fleet gặp lỗi. Chúng xử lý các host không thể truy cập theo cách khác nhau.
any_errors_fatal: true có phản ứng với host không thể truy cập. Ansible hoàn thành task hiện tại trên phần còn lại của batch, rồi dừng play cho mọi host trong batch đó. Dùng tùy chọn này khi một lần chạy chỉ có ý nghĩa nếu tất cả cùng thành công hoặc cùng thất bại, chẳng hạn như thay đổi schema cần phối hợp.
max_fail_percentage: 30 không phản ứng với host không thể truy cập. Ansible lấy số host failed chia cho kích thước của batch. Các host không thể truy cập được giữ trong một danh sách riêng, nên không làm thay đổi con số đó. Mười host, trong đó có bốn host không thể truy cập, vẫn tiếp tục chạy với max_fail_percentage: 10. Nhưng nếu hai host fail một task thì play sẽ dừng. Tài liệu còn nêu một điểm dễ nhầm: "Tỷ lệ được đặt phải bị vượt qua, không được bằng." Với serial: 4, để dừng sau khi 2 trong 4 host fail, phải ghi 49, không phải 50.
Có một trường hợp host không thể truy cập tự làm dừng lần chạy. Nếu mọi host trong batch đều failed hoặc không thể truy cập, Ansible không còn host nào để xử lý và kết thúc play bằng NO MORE HOSTS LEFT.
triển khai thay đổi lần lượt trên toàn bộ fleet
- name: Rolling nginx config update
hosts: webservers
serial: 2
max_fail_percentage: 25
tasks:
- name: Deploy the site config
ansible.builtin.template:
src: site.conf.j2
dest: /etc/nginx/conf.d/site.conf
owner: root
mode: "0644"
become: true
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
become: trueserial: 2 chạy toàn bộ play trên hai host, chờ hoàn tất rồi mới chạy trên hai host tiếp theo. serial: "25%" tự điều chỉnh theo kích thước của group. Một danh sách, serial: [1, 5, 10], là cấu hình canary: chạy trên một host trước, sau đó năm host, rồi mười host; các host còn lại sẽ chạy theo batch có kích thước bằng batch gần nhất. max_fail_percentage được tính theo từng batch, nên hai tùy chọn này phối hợp với nhau. Nếu máy đầu tiên gặp lỗi, lượt chạy sẽ dừng trước khi lỗi lan đến bốn mươi máy. Nhờ vậy, quản lý một fleet máy chủ Linux từ một máy control bằng một lệnh duy nhất vẫn an toàn.
Khi nào nên bỏ qua host không thể truy cập và khi nào không nên
Hãy bỏ qua chúng đối với các tác vụ thực hiện khi có thể. Một lần thu thập fact hoặc kiểm tra drift theo giờ không mất gì khi bỏ qua một host đang down, vì lần chạy tiếp theo sẽ xử lý host đó. ignore_unreachable: true ở cấp play là lựa chọn phù hợp trong trường hợp này, kết hợp với bước ping để các tên bị bỏ qua được ghi vào nơi mà người vận hành sẽ đọc.
Không bao giờ bỏ qua chúng trong lần chạy vá bảo mật. Giá trị của lần chạy đó nằm ở việc bảo đảm mọi host đều đã được cài bản sửa lỗi. Việc ẩn trạng thái không thể truy cập sẽ biến “một server vẫn còn dễ bị tấn công” thành một bản tổng kết xanh hoàn toàn. Host không thể truy cập trong hai tuần là host có khả năng tụt hậu nhiều nhất. Hãy để lần chạy đó kết thúc với mã 4 và để người vận hành kiểm tra.
Trong cả hai trường hợp, có một quy tắc cần tuân theo: chỉ ngăn việc dừng, không được ẩn bản ghi. Nếu một host bị bỏ qua, phải có nơi ghi rõ điều đó, trong bản tổng kết, log CI hoặc cảnh báo monitoring. Ansible chỉ biết một host tồn tại trong vài giây mà một play chạy trên host đó, nên không phù hợp để phát hiện một server đã down từ thứ Ba. Công việc đó thuộc về monitoring, còn một Ansible playbook cài đặt Zabbix có thể cung cấp góc nhìn toàn bộ fleet chỉ sau một buổi chiều.
FAQ
Sự khác nhau giữa ignore_errors và ignore_unreachable trong Ansible là gì?
ignore_errors: true áp dụng cho task đã chạy trên host và trả về lỗi, chẳng hạn command kết thúc với mã khác 0. ignore_unreachable: true áp dụng cho host mà Ansible không thể kết nối, nên chưa có module nào chạy. Hai tùy chọn này đọc các trường khác nhau trong kết quả của task và không thay thế cho nhau. Tài liệu Ansible nêu rằng ignore_errors “không khiến Ansible bỏ qua lỗi biến chưa được định nghĩa, lỗi kết nối, lỗi thực thi (ví dụ: thiếu package) hoặc lỗi cú pháp”, và một cổng SSH bị đóng là lỗi kết nối.
ignore_unreachable có ẩn host khỏi phần tổng kết play không?
Về thực tế là có. Khi đặt keyword này, Ansible không còn tính host đó dưới unreachable mà tính là ok và ignored một lần cho mỗi task, sau đó tiến trình kết thúc với mã 0. Các dòng fatal: [host]: UNREACHABLE! vẫn được in ra, nên log vẫn chính xác dù phần tổng kết và mã thoát không phản ánh lỗi. Hãy theo dõi cột ignored, hoặc chạy ansible all -m ansible.builtin.ping -o ở một bước riêng để host không thể kết nối vẫn tạo ra mã thoát khác 0 ở một nơi nào đó.
ansible-playbook trả về mã thoát nào khi một host không thể kết nối?
Nó trả về 4. Một lần chạy có ít nhất một host failed sẽ trả về 2, và hai giá trị này là các bit flag, nên lần chạy có cả host failed và host không thể kết nối sẽ trả về 6. Lần chạy không có lỗi trả về 0. Các mã này đã được kiểm tra với mã nguồn ansible-core vào tháng 8 năm 2026. Đặt ignore_unreachable: true sẽ loại bỏ giá trị 4, vì vậy pipeline chỉ kiểm tra mã thoát sẽ không phát hiện được một máy bị bỏ qua.
Làm cách nào để bỏ qua phần còn lại của play đối với host không phản hồi?
Đặt task đầu tiên là ansible.builtin.ping với ignore_unreachable: true và register: reachable, sau đó thêm ansible.builtin.meta: end_host với điều kiện when: reachable.unreachable | default(false). end_host kết thúc play đối với host đó mà không đánh dấu host là failed. Đặt gather_facts: false trên play để lệnh ping của bạn là task phát hiện kết nối bị hỏng. Nếu không dùng mẫu này, host đã ngừng hoạt động vẫn nằm trong play và mỗi task tiếp theo lại phải chờ hết connection timeout.
Có nên bỏ qua các host không thể kết nối trong lần chạy vá bảo mật không?
Không. Một lần chạy vá bảo mật có giá trị vì nó bảo đảm mọi host đều đã được cập nhật. Việc bỏ qua các host không thể kết nối sẽ thay thế sự bảo đảm đó bằng một phần tổng kết màu xanh. Hãy để lần chạy kết thúc với mã 4, đọc tên các host không phản hồi và xử lý chúng. Chỉ nên dùng cơ chế bỏ qua trong các lần chạy cơ hội lặp lại, khi lần chạy tiếp theo sẽ xử lý những gì bị bỏ sót.