AnsibleのJinja2テンプレートとhandler設計|設定ファイルの動的生成とサービス自動再起動の仕組み

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Ansible > AnsibleのJinja2テンプレートとhandler設計|設定ファイルの動的生成とサービス自動再起動の仕組み
「設定ファイルをサーバーごとに手作業でコピーしているが、環境ごとに内容を変えるのが面倒だ」
「Ansibleで設定ファイルを配布した後、サービスのreloadを毎回手動でやっている」

こんな悩みは、Ansibleを使い始めたエンジニアが必ずぶつかる壁だ。copyモジュールで静的ファイルを配布するだけでは、環境ごとの差異(ホスト名・ポート番号・SSL設定の有無など)を吸収できない。設定変更のたびに手動でサービスを再起動するのは自動化の恩恵を半分しか受けていない状態だ。

この記事では、Ansibleの templateモジュール(Jinja2テンプレートで設定ファイルを動的生成)と handler/notify(設定変更を検知してサービスを自動再起動)の仕組みを、httpd.confを題材に解説する。さらに flush_handlers(handler即時実行)・listen(疎結合な通知設計)・handler連鎖(Ansible 2.9以降)まで踏み込み、実務で使えるPlaybook設計の全体像を網羅する。動作確認環境: RHEL 10 / Rocky Linux 9・Ansible 2.17。概念から実際のPlaybook設計まで順を追って説明する。

この記事のポイント

・templateモジュールはJinja2で変数・条件分岐を埋め込める設定ファイル配布モジュール
・handlerはnotify時のみPlay終了後に1回だけ実行される通知型タスク
・flush_handlersでhandlerを即時実行でき、後続taskが再起動後サービスに依存する場合に使う
・listenで複数taskから1つのhandlerを疎結合に呼び出せる
・handler連鎖(Ansible 2.9以降)でhandlerが別のhandlerをnotifyできる


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

copyモジュールではなくtemplateモジュールを使うべき理由

Ansibleで設定ファイルを配布するとき、最初はcopyモジュールを使いがちだ。copyモジュールはローカルのファイルをそのままリモートサーバーへ転送する。シンプルで理解しやすいが、「環境ごとに内容を変えたい」という要件が出た途端に限界が来る。

たとえば、次のような状況を考えてほしい。

・開発環境のhttpd.conf: ServerName dev01.example.com
・本番環境のhttpd.conf: ServerName www.example.com

この差異をcopyモジュールで管理しようとすると、ファイルを環境の数だけ用意してwhen条件で分岐させるか、変数ファイルで制御するか、いずれにせよ構成が複雑になっていく。そこで登場するのがtemplateモジュールだ。

copyとtemplateの違いは1点だけ

両モジュールの機能はほぼ同じ。決定的な違いは Jinja2レンダリングの有無だ。

モジュール 転送方法 Jinja2変数埋め込み 用途
copy バイナリそのまま転送 不可 画像・バイナリ・固定テキスト
template レンダリング後に転送 可能(.j2ファイル) 環境依存の設定ファイル
templateモジュールでは拡張子 .j2 のJinja2テンプレートファイルを用意する。Ansibleは実行時にそのファイルをレンダリング(変数展開・条件分岐の解決)してからリモートサーバーへ転送する。テンプレートファイルはAnsibleコントロールノード(自分のPC)側に置き、生成済みのファイルをリモートへ送る、という流れだ。

この仕組みにより「1つのテンプレートファイルから複数環境の設定を生成する」ことが可能になる。

Jinja2テンプレートの基本構文|変数・条件分岐・フィルターを使いこなす

Jinja2はPython製のテンプレートエンジンで、AnsibleはこれをPlaybook変数と統合している。設定ファイル(.j2)に以下の構文を埋め込むことで動的な内容を生成できる。

1. 変数の埋め込み({{ }})

最も基本の構文だ。二重波括弧の中にPlaybook変数名を書く。

# httpd.conf.j2(変数展開の例) ServerName {{ ansible_hostname }} ServerAdmin {{ server_admin }} Listen {{ http_port }}

Playbookの vars や group_vars に定義した変数が展開される。ansible_hostname はAnsibleが自動収集するマジック変数(インベントリのホスト名)だ。実行時にはサーバーごとのホスト名に自動で置き換わる。

2. 条件分岐({% if %})

環境によって設定内容を切り替えたいときに使う。

