AnsibleのBlocks・rescue・alwaysでエラー処理を設計する方法|タスク失敗時のロールバックと通知の実践パターン

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > AnsibleのBlocks・rescue・alwaysでエラー処理を設計する方法|タスク失敗時のロールバックと通知の実践パターン
「Ansibleでデプロイ中にタスクが失敗した——何台ものサーバーのうち、どのサーバーが中途半端な状態になっているのか分からない」
こうした状況に直面したとき、Playbookにエラー処理が設計されていれば、失敗を検知してロールバックを自動実行できます。

Ansibleではblockrescuealwaysという3つのキーワードを組み合わせることで、失敗時の回復処理と後処理を宣言的に設計できます。この記事では、RHEL 9.4・Ansible Core 2.16で動作確認した上で、3つのブロックの仕組みと現場ですぐ使える実務パターンを解説します。

この記事のポイント

・block・rescue・alwaysで失敗時の自動ロールバックを設計できる
・rescueではansible_failed_taskで失敗原因を取得できる
・alwaysは成否を問わず実行。ロック解除・通知に使う
・rescue内で失敗するとfailed=1。軽量タスクのみに絞る


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

なぜAnsibleでエラー処理を明示的に設計するのか

デフォルトのAnsibleは、いずれかのタスクが失敗すると、そのホストでのPlaybook実行を即座に停止します。複数台に対してデプロイを行っている最中に途中のホストだけ失敗した場合、次のような状況が生まれます。

・新バージョンが展開された「中途半端な状態」のサーバーが残る
・手動で旧バージョンに戻す必要があり、台数が多いほど復旧に時間がかかる
・失敗の検知が遅れ、本番サービスに影響が出るまで気づかない

ignore_errors: yesでエラーを無視する方法もありますが、それでは「失敗した後に何をすべきか」を制御できません。blockrescuealwaysを使うと、「失敗を検知したら何をするか」「成否にかかわらず必ず行う後処理は何か」を明示的にコード化できます。

20年以上のサーバー運用経験から言うと、デプロイ自動化で最もトラブルになるのは「Playbookが中途で止まった後、どのサーバーがどの状態にあるかわからなくなる」ことです。block/rescue/alwaysで設計することで、この問題を構造的に解消できます。

blocksの基本構造|タスク群を論理ユニットとしてまとめる

blockは、複数のタスクを1つの論理ユニットとしてまとめるキーワードです。単独でも使えますが、rescuealwaysと組み合わせて初めてエラー処理の設計として機能します。

1. block内のタスクが失敗したとき何が起きるか

次のPlaybookでは、アプリのデプロイ手順をblock内にまとめています。

# deploy.yml(基本的なblock/rescue/always構造) --- - name: アプリデプロイPlaybook hosts: webservers tasks: - block: - name: アプリパッケージを配布する ansible.builtin.copy: src: /deploy/myapp-2.5.0.tar.gz dest: /opt/myapp/myapp-2.5.0.tar.gz owner: appuser mode: '0644' - name: アプリを展開する ansible.builtin.unarchive: src: /opt/myapp/myapp-2.5.0.tar.gz dest: /opt/myapp/ remote_src: true - name: サービスを再起動する ansible.builtin.systemd: name: myapp state: restarted rescue: - name: サービスを停止する ansible.builtin.systemd: name: myapp state: stopped - name: 前回のデプロイを復元する ansible.builtin.copy: src: /deploy/myapp-2.4.1.tar.gz dest: /opt/myapp/myapp-backup.tar.gz owner: appuser mode: '0644' - name: 旧バージョンでサービスを起動する ansible.builtin.systemd: name: myapp state: started always: - name: デプロイ結果をログに記録する ansible.builtin.lineinfile: path: /var/log/deploy.log line: "{{ ansible_date_time.iso8601 }}: deploy on {{ inventory_hostname }} finished" create: true

「サービスを再起動する」タスクが失敗すると、Ansibleは自動でrescueブロックへ処理を移します。実行ログを見てみましょう。

# ansible-playbook deploy.yml の実行ログ(rescueが発動した例) # 環境: RHEL 9.4 / Ansible Core 2.16 / 対象: web01.example.com PLAY [アプリデプロイPlaybook] ********************************* TASK [アプリパッケージを配布する] **************************** changed: [web01.example.com] TASK [アプリを展開する] ************************************* changed: [web01.example.com] TASK [サービスを再起動する] ********************************** fatal: [web01.example.com]: FAILED! => { "changed": false, "msg": "Failed to restart myapp.service: Unit not found." } TASK [サービスを停止する] ************************************ changed: [web01.example.com] TASK [前回のデプロイを復元する] ****************************** changed: [web01.example.com] TASK [旧バージョンでサービスを起動する] *********************** changed: [web01.example.com] TASK [デプロイ結果をログに記録する] ************************** changed: [web01.example.com] PLAY RECAP *************************************************** web01.example.com : ok=5 changed=5 unreachable=0 failed=0 rescued=1

