SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

Ansible 如何忽略無法連線的主機?

Ansible 將無法連線與工作失敗分開處理。了解 ignore_unreachable、serial 與 max_fail_percentage 的差異,並掌握 play recap 中被略過的主機。

無法連線的主機不是失敗的工作

若要在 Ansible 中忽略無法連線的主機,請設定 ignore_unreachable: true;此選項即可生效。重點在於判斷何時使用,因為 Ansible 會以不同方式處理兩種問題。在主機上執行並回傳錯誤的工作屬於失敗。Ansible 完全無法連線的主機則屬於無法連線ignore_errors只適用於第一種情況。ignore_unreachable只適用於第二種情況。

以下是 play recap 中的差異。

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=0

Ansible 已連線至 web1,並執行 7 個工作。web2顯示 unreachable=1failed=0,表示該主機完全沒有執行任何工作。Ansible 始終未能建立連線,因此將該主機從 play 中移除,並繼續處理其餘主機。若該 play 用於安裝安全性更新,其中一台伺服器就不會安裝該更新。

主機無法連線的原因

「無法連線」表示連線在任何模組抵達主機前就已失敗。沒有模組輸出可供查看,只有連線錯誤,而且錯誤會出現在第一個接觸該機器的工作。

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}

msg 欄位包含實際原因。以下是常見情況:

  • Connection refused:TCP 連線遭拒,因此該埠沒有程式正在監聽。sshd 已停止,或 SSH 已移至其他埠,但 inventory 仍設定為 22。
  • Connection timed out:完全沒有收到回應。防火牆可能丟棄封包,或伺服器已關機。每次嘗試都會耗用完整的連線逾時時間,預設為 10 秒。
  • Host key verification failed.~/.ssh/known_hosts 中的金鑰與伺服器提供的金鑰不符。重新建立的 VPS 會保留原本的 IP 位址,但取得新的主機金鑰。因此,重灌後出現此錯誤是預期情況;在其他時候則表示有嚴重問題。
  • Permission denied (publickey):SSH 已回應,但拒絕了您的金鑰。連接埠沒有問題,因此這是驗證問題,通常是 ansible_user 設定錯誤,或金鑰尚未載入。
  • Timeout (12s) waiting for privilege escalation prompt:連線成功,但 become 失敗。sudo 正在等待永遠不會送達的密碼。

清單中常被認為會出現的原因是缺少 Python 直譯器,但它不屬於這類問題。SSH 已建立連線,因此主機可以連線。接著模組沒有可供執行的環境:

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}

該行表示 FAILED!,而摘要會將其計入 failed,因此 ignore_unreachable 永遠不會接觸該主機。為該主機設定 ansible_python_interpreter,或在該主機上安裝 python3。

如何在 play 中忽略無法連線的主機

在 task 層級,關鍵字放在模組旁邊:

- 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

在 play 層級,它會成為該 play 中所有 task 的預設值,單一 task 也可以將它設回原值:

- 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: false

了解其底層行為很重要。設定 ignore_unreachable 後,主機不會再從 play 中移除,因此後續每個 task 都會再次嘗試連線,並以相同方式再次失敗。每次嘗試都會等到連線逾時;除非在 ansible.cfg 中修改 timeout,否則逾時為 10 秒。對一台無回應伺服器執行包含 20 個 task 的 play,會讓執行時間增加約 200 秒,並在日誌中產生 20 行錯誤訊息。

因此,先檢查一次,再乾淨地停止處理該主機:

- 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: true

對每台無回應的主機,這樣只會嘗試連線一次,而不是每個 task 都嘗試一次。end_host 在 Ansible 2.8 中加入,會結束目前主機的 play,但不會將其標記為失敗。只有在連線失敗時,註冊結果才會包含 unreachable 鍵,因此 default(false) 能讓條件式對每台成功回應的主機都保持有效。在 play 層級關閉 facts 收集,是因為隱含的 Gathering Facts task 否則會成為第一個遇到連線故障的 task;你應該讓自訂的 ping task 負責這項檢查。

ignore_unreachable 同時是 play 關鍵字和 task 關鍵字。請將它保留在 playbook 中,讓讀者能直接看見,而不要放在 role 內,因為它決定一次執行可以略過哪些主機。playbook 與 role 的區分說明了這類設定應由哪一層負責。

此處不應使用 ignore_errors

Ansible 文件明確說明了這項限制。ignore_errors「只有在工作可執行,且回傳 'failed' 值時才有效。它不會讓 Ansible 忽略未定義變數錯誤、連線失敗、執行問題(例如缺少套件)或語法錯誤。」

