Ansible の「until」ループを修正する:「non-zero return code」リトライエラーの解決策

intermediate🔧 Ansible2026-07-09| Ansible 2.9以上、Linux (Ubuntu, CentOS, RHEL)、および AWS や Azure などのクラウド環境。

Error Message

FAILED! => {"attempts": 5, "changed": false, "msg": "non-zero return code"}
#ansible#devops#automation#troubleshooting#linux

午前2時のパイプライン停止深夜、本番環境のマイクロサービスに重要なホットフィックスをデプロイしているとします。Ansible プレイブックはスムーズに開始されましたが、ヘルスチェック中に突然停止してしまいました。60秒間の沈黙の後、ターミナルに強烈な赤いエラーブロックが表示されます:

FAILED! => {"attempts": 5, "changed": false, "msg": "non-zero return code"}

このエラーは、until キーワードを使用してリソースをポーリングしている際に、Ansible が retries(リトライ)制限に達したときに発生します。基本的には、ループ内のコマンドが失敗し続けたか、タイマーが切れる前に成功条件が満たされなかったことを意味します。

「until」条件が停滞する理由Ansible は、ループの各反復においてモジュールの成功とカスタムの until ロジックの両方を評価します。通常、以下の3つのいずれかが原因です:

  • 成功までのタイムラグ: サービスの初期化に90秒かかるのに、ループの待ち時間が50秒しかない場合。タスクが壊れているのではなく、単に待ちきれていない状態です。- 隠れた空白文字: stdout で「ready」という単語をチェックしているが、システムが実際には「ready\n」を返している場合。その改行文字1つで文字列比較は失敗します。- シェルの罠: shell モジュールを使用する場合、curl からの 404 エラーなどの 0 以外の終了コードが返されると、たとえ 200 OK を待つ意図があっても、Ansible はその試行を失敗と見なします。## 解決策:よりスマートなポーリングロジック### 1. 一時的なコマンド失敗の処理起動中の API を curl でチェックする場合、ポートが開放されるまでコマンド自体がエラーコードを返します。コマンドが失敗してもループを継続させる必要があります。until は本来失敗時にリトライしますが、生の shell コマンドよりも uri モジュールを使用する方がはるかにクリーンです。
- name: Spring Boot API が UP を報告するまで待機
  uri:
    url: "http://localhost:8080/actuator/health"
    status_code: 200
  register: api_result
  until: api_result.status == 200
  retries: 20
  delay: 10

# レガシーなシェルスクリプトの場合:
- name: レガシーサービスのステータスを確認
  shell: "/opt/bin/check_app_status.sh"
  register: shell_out
  until: shell_out.rc == 0
  retries: 15
  delay: 5
  failed_when: false # リトライが完了する前にタスクがクラッシュするのを防ぐ

2. 可視性の高いデバッグ条件が正しく見えるのに失敗し続ける場合は、Ansible が実際に何を受け取っているかを確認する必要があります。よくある間違いは、stdout がきれいな文字列であると思い込むことです。trim フィルタや search テストを使用して、余分なスペースやログのタイムスタンプに対してロジックを堅牢にしましょう。

- name: ログで「Database Migration Complete」を確認するまで待機
  shell: tail -n 5 /var/log/deploy.log
  register: log_check
  # 完全一致よりも search を使う方が安全
  until: log_check.stdout is search("Complete")
  retries: 12
  delay: 10

3. ポーリング期間の調整ここでは計算が重要です。重いデータベース移行に5分かかるのに、Ansible タスクで retries: 5delay: 10 を設定している場合、50秒の猶予しか与えていないことになります。常に最悪のシナリオを計算し、20% の安全マージンを追加してください。

- name: 重い DB 移行を待機(最大6分)
  command: /usr/bin/verify_db_schema.sh
  register: db_status
  until: db_status.rc == 0
  # 36回の試行 * 10秒の遅延 = 360秒
  retries: 36
  delay: 10

検証:解決策の確認プレイブックを -v フラグ付きで実行し、試行状況をリアルタイムで監視します。プレイブックが停止することなく、リトライカウンターが増えていくのが見えるはずです:

FAILED - RETRYING: Wait for API (20 retries left).
FAILED - RETRYING: Wait for API (19 retries left).
...
changed: [app_server] => {"attempts": 12, "changed": true, "status": 200}

タスクが最終的に緑色(success)になれば、タイミングとロジックが同期しています。36回目の試行に達しても失敗する場合は、Ansible のコードではなく、アプリケーションサービス側に問題があります。

安定したループのためのベストプラクティス- wait_for を優先する: 複雑な until ループを実行する前に、wait_for モジュールを使用してポートが開いているか確認してください。高速でリソース消費も少なくなります。- リターンコードを確認する: until: result.rc == 0 でテストする方が、stdout の文字列をパースするよりもほぼ常に信頼性が高いです。- 文字列をサニタイズする: テキストの一致を確認する必要がある場合は、| trim を使用して、比較失敗の原因となる目に見えない改行文字を削除してください。- ignore_errors を避ける: 一時しのぎとして ignore_errors: yes を使用しないでください。これは正当な失敗を隠してしまいます。代わりに failed_when を使用するか、until 条件を洗練させてください。

Related Error Notes