こうした状況に直面したとき、Playbookにエラー処理が設計されていれば、失敗を検知してロールバックを自動実行できます。
Ansibleでは
block・rescue・alwaysという3つのキーワードを組み合わせることで、失敗時の回復処理と後処理を宣言的に設計できます。この記事では、RHEL 9.4・Ansible Core 2.16で動作確認した上で、3つのブロックの仕組みと現場ですぐ使える実務パターンを解説します。この記事のポイント
・block・rescue・alwaysで失敗時の自動ロールバックを設計できる
・rescueではansible_failed_taskで失敗原因を取得できる
・alwaysは成否を問わず実行。ロック解除・通知に使う
・rescue内で失敗するとfailed=1。軽量タスクのみに絞る
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜAnsibleでエラー処理を明示的に設計するのか
デフォルトのAnsibleは、いずれかのタスクが失敗すると、そのホストでのPlaybook実行を即座に停止します。複数台に対してデプロイを行っている最中に途中のホストだけ失敗した場合、次のような状況が生まれます。・新バージョンが展開された「中途半端な状態」のサーバーが残る
・手動で旧バージョンに戻す必要があり、台数が多いほど復旧に時間がかかる
・失敗の検知が遅れ、本番サービスに影響が出るまで気づかない
ignore_errors: yesでエラーを無視する方法もありますが、それでは「失敗した後に何をすべきか」を制御できません。block・rescue・alwaysを使うと、「失敗を検知したら何をするか」「成否にかかわらず必ず行う後処理は何か」を明示的にコード化できます。20年以上のサーバー運用経験から言うと、デプロイ自動化で最もトラブルになるのは「Playbookが中途で止まった後、どのサーバーがどの状態にあるかわからなくなる」ことです。block/rescue/alwaysで設計することで、この問題を構造的に解消できます。
blocksの基本構造|タスク群を論理ユニットとしてまとめる
blockは、複数のタスクを1つの論理ユニットとしてまとめるキーワードです。単独でも使えますが、rescue・alwaysと組み合わせて初めてエラー処理の設計として機能します。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-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条件やbecome・tagsを一括で適用できます。個別タスクに繰り返し書く必要がなくなるため、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_taskとansible_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
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
実務パターン:エラー発生時の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 |
成否を問わず | 一時ファイル削除・ロック解除・通知送信 |
・
ansible_failed_task.nameとansible_failed_result.msgで失敗の詳細を取得できる・alwaysは成否にかかわらず実行される。ロック解除・ログ記録・通知に使う
・rescue内で失敗するとPlaybook全体が失敗する。rescue内は軽量でシンプルなタスクのみにする
・通知タスクへは
ignore_errors: trueを付けて、通知失敗がPlaybook全体を止めないよう設計するblock/rescue/alwaysを正しく設計することで、Ansibleのデプロイは「止まりっぱなし」ではなく「自己回復するPlaybook」へと進化します。Ansible構成管理の設計を体系的に学びたい方は、下記のリンクからAnsible実践ハンズオンの詳細をご覧ください。
Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:Ansibleのrole設計入門|ディレクトリ構造とtasks・handlers・defaultsで再利用可能なコードを作る方法
- この記事の属するカテゴリ:Ansibleへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます