Ansibleのasyncとpollで長時間タスクを制御する方法|タイムアウト対策と並行実行の設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > Ansibleのasyncとpollで長時間タスクを制御する方法|タイムアウト対策と並行実行の設計
「AnsibleのPlaybookを流したら途中でSSH接続が切れてタスクの成否がわからなくなった」「大量のホストに長時間かかる更新を流したいが、1台ずつ順番に待っていては時間がかかりすぎる」——

Ansibleのデフォルト動作は「タスクが完了するまでSSH接続を保持し続ける同期実行」です。数秒で終わるタスクなら問題ありませんが、パッケージの大規模更新やソースビルドなど、数分以上かかる処理ではSSHのタイムアウトや長い待ち時間が問題になります。

この記事では、Ansibleの asyncpoll を使って長時間タスクを非同期実行に切り替える方法を解説します。基本的な仕組みから async_status による完了確認、複数ホストへの並行実行設計、現場でよくある落とし穴まで体系的にまとめました。

この記事のポイント

・asyncでタスクをバックグラウンド実行し、SSHタイムアウトを回避できる
・poll: 0(fire-and-forget)+async_statusで複数ホストの並行実行が可能
・retries×delayがasync値以上になるよう設計することが重要
・Windowsモジュールはasyncに対応していないため注意が必要


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

asyncとpollが必要になる場面——なぜ長時間タスクでSSHが切れるのか

Ansibleは通常、コントロールノード(Ansibleを実行するPC・サーバー)からSSH経由でリモートホストに接続し、タスクを実行します。この接続はタスクが完了するまで維持されます。

問題が起きるのは次のような状況です。

パッケージの大規模更新(dnf update / apt upgrade):依存関係の解決とダウンロードに数分以上かかる
ソースコードからのビルド(make / cmake):コア数が少ない検証機では十数分以上かかることがある
データベースのバックアップ・ダンプ:数十GB規模では長時間連続してI/Oが走る
OSカーネル更新後の再起動待ち:再起動中はSSH接続が必ず切れる

デフォルト設定では、SSH接続がネットワーク経路のタイムアウトやSSHサーバー設定(ClientAliveInterval 等)によって切断されることがあります。接続が切れると、Ansibleはタスクの成否を判断できないまま失敗扱いになります。

これを解決するのが asyncpoll です。タスクをバックグラウンドに移すことでSSH接続を解放し、後から結果を確認する仕組みです。

asyncとpollの仕組みと基本設定

1. asyncでタスクをバックグラウンド実行する

async パラメーターに秒数を設定すると、タスクはリモートホスト上でバックグラウンドプロセスとして起動されます。指定した秒数がタスクの最大実行時間となり、超過するとKillされます。

# async: タスクの最大実行時間(秒)を指定してバックグラウンド実行する - name: パッケージを全件更新する ansible.builtin.dnf: name: "*" state: latest async: 1800 # 最大1800秒(30分)。超過するとKillされる poll: 30 # 30秒ごとにAnsibleが完了を確認する

async の値は「タスクが最長でかかる時間の見積もり」より大きく設定するのが原則です。小さすぎると途中でKillされて失敗します。パッケージ更新なら1800秒(30分)、ビルドなら3600秒(1時間)など、余裕を持った値にしましょう。

2. pollで完了確認の間隔を設定する

poll パラメーターはAnsibleがリモートホストへ完了確認(ポーリング)に行く間隔(秒)です。デフォルト値は10です。

poll: 10(デフォルト):10秒ごとに完了を確認する。確認が終わるまで次のタスクに進まない
poll: 30:確認頻度を下げる。ネットワーク負荷とSSH接続回数を減らしたい場合
poll: 0:完了を待たずに次のタスクへ進む(fire-and-forgetモード)

pollの値を大きくするほど確認頻度は下がりますが、完了直後の検知が遅れます。バランスを見て設定しましょう。パッケージ更新のように「完了のタイミングが予測しやすい」タスクは30秒程度が目安です。

3. poll: 0でfire-and-forgetにする

poll: 0 を設定すると、Ansibleはタスクをキックした直後に次のタスクへ進みます。ジョブIDが返され、後から async_status モジュールで結果を確認できます。

# poll: 0 でfire-and-forget(完了を待たずに次へ進む) - name: パッケージ更新をバックグラウンドで開始する ansible.builtin.dnf: name: "*" state: latest async: 1800 poll: 0 register: dnf_job # ジョブIDをregisterで保持する(後で確認に使う)

register でジョブの情報(ジョブIDを含む辞書)を変数に保存しておき、後のタスクで完了を確認します。この変数を忘れると、ジョブが失敗しても検知できなくなるため必ず記述してください。