# SSL設定を本番環境のみ有効化する例 {% if enable_ssl | default(false) %} SSLEngine on SSLCertificateFile {{ ssl_cert_path }} SSLCertificateKeyFile {{ ssl_key_path }} {% endif %}

変数 enable_ssl が true の場合のみSSL設定ブロックが生成される。| default(false) はフィルター(次節参照)でデフォルト値を設定している。開発環境ではSSLを無効のまま、本番環境ではgroup_varsで enable_ssl: true を設定する、という使い方が典型的だ。

3. フィルター(|)

変数をパイプ(|)でつないで加工できる。よく使うフィルターを示す。

・{{ http_port | default(80) }}: 変数が未定義なら80を使う
・{{ server_name | upper }}: 大文字に変換
・{{ items | join(',') }}: リストをカンマ区切りの文字列に変換
・{{ path | basename }}: パスからファイル名を取り出す

特に | default() は必須のフィルターだ。変数が未定義のままテンプレートに記述すると実行時にエラーになる(AnsibleUndefinedVariable)。デフォルト値を設定しておけばグループ全体で安全に使いまわせる。

templateモジュールでhttpd.confを動的生成する実践例

実際のディレクトリ構成から、Playbook・テンプレートの記述、実行結果まで順を追って説明する。

1. ディレクトリ構成

site.yml inventory/ prod/ ← 本番環境インベントリ dev/ ← 開発環境インベントリ group_vars/ webservers.yml ← 共通変数 prod.yml ← 本番環境変数(enable_ssl: true等) roles/ apache/ tasks/ main.yml templates/ httpd.conf.j2 ← Jinja2テンプレート handlers/ main.yml

2. Jinja2テンプレート(roles/apache/templates/httpd.conf.j2)

# httpd.conf.j2 — Jinja2変数・条件分岐は実行時に展開される ServerName {{ ansible_hostname }} ServerAdmin {{ server_admin | default('webmaster@localhost') }} Listen {{ http_port | default(80) }} DocumentRoot {{ document_root | default('/var/www/html') }} Options Indexes FollowSymLinks AllowOverride {{ allow_override | default('None') }} Require all granted {% if enable_ssl | default(false) %} Listen 443 ServerName {{ ansible_fqdn }} SSLEngine on SSLCertificateFile {{ ssl_cert_path }} SSLCertificateKeyFile {{ ssl_key_path }} {% endif %}

3. タスクファイル(roles/apache/tasks/main.yml)

--- - name: httpd.confをテンプレートから配布する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf owner: root group: root mode: '0644' validate: '/usr/sbin/httpd -t -f %s' notify: Reload Apache

validate パラメーターに注目してほしい。httpd -t -f は設定ファイルの構文チェックを行うコマンドで、これが失敗するとAnsibleはファイルを転送しない。誤った設定を本番サーバーに配布するリスクを防ぐ重要なオプションだ。notify: Reload Apache は次章のhandlerを呼び出す。

Apacheの設定項目の詳細(タイムアウト値やKeepAliveの調整など)については、Apache タイムアウト設定の詳細も参考にしてほしい。

4. 実行結果(RHEL 10 / Rocky Linux 9 での出力例)

本番サーバー(web01.example.com)で初回適用した際の出力を示す。

# ansible-playbook site.yml -i inventory/prod 実行時の出力 TASK [apache : httpd.confをテンプレートから配布する] ** changed: [web01.example.com] RUNNING HANDLERS ************************************ TASK [apache : Reload Apache] *********************** changed: [web01.example.com] PLAY RECAP ****************************************** web01.example.com : ok=4 changed=2 unreachable=0 failed=0

2回目以降、httpd.confの内容に変更がなければ changed: [web01.example.com] が ok: [web01.example.com] に変わり、handlerは呼ばれない。これがAnsibleの冪等性(べき等性)だ。同じPlaybookを何度実行しても、変更がなければサービスへの影響はゼロになる。

templateとhandlerを組み合わせたPlaybook設計を実機で体験したい方は、>> Ansibleハンズオン講座の詳細を見る をご覧ください。

handlerとnotifyの仕組み|設定変更時だけサービスを再起動する

handlerはAnsibleにおける「通知受信型のタスク」だ。通常のタスクとの違いを理解することが、Playbook設計の品質を大きく左右する。

1. handlerが解決する問題

設定ファイルを変更した後は必ずサービスをreloadまたはrestartしなければならない。しかし通常のタスクとしてサービス再起動を毎回書くと、2つの問題が発生する。

