【連載 第3回】ハンドラーと通知 (Handlers):無駄な再起動を防ぐ〜スマートな構成管理〜

「設定ファイルは変えてないけど、念のため再起動しておこう」←これが一番危険です。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回(第2回)は、LoopとWhenを使って、Playbookに条件分岐などのロジックを組み込む方法を学びました。
これで、OSの違いやユーザーの増減に柔軟に対応できるPlaybookが書けるようになったはずです。

さて、今回はサーバー構築・運用における「あるある」な悩みについてお話ししましょう。

  • 「Apacheの設定ファイルを書き換えたら、反映させるために再起動が必要だ」
  • 「でも、設定ファイルが変わっていない時は、再起動したくない(サービスを止めたくない)」
  • 「複数の設定ファイルを同時に更新した時、再起動は1回だけにまとめたい」

もしあなたが、タスクの最後に必ず service: state=restarted を書いているなら、それはサーバーに無駄な負荷をかけ、わずかとはいえダウンタイム(停止時間)を生み出しています。
Ansibleには、「変更があった時だけ、あとでまとめて実行する」という賢い仕組みがあります。
それが「ハンドラー (Handlers)」です。

コウ君

先生! 僕、いつもタスクの最後に再起動コマンドを入れてました…。
設定が変わってなくても「changed」になって再起動しちゃうから、なんとなく気持ち悪いなとは思ってたんです。
これって「変更があった時だけ」にできるんですか?

リナックス先生

その通りよ、コウ君。
Ansibleの「Handlers」を使えば、必要な時だけ、必要な処理を実行できるわ。
しかも、複数の変更があっても「最後に1回だけ」実行してくれる優れものよ。
今回は基本の notify から、プロが使う「即時反映テクニック」まで、一気にレベルアップしましょう!

本記事では、サービスの再起動を例に、Handlersの仕組みと、実務で必須となる応用テクニック(flush_handlers, force_handlers)を解説します。

🚀 【続・Ansible講座】本連載のカリキュラム


1. ハンドラー (Handlers) とは何か?

Ansibleのタスクは通常、上から順に実行されます。
しかし、Handlersセクションに書かれたタスクは、「他のタスクから呼び出された(通知された)時だけ」実行される特別なタスクです。

主な利用シーン

  • 設定ファイルの変更時: httpd.conf を書き換えたら Apache を再起動する。
  • アプリのデプロイ時: ソースコードを更新したら、アプリケーションサーバー(TomcatやUnicornなど)をリロードする。
  • データベースの初期化時: 新しいDBを作成した直後だけ、初期データを投入する。

メリット

  1. 無駄な処理を省ける: 変更がない(ok 状態の)時は実行されないため、処理時間の短縮とサービスの安定稼働につながります。
  2. 重複実行を防げる: 複数のタスクから同じハンドラーを呼んでも、実行されるのは「最後に1回だけ」です。

2. 基本的な書き方:notify と handlers

実際にApacheのインストールと設定変更を行うPlaybookを書いてみましょう。

ファイル名:handlers_basic.yml

---
- name: Handlersのテスト
  hosts: all
  become: true

  tasks:
    - name: Apacheをインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

    - name: 設定ファイルを配置
      ansible.builtin.copy:
        src: httpd.conf
        dest: /etc/httpd/conf/httpd.conf
      # 変更があった場合のみ、ハンドラーに通知を送る
      notify: Restart Apache

    - name: サービスを起動(常駐化)
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: true

  # ハンドラーの定義(タスクとは別のセクション)
  handlers:
    - name: Restart Apache
      ansible.builtin.service:
        name: httpd
        state: restarted

解説:

  • notify: Restart Apache
    copy タスクの結果が changed(変更あり)になった場合のみ、Restart Apache という名前のハンドラーを実行予約します。もしファイルが既に同じ内容だった場合(ok)、通知は飛びません。
  • handlers: セクション
    タスクリストの外側(同列)に定義します。ここの namenotify で指定した名前と一致する必要があります。

3. Handlersの重要な3つの挙動

ハンドラーを使いこなすには、その「動きのクセ」を理解しておく必要があります。

① 実行タイミングは「Playの最後」

ここが誤解されやすいポイントです。
notify を書いたタスクの直後に実行されるのではありません。
全てのタスクが終わった後に、まとめて実行されます。

実行フローの例:

  1. タスクA(設定変更) → notify: restart (予約!)
  2. タスクB(他の処理)
  3. タスクC(他の処理)
  4. ハンドラー実行: restart

② 重複は排除される(デデュープ)

もし、タスクAとタスクBの両方で notify: restart を呼んだとしても、再起動は最後に1回だけ行われます。
これにより、「設定ファイルを3つ書き換えたから3回再起動してしまい、サービス断時間が3倍になった」という事故を防げます。

③ 変更がないと動かない

Playbookを2回連続で実行したとします。
2回目は設定ファイルが既に最新なので、copy タスクは ok(緑色)になります。
この場合、notify は発動せず、ハンドラーも実行されません。
これがAnsibleの「冪等性(べきとうせい)」を支える重要な仕組みです。