async_statusで非同期ジョブの完了を確認する

1. ジョブIDを使ってasync_statusで完了を待つ

poll: 0 で投げたジョブは、async_status モジュールを使って完了を待ちます。untilretries / delay を組み合わせた繰り返しループで実装するのが基本パターンです。

# ジョブの完了を定期確認する - name: パッケージ更新の完了を待つ ansible.builtin.async_status: jid: "{{ dnf_job.ansible_job_id }}" # ジョブIDを指定 register: job_result until: job_result.finished # finishedがtrueになるまで繰り返す retries: 60 # 最大60回試行 delay: 30 # 30秒ごとに確認する # 設計上の原則: retries * delay >= async の関係を必ず守る # この例: 60 * 30 = 1800秒 = async: 1800 → OK

重要な設計ルール: retries × delay の値が async の値以上になるよう設定してください。これが不足していると、asyncジョブが完了する前にuntilループが終了し、タスクが失敗扱いになります。

2. 複数タスクをfireしてまとめて待つ設計パターン

fire-and-forgetの最大の強みは「複数ジョブを同時に起動してから、まとめて結果を待つ」設計が可能な点です。独立した複数処理を順番に実行する必要がなくなります。

# 独立した複数ジョブをfireしてバックグラウンドで並行実行する - name: DB1バックアップを開始 ansible.builtin.command: /usr/local/bin/backup-db1.sh async: 3600 poll: 0 register: backup1_job - name: DB2バックアップを開始 ansible.builtin.command: /usr/local/bin/backup-db2.sh async: 3600 poll: 0 register: backup2_job # この時点でDB1とDB2は並行実行中 - name: DB1バックアップの完了を待つ ansible.builtin.async_status: jid: "{{ backup1_job.ansible_job_id }}" register: result1 until: result1.finished retries: 120 delay: 30 - name: DB2バックアップの完了を待つ ansible.builtin.async_status: jid: "{{ backup2_job.ansible_job_id }}" register: result2 until: result2.finished retries: 120 delay: 30

順番にfireしても、バックグラウンドで並行実行されます。合計処理時間は「DB1 + DB2」ではなく「max(DB1, DB2)」になります。

3. 実際の検証サーバーでの出力例

RHEL 9.4 検証サーバー(web01.example.internal、web02.example.internal)で dnf update をasync実行した際の出力例です。

TASK [パッケージ更新をバックグラウンドで開始する] ***** changed: [web01.example.internal] changed: [web02.example.internal] TASK [パッケージ更新の完了を待つ] ******************** FAILED - RETRYING: [web01.example.internal]: パッケージ更新の完了を待つ (59 retries left) FAILED - RETRYING: [web02.example.internal]: パッケージ更新の完了を待つ (59 retries left) FAILED - RETRYING: [web01.example.internal]: パッケージ更新の完了を待つ (58 retries left) FAILED - RETRYING: [web02.example.internal]: パッケージ更新の完了を待つ (58 retries left) changed: [web01.example.internal] changed: [web02.example.internal] PLAY RECAP ********************************************* web01.example.internal : ok=2 changed=2 unreachable=0 failed=0 web02.example.internal : ok=2 changed=2 unreachable=0 failed=0

FAILED - RETRYING の表示はエラーではありません。until ループが完了を確認しながら待機中であることを示しています。最終的にジョブが完了すると changed で終了し、PLAY RECAPに failed=0 が表示されます。
AnsibleのPlaybook設計からCI/CDパイプラインまで、現場で使える構成管理の「型」を身につけたい方へ。20年以上の現場経験を持つエンジニアが基礎から体系的に指導します。
Ansible実践ハンズオンの詳細を見る >>

複数ホストへの並行実行設計

1. 各ホストで独立してasyncジョブを走らせる

asyncを使うと、Ansibleが管理する各ホストで独立してバックグラウンドジョブが走ります。インベントリに複数ホストを記載した場合、デフォルトでは全ホストへ同時にジョブをfireするため、並行実行になります。

# インベントリの全Webサーバーで並行してパッケージ更新を実行する --- - name: 全Webサーバーのパッケージ更新 hosts: web_servers # web01, web02, web03 の3台 tasks: - name: パッケージ更新をバックグラウンドで開始 ansible.builtin.dnf: name: "*" state: latest async: 1800 poll: 0 register: update_job - name: パッケージ更新の完了を待つ ansible.builtin.async_status: jid: "{{ update_job.ansible_job_id }}" register: job_result until: job_result.finished retries: 60 delay: 30