・不必要なサービス再起動: 設定ファイルに変更がなくてもサービスが再起動される。本番環境では瞬断リスクがある
・重複再起動: httpd.confとssl.confを別タスクで配布した場合、サービスが2回再起動される

handlerはこの両方を解決する。「changedになった時だけ」「Play終了後に1回だけ」実行される点が通常のタスクとの決定的な違いだ。

2. handlerの書き方(roles/apache/handlers/main.yml)

--- - name: Reload Apache ansible.builtin.service: name: httpd state: reloaded - name: Restart Apache ansible.builtin.service: name: httpd state: restarted

handlerは handlers/main.yml(Role構成の場合)またはPlaybook内の handlers: セクションに定義する。name がhandlerの識別子で、タスク側の notify: と完全一致させる必要がある(大文字・小文字・スペースを含む)。

3. 複数タスクから同じhandlerをnotifyする

--- # httpd.confとssl.confを両方配布するタスク - name: httpd.confを配布する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf validate: '/usr/sbin/httpd -t -f %s' notify: Reload Apache - name: ssl.confを配布する ansible.builtin.template: src: ssl.conf.j2 dest: /etc/httpd/conf.d/ssl.conf notify: Reload Apache

2つのタスクが両方 changed になっても、Reload Apache handlerはPlay終了時に1回だけ実行される。Ansibleが重複をキューで自動排除するためだ。

4. handlerが実行されるタイミング(実行フロー)

Ansibleのhandlerは通常、Playの全タスク終了後(Play recapの直前)に実行される。実行フローを整理すると次のようになる。

・タスク1: httpd.conf配布 → changed → 「Reload Apache」をキューに追加
・タスク2: ssl.conf配布 → changed → 「Reload Apache」をキューに追加(重複は自動排除)
・タスク3: ファイアウォール設定 → ok(変更なし、notifyは発火しない)
・(全タスク終了)
・RUNNING HANDLERS: 「Reload Apache」を1回だけ実行

5. flush_handlersでhandlerをその場で実行する

Play末尾まで待たずに途中でhandlerを実行させたい場面がある。

・後続タスクが再起動後のサービスに依存する: Apacheを再起動してからHTTPヘルスチェックを実行したい
・pre_tasksとtasksの間でhandlerを実行したい: OSの設定を反映してからアプリをインストールしたい
・複数のPlayに分割したPlaybookで前Playのhandlerを確実に終わらせたい: handlerはPlay内でしか持ち越されないため

meta: flush_handlers をタスクとして記述すると、その時点で通知済みの全handlerが即時実行される。

- hosts: webservers tasks: - name: httpd.confを配布する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf validate: '/usr/sbin/httpd -t -f %s' notify: Reload Apache - name: ssl.confを配布する ansible.builtin.template: src: ssl.conf.j2 dest: /etc/httpd/conf.d/ssl.conf notify: Reload Apache # ここでhandlerを即時実行する - name: Flush handlers before health check ansible.builtin.meta: flush_handlers # この時点ではApacheが再起動済み - name: Check Apache health endpoint ansible.builtin.uri: url: http://localhost/health status_code: 200 timeout: 10 handlers: - name: Reload Apache ansible.builtin.service: name: httpd state: reloaded

meta: flush_handlers の実行後、Reload Apache は通知済みリストから消える。Play末尾での再実行はない。

6. listenで複数タスクから同一handlerを呼び出す

通常のnotifyはhandler名を直接指定する。Role間にまたがる設計ではhandler名がRole間の暗黙の依存になりやすい。listen を使うと、handler側にトピック名を持たせることができ、タスク側はトピック名だけを通知するだけで済む。

tasks: - name: httpd.confを配布する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: apache config changed # トピック名を通知 - name: ssl.confを配布する ansible.builtin.template: src: ssl.conf.j2 dest: /etc/httpd/conf.d/ssl.conf notify: apache config changed # 同じトピック名 handlers: - name: Validate Apache config ansible.builtin.command: /usr/sbin/httpd -t listen: apache config changed # トピックを購読 - name: Reload Apache ansible.builtin.service: name: httpd state: reloaded listen: apache config changed # 同じトピックを購読

apache config changed トピックに通知が来ると、Validate Apache config と Reload Apache の両方が handlers:セクションに記述した順序 で実行される。構文チェックが先に走り、問題があればreloadを止めることができる。