連線失敗不會變成包含 failed: true 的工作結果。它會以獨立旗標回報,而 Ansible 會先處理該旗標:主機會列入 unreachable 清單,並從 play 中移除。在 play 的 12 個工作上全部加入 ignore_errors: true,SSH 埠關閉的主機仍會在第一個工作處停止。這是此領域最常見的混淆,建議特別檢查較早撰寫的 playbook,例如在 學習為 VPS 撰寫第一個 playbook 時建立的檔案。

先排除問題,再停用檢查

永久停用檢查會讓整個伺服器群組逐漸偏離標準,因為無人能連線的主機,也無人會替它套用修補程式。請先依照以下順序處理。這裡的每個指令都只會讀取資料。

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o 會在一台主機上執行一個模組,並輸出一行結果。
  2. 在相同指令中加入 -vvvv。Ansible 會輸出它建立的完整 ssh 指令,包括目標使用者、連接埠、私密金鑰及傳入的選項。
  3. 使用 -v 自行執行該 ssh 指令。如果一般 ssh 無法連線,問題就在 Ansible 之下的層級,任何 playbook 關鍵字都無法修正。
  4. 讀取 msg 字串,並與上方的清單比對。Connection refusedConnection timed out 指向兩個不同位置,一個是 SSH 服務,另一個是網路路徑。
  5. 如果是 Host key verification failed.,請查看使用 ssh-keygen -F web2.example.com 儲存的內容。如果伺服器已重建,請使用 ssh-keygen -R web2.example.com 移除舊項目,並在與供應商主控台核對新金鑰後接受它。在 ansible.cfg 中設定 host_key_checking = False 可清除錯誤,但也會移除用來告知目前由另一台機器回應該位址的檢查。
  6. 如果是 Permission denied (publickey),請確認 Ansible 預期使用的設定。ansible-inventory -i inventory.ini --host web2 會輸出目前生效的變數,包括 ansible_useransible_port
  7. 如果 SSH 可正常運作,但模組無法執行,請使用 ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none' 檢查直譯器。raw 模組會透過 shell 執行指令,目標主機不需要安裝 python。

完成上述處理後,忽略該主機才是經過判斷的決策,而不是慣性做法。

recap 會將無法連線的主機分開計算,而 CI 通常會漏掉這種情況

ansible-playbook 成功時回傳 0,至少一台主機失敗時回傳 2,至少一台主機無法連線時回傳 4。這兩個值在原始碼中是位元旗標,因此同時有失敗主機與無法連線主機的執行結果會回傳 6。ansible 命令也會回傳相同的代碼。這些行為已在 2026 年 8 月對照 ansible-core 原始碼確認。

現在設定 ignore_unreachable: true,並對相同的無法連線主機執行相同的 7 個工作 play:

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=7

web2 會回報 unreachable=0,且 7 個工作 ok,但執行結果會回傳 0。設定這個關鍵字後,Ansible 會針對該主機遞增 okignored 計數器,而不是遞增它稱為 dark 的計數器;後者會填入 unreachable 欄。紅色的 UNREACHABLE! 行仍會輸出,因此日誌內容是正確的,但 recap 與退出代碼並未反映實際情況。

如果 CI 工作執行 playbook 後只檢查 $?,就會將這次執行判定為成功,而摘要中不會顯示某台機器從未被連線。請在 play 前將連線能力檢查設為獨立步驟:

ansible all -i inventory.ini -m ansible.builtin.ping -o

這會為每台主機輸出一行;只要有任何主機無法連線,就會回傳 4。如此一來,pipeline 有可依據的失敗條件,日誌中也會列出主機名稱。ping 需要目標主機上有可用的 Python interpreter,因此驗證的不只是連線本身;通常這正是所需的檢查。接著使用 ignore_unreachable 執行 playbook,讓仍可連線的主機繼續套用變更。

批次中的 any_errors_fatal 與 max_fail_percentage

這兩個 play 關鍵字會決定 fleet 的部分主機發生問題後要如何處理,而且兩者對無法連線的主機採用不同處理方式。

any_errors_fatal: true 會對無法連線的主機做出反應。Ansible 會先在批次中的其餘主機完成目前的 task,然後停止該批次所有主機的 play。若執行作業必須全部成功或全部失敗,例如協調進行的 schema 變更,請使用此設定。

max_fail_percentage: 30 不會對無法連線的主機做出反應。這項檢查會以批次大小為分母,計算 failed 主機數量;無法連線的主機會列在另一份清單中,因此不會影響該數值。即使 10 台主機中有 4 台無法連線,使用 max_fail_percentage: 10 時仍會繼續執行;但如果有 2 台主機執行 task 失敗,play 就會停止。文件另外指出一個容易誤解的細節:「設定的百分比必須超過,不能等於。」使用 serial: 4 時,若要在 4 台主機中有 2 台失敗後停止,必須設定為 49,而不是 50。