この構成では3台が同時にパッケージ更新を開始します。従来の同期実行(linearストラテジ)では「3台 × 1台あたりの更新時間」かかるところ、asyncの並行実行なら「1台分の更新時間」で3台の更新が完了します。

2. freeストラテジで並行度をさらに上げる

Ansibleのデフォルト実行ストラテジは linear で、全ホストが同じタスクを終えるまで次のタスクに進みません。free ストラテジに切り替えると、各ホストが独立して次のタスクへ進めるため、ホスト間の処理速度差による待ち時間がなくなります。

--- - name: 全Webサーバーのパッケージ更新(freeストラテジ) hosts: web_servers strategy: free # ホストごとに独立して次のタスクへ進む tasks: - name: パッケージ更新をバックグラウンドで開始 ansible.builtin.dnf: name: "*" state: latest async: 1800 poll: 30 # pollあり: このホストの完了を待ってから次へ

strategy: free では処理が速いホストが先に次のタスクへ進みます。ただし strategy: free は各ホストの進行状況が非同期になるため、タスク間に依存関係がある場合は設計に注意が必要です。独立したパッケージ更新やバックアップのような処理に向いています。

トラブルシュートと落とし穴

1. async_statusで「Could not find job」が出る場合

async_status 実行時に「Could not find job」エラーが出る主な原因は次の2つです。

ホスト間で変数が混同している:複数ホストで実行した場合、Ansibleは各ホストの変数を自動分離しますが、delegate_toを使う場合などに混同が起きることがある
別のPlayで変数スコープが切れている: register した変数は同一Playの範囲でのみ参照できる。別のPlayではjidが引き継がれない

# ジョブIDが本当に取れているか確認する - name: ジョブIDのデバッグ出力 ansible.builtin.debug: msg: "Job ID: {{ update_job.ansible_job_id }}"

debug モジュールでジョブIDを出力して確認しましょう。ansible_job_id が空文字や undefined の場合、前のタスクでregisterが正しく機能していません。

2. retriesが足りずuntilループがタイムアウトする場合

retries × delay < async の設定は設計ミスです。asyncジョブが完了する前にuntilループが終了して、タスクが失敗扱いになります。

# NG例: retries * delay = 10 * 30 = 300秒 だが async: 1800 秒 # → 1800秒のジョブが完了する前にuntilループが300秒で終わる - name: 完了確認(NG設定) ansible.builtin.async_status: jid: "{{ update_job.ansible_job_id }}" register: job_result until: job_result.finished retries: 10 # 少なすぎる delay: 30 # OK例: retries * delay = 60 * 30 = 1800秒 = async: 1800 秒 # → asyncと同じ秒数まで待てる - name: 完了確認(OK設定) ansible.builtin.async_status: jid: "{{ update_job.ansible_job_id }}" register: job_result until: job_result.finished retries: 60 # retries * delay >= async を常に守る delay: 30

3. asyncが使えないケースと注意事項

async はすべての状況で使えるわけではありません。次のケースでは使用できません。

Windowsホストへのタスク: WinRMベースのWindowsモジュールはasyncに対応していない
include_tasks などの動的インクルード: タスク自体にasyncは設定できない(インクルード先のタスクに個別に設定する必要がある)
poll: 0のまま完了確認なし: fire-and-forgetでasync_statusを省くと、ジョブが失敗しても検知できずPlaybookが成功扱いで終わる

特に「poll: 0のまま完了確認なし」は最も見つけにくい設計ミスです。エラーが発生しても表面上Playbookが正常終了するため、長期間気づかないことがあります。poll: 0 を使う場合は必ず対応する async_status タスクを実装してください。

本記事のまとめ

やりたいこと 設定・方法
SSHタイムアウトを回避して長時間タスクを実行 async: 秒数 をタスクに追加する
定期的に完了を確認しながら待つ async: 秒数poll: 秒数 を組み合わせる
タスクを投げてすぐ次へ進む(並行実行) async: 秒数 poll: 0register でジョブID保持
fire-and-forgetジョブの完了を後から確認 ansible.builtin.async_status: jid: "{{ 変数.ansible_job_id }}"
untilループの設計原則 retries × delay >= async の関係を常に守る
複数ホストのホスト間待ちをなくす Playに strategy: free を設定する
Ansibleの asyncpoll は、長時間タスクのSSHタイムアウト回避と複数ホストへの並行実行を実現するPlaybook設計の要です。特に poll: 0 によるfire-and-forgetと async_status の組み合わせは、Playbookの総実行時間を大幅に短縮できます。

「接続タイムアウトが怖いからsshの設定を変えて対処する」ではなく、asyncとpollで正しく非同期設計することが、現場で信頼されるPlaybook実装の第一歩です。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。