listenを使った設計の利点をまとめる。

・handler名の変更がタスクに波及しない: handler名を変えてもタスク側の修正が不要
・1つの通知で複数のhandlerを順序付きで起動できる: 構文チェック→reloadを1つのnotifyで実現できる
・Role間の結合度を下げられる: Roleの境界を越えてトピックを共有でき、疎結合な設計になる

7. handler連鎖(Ansible 2.9以降)|handlerが別のhandlerを呼び出す

Ansible 2.9以降、handlerが別のhandlerをnotifyする「handler連鎖」が公式サポートされている。再起動後にヘルスチェックを走らせるといった、2段階の後処理を1つのnotifyでまとめて記述できる。

handlers: - name: Restart Apache ansible.builtin.service: name: httpd state: restarted notify: Verify Apache Health # handler連鎖:下流handlerを呼び出す - name: Verify Apache Health ansible.builtin.uri: url: http://127.0.0.1/health status_code: 200 retries: 3 delay: 5

注意点: handler連鎖で呼び出される下流のhandler(Verify Apache Health)もPlay末尾で実行される。notifyが書かれたhandlerの直後に即時実行されるわけではない点は通常のhandlerと同じだ。多段連鎖はデバッグが難しくなるため、実務では2段以上の連鎖は最小限にとどめることを推奨する。

Postfix(Postfix の基本設定の解説)のmain.cfなど、設定ファイルを持つ他のサービスでも全く同じtemplateとhandlerのパターンが使える。

よくある失敗パターンとトラブルシュート

1. handlerが実行されない

症状: タスクが changed になっているのに RUNNING HANDLERS が出ない。

【重要】原因①: notifyとhandler名が一致していない
タスク側の notify とhandler側の name は文字列の完全一致が必要だ。大文字・小文字・スペースの違いも区別される。この不一致がhandlerが動かない最多原因なので、名前のコピーペーストミスに注意してほしい。

# NG: notify名とhandler名が不一致(大文字・小文字が異なる) notify: reload apache handlers: - name: Reload Apache # OK: 完全一致 notify: Reload Apache handlers: - name: Reload Apache

原因②: --check モードで実行している
--check(ドライラン)モードでは実際の変更が発生しないため、handlerも実行されない。動作確認は通常実行で行うこと。

原因③: Playbookがエラーで中断した
タスクがエラーで中断すると、その後のhandlerは実行されない。--force-handlers オプションを使うと、エラー発生時でもhandlerを実行できる(クリーンアップ処理などに活用する)。

原因④: タスクがchangedでなくokだった
notifyはタスクが changed を返したときだけ有効になる。設定ファイルに変更がない場合、template は ok を返し、notifyは発動しない。意図的にhandlerを起動したい場合は changed_when: true を設定することで、強制的に changed 扱いにできる。

# 常にchangedとして扱い、notifyを発動させる例 - name: 設定反映スクリプトを実行する ansible.builtin.command: /opt/app/bin/reload-config changed_when: true notify: Restart Apache

2. テンプレートで「undefined variable」エラーが出る

症状:

fatal: [web01.example.com]: FAILED! => { "msg": "AnsibleUndefinedVariable: 'ssl_cert_path' is undefined" }

対処法①: defaultフィルターでデフォルト値を設定する

# テンプレート内でデフォルト値を指定する SSLCertificateFile {{ ssl_cert_path | default('/etc/pki/tls/certs/localhost.crt') }}

対処法②: 条件分岐で変数定義チェックを行う

# 変数が定義されている場合のみSSL設定を出力する {% if ssl_cert_path is defined %} SSLCertificateFile {{ ssl_cert_path }} {% endif %}

対処法③: group_vars / host_varsで変数を定義する
そもそも変数を定義し忘れているケースが多い。group_vars/webservers.yml や host_vars/web01.yml に必要な変数を明示的に定義することが根本的な解決策だ。変数の優先順位はhost_vars > group_vars > defaults(Role)の順になる。

3. validateエラーで設定ファイルが配布されない

症状: templateモジュールに validate を設定した際に構文エラーが検出され、ファイル転送がブロックされる。

これは 正常な動作だ。validate は誤った設定を本番サーバーに送らないための安全装置として機能している。エラーメッセージに従って .j2 テンプレートを修正し、正しい設定ファイルが生成されることを確認してから再実行すること。

4. タスク失敗後もhandlerを確実に実行したい(force_handlers)

