【連載 第7回】エラーハンドリングとデバッグ:止まらない自動化のために〜失敗をコントロールする技術〜

「エラーで止まりました。サーバーの状態? 分かりません…」が一番怖い。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回(第6回)は、Ansible Vaultを使って機密情報を安全に管理する方法を学びました。
これでセキュリティもバッチリですね。

さて、自動化を進めていくと、避けて通れないのが「エラー」です。
手動作業なら、エラーが出たらその場で人間が判断して、「一つ前の手順に戻そう」とか「このエラーは無視して進めよう」と対応できます。
しかし、Ansibleは指示された通りにしか動きません。

  • 「パッケージのインストール中にネットワークが切れてエラーになった」
  • 「設定ファイルを書き換えた後、サービスの再起動に失敗した」

この時、最悪なのは「設定ファイルは新しいのに、サービスは止まったまま」という中途半端な状態で放置されることです。
今回は、こうした事態を防ぐための「エラーハンドリング(例外処理)」と、原因を素早く突き止めるための「デバッグ技術」を学びます。

コウ君

先生、耳が痛いです…。
この前、100台のサーバーにパッチを当てるPlaybookを流したんですが、50台目でエラーが出て止まっちゃって。
「どこまで終わったの?」「残りのサーバーはどうするの?」って上司に詰められてパニックになりました。
エラーが出ても安全に止まる方法、教えてください!

リナックス先生

それは大変だったわね、コウ君。
プロの自動化は「正常に動くこと」だけでなく、「失敗した時にどうリカバリーするか」まで設計するものなの。
Ansibleには、エラー発生時に自動で元に戻す(ロールバック)機能や、実行中に変数を覗き見て修正するデバッガー機能まであるわ。
これらを使いこなせれば、障害対応のスピードが劇的に上がるわよ!

本記事では、堅牢なPlaybookを書くためのエラー制御構文(block/rescue)と、プロが愛用するデバッグオプションを徹底解説します。


1. デバッグの基本:-v オプションとdebugモジュール

何かおかしいと思った時、まずやるべきことは「情報の可視化」です。
Ansibleには強力なログ出力機能と、変数を表示するモジュールがあります。

1-1. 実行時の詳細出力 (-v オプション)

ansible-playbook コマンドを実行する際、-v (verbose) オプションをつけると、出力される情報の量が増えます。
v の数によって詳細度が変わります。

オプション 出力内容 使いどころ
-v タスクの結果(changed/failed)の概要を表示。 普段使い。
-vv タスクの入出力データを表示。 変数の値がおかしい時。
-vvv ターゲットホストとの接続情報などを表示。 SSHがつながらない時。
-vvvv 接続デバッグの全情報(ユーザー名、パスワード以外)。 Ansible自体のバグを疑う時(稀)。

基本的には -vv をつければ、どの変数がどう展開されたかが見えるので、原因特定に役立ちます。

1-2. 変数の中身を見る (debugモジュール)

プログラミングでいう print()console.log() にあたるのが debug モジュールです。
「この変数は正しく定義されているか?」「registerで取得した値はどうなっているか?」を確認するのに必須です。

    - name: 変数の中身を確認
      ansible.builtin.debug:
        var: my_variable_name
        verbosity: 1  # -v オプションを付けた時だけ表示する設定

verbosity を設定しておくと、普段の実行時には表示されず、デバッグ時(-v)のみ表示されるようになるため、Playbookに埋め込んだままでも邪魔になりません。


2. エラーハンドリングの基礎:ignore_errors

通常、Ansibleはタスクが失敗(赤色)すると、そのホストに対する処理を即座に停止します。
しかし、「失敗してもいいから次に進んでほしい」場合もあります。

例えば、「古い設定ファイルがあれば削除する」タスクなどです。
ファイルがなければエラーになりますが、それは「削除成功」と同じ意味なので、止まってほしくありません。

ignore_errors: yes

    - name: 古いログファイルを削除(なければエラーになるが無視)
      ansible.builtin.command: rm /var/log/old_app.log
      ignore_errors: yes

これを設定すると、エラーが発生しても赤文字で表示された後、...ignoring と表示されて次のタスクに進みます。

⚠️ 注意点
ignore_errors はあくまで「エラーを無視して進む」だけです。
本当にエラーだった場合(権限不足など)も見逃してしまうため、多用は禁物です。
基本的には、後述する failed_whenstat モジュールによる事前チェックを使うほうが安全です。


3. ブロック構造による例外処理:block / rescue / always

プログラミング言語(PythonやJavaなど)には try-catch-finally という構文があります。
「処理を実行し(try)、エラーが起きたら復旧処理をし(catch)、最後に必ず後始末をする(finally)」というものです。

Ansibleでも block / rescue / always を使うことで、これと同じことができます。
これが「ロールバック処理」実装の鍵です。

構成例:アップデート失敗時のロールバック

「アプリケーションを更新してみて、失敗したら旧バージョンに戻す」という処理を書いてみましょう。

  tasks:
    - name: アプリケーションの更新処理
      block:
        - name: バージョンアップを実行
          ansible.builtin.command: /opt/app/bin/update.sh
          
        - name: 起動確認(ヘルスチェック)
          ansible.builtin.uri:
            url: http://localhost:8080/health
            status_code: 200

      rescue:
        - name: 【復旧】更新に失敗したため、旧バージョンに戻す
          ansible.builtin.command: /opt/app/bin/rollback.sh

        - name: 管理者に通知する
          ansible.builtin.debug:
            msg: "アップデートに失敗しました。ロールバックを実行しました。"

      always:
        - name: 一時ファイルの削除(成功・失敗に関わらず実行)
          ansible.builtin.file:
            path: /tmp/update_work_dir
            state: absent