4. 【応用】複数のタスクをまとめる「listen」

実務では、「Apacheの設定変更」と「SSL証明書の更新」のどちらが起きても、Webサービス全体を再起動したい場合があります。
さらに、NginxとApacheの両方を再起動したい場合もあるでしょう。

そんな時に便利なのが listen です。
「トピック(話題)」を購読するようなイメージで使えます。

記述例

  tasks:
    - name: Apache設定変更
      copy: src=httpd.conf dest=/etc/httpd/conf/httpd.conf
      notify: web services restart

    - name: SSL証明書更新
      copy: src=server.crt dest=/etc/pki/tls/certs/server.crt
      notify: web services restart

  handlers:
    - name: Restart Apache
      service: name=httpd state=restarted
      listen: web services restart

    - name: Restart Nginx
      service: name=nginx state=restarted
      listen: web services restart

解説:

  • notify にはハンドラー名ではなく、トピック名(web services restart)を指定します。
  • ハンドラー側で listen: トピック名 を記述します。
  • これにより、1つの通知で複数のハンドラー(ApacheとNginx)を同時に発火させることができます。

5. 【プロの技1】即時実行させる「flush_handlers」

先ほど「ハンドラーは最後に実行される」と言いましたが、これでは困るケースがあります。
例えば、「設定を変更して再起動した後、接続テストを行いたい」という場合です。

デフォルトの挙動だと、「設定変更 → 接続テスト(まだ再起動してないから古い設定のまま!) → 再起動」となってしまい、テストの意味がありません。
そこで、meta: flush_handlers を使って、溜まっているハンドラーを強制的に実行させます。

記述例

  tasks:
    - name: 設定変更
      template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
      notify: Restart Apache

    # ここで「溜まっているハンドラーを全部実行せよ!」と命令する
    - name: 今すぐ再起動を実行
      ansible.builtin.meta: flush_handlers

    - name: 接続テスト(新しい設定で動いているか確認)
      ansible.builtin.uri:
        url: http://localhost/
        status_code: 200

このテクニックは、CI/CDパイプラインや、複雑なデプロイフローを組む際に必須となります。


6. 【プロの技2】エラーでも実行させる「force_handlers」

もう一つ、プロが必ず知っておくべき設定があります。
通常、Playbookの途中でタスクがエラー(failed)になると、Ansibleはその時点で停止します。
その際、予約されていたハンドラーは実行されずに終わります。

これは非常に危険な状態です。

  1. 設定ファイルを書き換えた(notify 予約)。
  2. 次のタスクで予期せぬエラー発生! 停止!
  3. サーバーの状態:「設定ファイルは新しいのに、プロセスは古い設定で動いている」

この状態で誰かが手動で再起動したり、サーバーが落ちて自動復旧したりすると、書きかけの設定ファイルが読み込まれて起動しなくなる可能性があります。

これを防ぐために、「途中でエラーが起きても、予約されたハンドラーだけは意地でも実行する」設定、それが force_handlers: yes です。

記述例

---
- name: 安全なデプロイ
  hosts: all
  force_handlers: yes  # <-- Playbookの先頭にこれを書く!

  tasks:
    - name: 設定変更
      template: src=app.conf dest=/etc/app/app.conf
      notify: Restart App

    - name: わざと失敗するタスク
      command: /bin/false
      # ここで止まっても、Restart App は実行される!

  handlers:
    - name: Restart App
      service: name=app state=restarted

⚠️ プロの助言
本番環境向けのPlaybookでは、基本的に force_handlers: yes を有効にすることを強く推奨します。
「中途半端な状態で放置される」のが、システム運用において最も恐ろしいことだからです。


まとめ:Ansibleは「結果」にコミットする

お疲れ様でした!
第3回では、システムの整合性を保ちながら効率的に処理を行う「Handlers」について深く学びました。

今回の要点まとめ:

  • notify / handlers を使えば、変更があった時だけ処理を実行できる。
  • ハンドラーは基本的に「最後に1回だけ」実行される。
  • listen を使えば、複数のハンドラーをまとめて発火できる。
  • meta: flush_handlers で、即座に再起動を実行できる。
  • force_handlers: yes は、エラー時の不整合を防ぐ保険になる。
コウ君

「force_handlers」の話、ゾッとしました…。
設定ファイルだけ変わってて、再起動したら動かなくなるなんて、考えただけでも恐ろしいです。
これからは必ずこのオプションを入れるようにします!

さて、ここまでは「静的なファイル」をコピーして配置してきましたが、実務では「IPアドレスやホスト名を、ファイルの中に動的に埋め込みたい」ということが多々あります。
次回は、Python製の強力なテンプレートエンジンを使った、設定ファイルの動的生成について学びます。

次回、【第4回】テンプレート (Jinja2):設定ファイルを動的に生成する でお会いしましょう!

▼ Ansibleの実験場を作ろう ▼

再起動や設定変更を自由に試せる
「おすすめVPS」

VPSランキングを見る

自動化スキルを活かす
「ITエンジニア転職」

転職エージェントを見る

コメント