デフォルトでは、Playbookのタスクが失敗するとその時点でPlay実行が止まり、通知済みのhandlerも実行されない。クリーンアップやアラート通知のhandlerを確実に動かしたい場合は force_handlers: true をPlay宣言に追加する。

- hosts: appservers force_handlers: true # タスク失敗時もhandlerを実行する tasks: - name: 設定ファイルを配布する ansible.builtin.template: src: app.conf.j2 dest: /etc/app/app.conf notify: Notify deploy result - name: データベースマイグレーションを実行する ansible.builtin.command: /opt/app/bin/migrate.sh # このタスクが失敗してもhandlerが実行される handlers: - name: Notify deploy result ansible.builtin.uri: url: https://hooks.example.internal/notify method: POST body_format: json body: text: "Deploy finished on {{ inventory_hostname }}"

force_handlers: true はPlay単位での指定を推奨する。ansible.cfgに force_handlers = True と書いて全Playbookのデフォルトにもできるが、予期しないhandler実行が増えるリスクがあるため、必要なPlayに限定するのが安全だ。

5. handler内でregister変数を使う

症状: handlerにregisterを書いたが、後続のタスクから変数を参照できない。

handlerのタスク結果をregisterで受け取っても、後続のtaskから参照できない。handlerはPlay末尾で実行されるため、タスクのループ内で結果を使い回すような処理には向いていない。こうした処理はtask側にインラインで書くか、別のPlayに分離するのが適切だ。

# NG: handlerでregisterしても後続taskから参照できない handlers: - name: Check service status ansible.builtin.command: systemctl is-active httpd register: service_result # この変数は後続taskから見えない # OK: task側でregisterを使い、handlerは再起動のみに限定する tasks: - name: httpd.confを配布する ansible.builtin.template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: Reload Apache register: template_result - name: 設定変更の結果を確認する ansible.builtin.debug: var: template_result.changed

6. 複数ホストでhandlerの挙動を誤解する

症状: web01でchangedが出たのに、web02のApacheが再起動されない。

handlerはホストごとに独立して管理される。web01でchangedが出てもweb02のhandlerは呼ばれない。「1台でも設定変更があったら全台一斉に再起動したい」という要求がある場合は、handlerではなく delegate_to や run_once を組み合わせた別の設計が必要だ。

# 全ホストへの適用完了後に1回だけ実行したい場合の例 handlers: - name: Notify all done ansible.builtin.uri: url: https://hooks.example.internal/notify method: POST body_format: json body: text: "Deploy complete" run_once: true # 1ホストでchangedが出た時点で1回だけ実行 delegate_to: localhost

本記事のまとめ

やりたいこと 使う機能 ポイント
環境別設定ファイルを動的生成 templateモジュール + .j2ファイル Jinja2で変数・条件を埋め込む
変数が未定義の場合にデフォルト値を使う {{ 変数 | default(値) }} フィルター AnsibleUndefinedVariableを防ぐ
設定変更時だけサービスを再起動 notify + handler Play終了後に1回だけ実行
設定ファイルの構文を転送前にチェック templateモジュールのvalidate httpd -t -f でApache構文検証
複数タスクから同じhandlerを1回だけ呼ぶ 同名のnotifyを複数設定 Ansibleがキューで重複を自動排除
後続タスクの前にhandlerを即時実行する meta: flush_handlers ヘルスチェック前のサービス再起動に使う
複数タスクから1つのhandlerを疎結合に呼ぶ handlerのlistenトピック handler名変更がタスクに波及しない
再起動後にヘルスチェックを自動実行する handler連鎖(handler内のnotify) Ansible 2.9以降。多段は最小限に
タスク失敗時もhandlerを必ず実行する Play宣言にforce_handlers: true クリーンアップ・通知処理に活用
Ansibleのtemplateとhandlerを組み合わせることで、設定ファイルの配布から変更検知・サービス再起動までを一貫して自動化できる。copyモジュールで固定ファイルを配布しているPlaybookがある場合は、今回のパターンに置き換えることで環境差異の吸収と再起動タイミングの制御を同時に実現できる。

さらに flush_handlers と listen を組み合わせ、Ansible 2.9以降のhandler連鎖も活用することで、再起動タイミングの制御と疎結合な設計が可能になる。どのサービス(Apache・Postfix・Nginxなど)でも同じ構造で応用できる汎用パターンとして覚えておいてほしい。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

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

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

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

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

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

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

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

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

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。