解説:

  • block: メインの処理を記述します。ここでエラーが発生すると、即座に rescue に飛びます。
  • rescue: block でエラーが起きた時だけ実行されます。ここで環境を元に戻します。
  • always: エラーの有無にかかわらず、最後に必ず実行されます。一時ファイルの掃除などに使います。

これを使えば、「エラーで止まって中途半端な状態」を回避し、常に「正常な状態(旧バージョン)」か「新しい状態(新バージョン)」のどちらかを保つことができます。


4. 「失敗」の定義を変える:failed_when (復習と応用)

第2回でも少し触れましたが、command モジュールなどは終了コード(rc)が0以外だとエラーになります。
しかし、コマンドによっては「特定のメッセージが出たらエラー」と判断したい場合があります。

標準出力の内容でエラー判定する

例えば、「エラー」という文字が含まれていたら失敗扱いにする場合。

    - name: ログチェック
      ansible.builtin.command: cat /var/log/deploy.log
      register: log_result
      failed_when: "'ERROR' in log_result.stdout"

複数の条件を組み合わせる

「終了コードが0以外、かつ、”not found” というメッセージが含まれていない場合」のみエラーとする(”not found” は許容する)場合。

    - name: ユーザー削除コマンド
      ansible.builtin.command: delete_user.sh bob
      register: result
      failed_when:
        - result.rc != 0
        - '"user not found" not in result.stderr'

このように failed_when を使いこなすことで、コマンド特有の挙動に合わせた柔軟なエラー制御が可能になります。


5. 「変更」の定義を変える:changed_when (復習と応用)

Ansibleの理想は「冪等性(何度実行しても同じ結果になる)」です。
しかし、command モジュールは実行するたびに必ず changed(黄色)になってしまいます。
これでは「本当に変更があったのか?」が分からず、Handlersの無駄な発火にも繋がります。

実行結果に基づいて changed を判定する

例えば、「設定ファイルを更新するコマンド」を実行した際、標準出力に “Updated” と出た時だけ changed にしたい場合。

    - name: 設定更新ツールを実行
      ansible.builtin.command: /usr/local/bin/config_updater
      register: update_result
      changed_when: "'Updated' in update_result.stdout"

こうすることで、何も変更がなかった時は ok(緑色)になり、Handlersも発動しません。
「無駄な再起動を防ぐ」ための重要なテクニックです。


6. 【プロの技】デバッガー (debugger) の活用

これは中級者でも意外と知らない機能です。
Ansibleには、タスクが失敗した瞬間に一時停止し、インタラクティブ(対話的)に調査・修正ができる「デバッガー」が搭載されています。

6-1. デバッガーの有効化

Playbook内のタスクに debugger: on_failed を記述します。

    - name: わざと失敗するタスク
      ansible.builtin.service:
        name: non_existent_service
        state: restarted
      debugger: on_failed

6-2. デバッグモードでの操作

実行してエラーになると、プロンプトが [target_host] TASK: ... (debug)> に変わります。
ここで以下のコマンドが使えます。

  • p task.args: 現在のタスクの引数(パラメータ)を表示する。
  • p result: エラー内容を表示する。
  • task.args['name'] = 'httpd': 変数やパラメータをその場で書き換える。
  • r (redo): タスクを再実行する。
  • c (continue): 無視して次に進む。

実際の使用イメージ:

  1. サービス名が間違っていてエラーで停止した。
  2. デバッガーで正しいサービス名に書き換える。
    task.args['name'] = 'httpd'
  3. 再実行する。
    r
  4. 成功して次へ進む!

いちいちPlaybookを修正して最初から流し直す必要がないため、開発中のトライ&エラーが劇的に速くなります。


まとめ:失敗をコントロールせよ

お疲れ様でした!
第7回では、自動化の信頼性を高めるためのエラーハンドリングとデバッグ手法を学びました。

今回の要点まとめ:

  • -vv オプションdebug モジュール はトラブルシューティングの基本。
  • ignore_errors は安易に使わず、リスクを理解して使う。
  • block/rescue/always を使えば、エラー時のロールバック処理が書ける。
  • failed_whenchanged_when で、タスクの結果判定を厳密にする。
  • debugger: on_failed を使えば、エラー発生箇所で一時停止して修正・再開ができる。
コウ君

デバッガー機能、初めて知りました!
エラーが出るたびに修正して、SSH切断して、また最初から実行して…ってやってた時間がもったいないです。
block/rescueも、シェルスクリプトで書くと超大変なのに、Ansibleならこんなにスッキリ書けるんですね。

さて、いよいよ次回は最終回です。
これまで学んだ知識(変数、ループ、ロール、Vault、エラー処理)を総動員して、実用的かつ堅牢な「LAMP環境の完全自動構築」プロジェクトに挑戦します。

次回、【第8回】実践プロジェクト:LAMP環境の完全自動構築と総まとめ で、有終の美を飾りましょう!

▼ 壊しても安心な環境でデバッグしよう ▼

スナップショット機能で即復旧
「おすすめVPS」

VPSランキングを見る

運用設計スキルを活かす
「ITエンジニア転職」

転職エージェントを見る

コメント