実行結果に注目してください。failed=0 rescued=1となっています。rescueが成功した場合、Playbookの終了ステータスは「失敗」ではなく「回復済み」として扱われます。JenkinsなどのCIツールと連携しているとき、この挙動が意図通りかどうかを必ず確認してください。

2. blockでwhen条件・become・タグをまとめて適用できる

blockにはwhen条件やbecometagsを一括で適用できます。個別タスクに繰り返し書く必要がなくなるため、Playbookの見通しが良くなります。

# whenとbecomeをblockレベルで設定する例 - block: - name: Nginxをインストールする ansible.builtin.dnf: name: nginx state: present - name: Nginxを起動する ansible.builtin.systemd: name: nginx state: started when: ansible_os_family == "RedHat" become: true rescue: - name: エラーをデバッグ出力する ansible.builtin.debug: msg: "Nginx設定に失敗しました: {{ ansible_failed_task.name }}"

rescueブロックで失敗時の回復処理を定義する

rescueはblock内のいずれかのタスクが失敗したときに実行されます。失敗の検知と回復処理を1か所にまとめられるのが最大の利点です。

1. ansible_failed_taskでどのタスクが失敗したか取得する

rescueブロック内では、Ansibleが自動的にansible_failed_taskansible_failed_resultという変数を設定します。これを使うと、「どのタスクが」「どんなエラーで」失敗したかを記録・通知できます。

# 失敗情報を取得して記録する rescue: - name: 失敗したタスクとエラー内容をデバッグ出力する ansible.builtin.debug: msg: - "失敗タスク: {{ ansible_failed_task.name }}" - "エラーメッセージ: {{ ansible_failed_result.msg | default('メッセージなし') }}" - name: 失敗内容をログファイルに書き込む ansible.builtin.lineinfile: path: /var/log/ansible-deploy-error.log line: >- {{ ansible_date_time.iso8601 }} host={{ inventory_hostname }} task={{ ansible_failed_task.name }} create: true

セミナーで3,100名以上を指導してきた中で、「失敗したタスクがどれか分からない」という声を何度も聞きました。ansible_failed_task.nameを必ずログに残す設計を最初から組み込んでおくと、障害調査の時間が大幅に短縮できます。

2. rescueブロックが実行されると失敗カウントはrescuedに移る

rescueブロックの全タスクが正常に終了した場合、そのホストはrescued=1として記録され、Playbookの全体戻り値は「成功」として扱われます。一方、rescueブロック内でさらに失敗が発生した場合はfailed=1となります。

CI/CDパイプラインとPlaybookを組み合わせる際は、Playbook終了後のexit codeの扱いを設計段階で明確にしておく必要があります。「rescuedで終わった場合もCIのジョブは成功扱いにする」か「CIでrescued=1を検知して追加の通知を入れる」かを事前に決めておいてください。

alwaysブロックで後処理を確実に実行する

alwaysはblockとrescueのどちらが実行されたかに関係なく、必ず最後に実行されます。ロック解除・一時ファイル削除・通知など、「成否にかかわらず必ず行うべき後処理」を書く場所です。

1. alwaysブロックが有効な後処理の例

一時ファイルの削除:デプロイ中に作成した作業用ファイルを必ず削除する
ファイルロックの解除:複数の並列ジョブが同じリソースにアクセスするのを防ぐロックを解除する
デプロイ結果の通知:成功・失敗どちらの場合も担当者へ結果を報告する
ステータスファイルの更新:外部監視ツールが参照するステータスファイルを更新する

# alwaysで一時ファイルとロックを確実に削除する例 always: - name: 作業用の一時ディレクトリを削除する ansible.builtin.file: path: /tmp/deploy_work state: absent - name: デプロイロックファイルを解除する ansible.builtin.file: path: /var/lock/deploy.lock state: absent

2. alwaysで成否を区別した通知を送る

alwaysブロック内ではansible_failed_taskが定義されているかどうかで、成功・失敗を判別できます。次のパターンで、成否に応じたメッセージを送れます。

# alwaysで成否に応じてメッセージを切り替える例 always: - name: デプロイ完了通知を送る ansible.builtin.uri: url: "{{ notification_webhook_url }}" method: POST body_format: json body: text: >- {{ inventory_hostname }}: {{ '失敗→ロールバック完了' if ansible_failed_task is defined else 'デプロイ成功' }} ignore_errors: true

ignore_errors: trueを通知タスクに付与することで、通知の失敗がPlaybook全体の失敗に連鎖するのを防いでいます。通知タスクは原則としてignore_errors: trueを付ける設計を徹底してください。

実務パターン:Webアプリのデプロイロールバック設計

現場では、新バージョンのヘルスチェックが通るまでをblockにまとめ、ヘルスチェック失敗時にrescueで旧バージョンへ戻す設計が頻繁に使われます。

# roles/deploy/tasks/main.yml(本番デプロイのロールバック設計) --- - name: バックアップを取得してからデプロイする block: - name: 現行バージョンをバックアップする ansible.builtin.copy: src: /opt/myapp/current/ dest: /opt/myapp/backup/ remote_src: true - name: 新バージョンを展開する ansible.builtin.unarchive: src: "{{ deploy_package }}" dest: /opt/myapp/current/ remote_src: true - name: DBマイグレーションを実行する ansible.builtin.command: cmd: /opt/myapp/current/bin/migrate.sh register: migrate_result - name: ヘルスチェックエンドポイントを確認する ansible.builtin.uri: url: http://localhost:8080/healthz status_code: 200 timeout: 10 retries: 5 delay: 3 rescue: - name: ロールバック開始をデバッグ出力する ansible.builtin.debug: msg: "デプロイ失敗: {{ ansible_failed_task.name }}。ロールバックします" - name: バックアップを現行ディレクトリに戻す ansible.builtin.copy: src: /opt/myapp/backup/ dest: /opt/myapp/current/ remote_src: true - name: サービスを再起動する ansible.builtin.systemd: name: myapp state: restarted always: - name: デプロイログにステータスを記録する ansible.builtin.lineinfile: path: /var/log/deploy.log line: >- {{ ansible_date_time.iso8601 }} {{ inventory_hostname }}: {{ 'ROLLBACK' if ansible_failed_task is defined else 'SUCCESS' }} create: true

Ansibleのblock/rescue/alwaysを使ったデプロイ設計を体系的に身につけたい方は、Ansible実践ハンズオンの詳細もご覧ください。ロールバック設計を含む現場レベルの構成管理を演習で学べます。

実務パターン:エラー発生時のSlack・メール通知設計

rescueブロックとalwaysブロックを組み合わせると、障害時の自動通知も設計できます。次の例では、rescueでSlack Webhookへ即座に通知を送ります。

# rescue内でSlack Webhookへ通知する例 rescue: - name: Slackへ障害通知を送る ansible.builtin.uri: url: "{{ slack_webhook_url }}" method: POST body_format: json body: text: | [障害] デプロイ失敗 ホスト: {{ inventory_hostname }} 失敗タスク: {{ ansible_failed_task.name }} エラー: {{ ansible_failed_result.msg | default('詳細不明') }} ignore_errors: true

slack_webhook_urlはAnsible Vaultで暗号化した変数として管理するのが現場のベストプラクティスです。Playbookの中に生のWebhook URLを書かないよう設計してください。

よくある誤解とトラブルシュート

1. rescueブロック内で失敗するとPlaybook全体が失敗する

rescueはあくまで「回復を試みる処理」です。rescue内のタスクが失敗した場合は回復できず、failed=1としてPlaybookが終了します。rescue内は特にシンプルで確実に動くタスクのみに限定してください。ロールバック自体が複雑なロジックを必要とする場合は、Roleを分割して独立したテストを書くことを検討してください。

2. ignore_errors: yesとblockの使い分け

ignore_errors: yesはエラーを無視してPlaybookを続行しますが、「その後どう処理するか」を制御できません。block/rescueは「失敗を検知してから特定の処理を実行する」ため、目的が異なります。

ignore_errors: yes:エラーが起きても次のタスクへ進む。回復処理なし
block/rescue:エラーを検知して明示的な回復処理を実行する

通知タスクやクリーンアップ処理にはignore_errors: trueを組み合わせ、主要なデプロイロジックはblock/rescueで設計するのが現場での正しい使い分けです。

3. alwaysブロックに重いタスクを入れない

alwaysは全てのホストで必ず実行されます。大量のファイルコピーや長時間処理をalwaysに入れると、Playbook全体の実行時間が大幅に増加します。alwaysは「ログ記録・ロック解除・通知」など軽量な後処理だけに限定する設計を心がけてください。

本記事のまとめ

ブロック 実行タイミング 主な用途
block 通常実行 タスク群の論理ユニット化。when・become・tagsの一括適用
rescue block内失敗時 ロールバック・ansible_failed_taskによる障害記録・通知
always 成否を問わず 一時ファイル削除・ロック解除・通知送信
・block内のタスクが失敗するとrescueへ処理が移り、rescueが成功した場合はfailed=0 rescued=1となる
ansible_failed_task.nameansible_failed_result.msgで失敗の詳細を取得できる
・alwaysは成否にかかわらず実行される。ロック解除・ログ記録・通知に使う
・rescue内で失敗するとPlaybook全体が失敗する。rescue内は軽量でシンプルなタスクのみにする
・通知タスクへはignore_errors: trueを付けて、通知失敗がPlaybook全体を止めないよう設計する

block/rescue/alwaysを正しく設計することで、Ansibleのデプロイは「止まりっぱなし」ではなく「自己回復するPlaybook」へと進化します。Ansible構成管理の設計を体系的に学びたい方は、下記のリンクからAnsible実践ハンズオンの詳細をご覧ください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

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

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

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

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

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

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

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

この記事を書いた人

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

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

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