有一種情況會讓無法連線的主機自行停止執行作業。如果批次中的每台主機都已失敗或無法連線,Ansible 就沒有可繼續處理的主機,並以 NO MORE HOSTS LEFT 結束 play。

serial:在整個伺服器群組中逐步套用變更

- 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: true

serial: 2 會先對兩台主機執行完整 play,完成後再開始處理下一批兩台主機。serial: "25%" 會依群組大小調整。清單 serial: [1, 5, 10] 採用金絲雀部署方式:先處理一台主機,再處理五台,接著處理十台;剩餘主機則以最後一批的大小分批執行。max_fail_percentage 以每一批為單位計算,因此兩者會搭配運作。若第一台主機發生故障,執行程序會在影響第 40 台主機前停止。這使得只需執行一個命令,就能安全地從單一控制主機管理整個 Linux 伺服器群組

何時可以忽略無法連線的主機,何時不應忽略

對於機會性工作,可以忽略無法連線的主機。事實收集或每小時執行的 drift check 若略過已關機的主機,不會遺漏任何內容,因為下一次執行時會再次處理。此時應使用 play 層級的 ignore_unreachable: true,並搭配 ping 步驟,讓略過的主機名稱出現在人員會查看的位置。

安全性修補工作絕不應忽略無法連線的主機。這類工作的價值,在於確保每台主機都已套用修補程式;若隱藏無法連線狀態,「仍有一台伺服器存在漏洞」就會變成看似正常的結果摘要。已連續兩週無法連線的主機,最可能嚴重落後。讓該次執行以 4 結束,並交由人員處理。

兩種情況都適用同一項規則:抑制停止執行,但絕不抑制記錄。如果略過某台主機,摘要、CI 日誌或監控警示中必須明確說明。Ansible 只有在 play 對主機執行的幾秒內,才知道該主機存在。因此,不應依賴 Ansible 來得知某台伺服器自星期二起就一直停止服務。這項工作應交由監控系統處理,而 安裝 Zabbix 的 Ansible playbook 可在一個下午內建立全機隊的可見性。

FAQ

ignore_errors 和 ignore_unreachable 在 Ansible 中有何不同?

ignore_errors: true 適用於已在主機上執行但回傳失敗的工作,例如命令以非零狀態結束。ignore_unreachable: true 適用於 Ansible 無法連線的主機,因為沒有任何模組實際執行。兩者讀取工作結果中的不同欄位,彼此無法涵蓋對方的情況。Ansible 文件指出,ignore_errors「不會讓 Ansible 忽略未定義變數錯誤、連線失敗、執行問題(例如缺少套件)或語法錯誤」,而關閉的 SSH 埠就是連線失敗。

ignore_unreachable 會讓主機從 play recap 中消失嗎?

實際上會。設定此關鍵字後,Ansible 不再將該主機計入 unreachable,而是每個工作各計入一次 okignored,且執行結果會以 0 結束。fatal: [host]: UNREACHABLE! 行仍會輸出,因此日誌仍然正確,但 recap 與結束代碼並不能反映該主機的狀態。請查看 ignored 欄位,或將 ansible all -m ansible.builtin.ping -o 作為獨立步驟執行,讓無法連線的主機仍能在某個階段產生非零結束代碼。

ansible-playbook 在主機無法連線時會回傳什麼結束代碼?

它會回傳 4。只要至少有一台主機失敗,執行結果就會回傳 2;這兩個值是位元旗標,因此同時有失敗主機與無法連線主機時,會回傳 6。成功執行時會回傳 0。這些代碼已於 2026 年 8 月對照 ansible-core 原始碼確認。設定 ignore_unreachable: true 會移除 4,因此只測試結束代碼的 pipeline 無法發現被略過的主機。

如何略過未回應主機的 play 其餘部分?

將第一個工作設為 ansible.builtin.ping,並搭配 ignore_unreachable: trueregister: reachable;接著在條件 when: reachable.unreachable | default(false) 下執行 ansible.builtin.meta: end_hostend_host 會結束該主機的 play,但不會將它標記為失敗。在 play 上設定 gather_facts: false,讓 ping 工作成為觸發連線失敗的位置。若未採用此模式,無回應的主機仍會留在 play 中,後續每個工作都會再次等待連線逾時。

執行安全性修補時,應該忽略無法連線的主機嗎?

不應該。執行修補的價值在於確認每台主機都已完成更新;忽略無法連線的主機,會把這項保證替換成綠色的 recap。讓執行結果以 4 結束,找出未回應的主機名稱並修復問題。抑制錯誤只適合重複執行的機會性工作,因為下一次執行會處理前一次遺漏的主機。

#ansible#playbooks#error-handling#inventory#automation