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

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Ansible > AnsibleのJinja2テンプレートとhandler設計|設定ファイルの動的生成とサービス自動再起動の仕組み
「設定ファイルをサーバーごとに手作業でコピーしているが、環境ごとに内容を変えるのが面倒だ」
「Ansibleで設定ファイルを配布した後、サービスのreloadを毎回手動でやっている」

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

この記事では、Ansibleの templateモジュール(Jinja2テンプレートで設定ファイルを動的生成)と handler/notify(設定変更を検知してサービスを自動再起動)の仕組みを、httpd.confを題材に解説する。動作確認環境: RHEL 10 / Rocky Linux 9・Ansible 2.17。概念から実際のPlaybook設計まで順を追って説明する。

この記事のポイント

・templateモジュールはJinja2で変数・条件分岐を埋め込める設定ファイル配布モジュール
・copyとtemplateの違いはJinja2レンダリングの有無だけ
・handlerはnotify時のみPlay終了後に1回だけ実行される通知型タスク
・template + handlerで設定配布→変更検知→再起動を完全自動化できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も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の varsgroup_vars に定義した変数が展開される。ansible_hostname はAnsibleが自動収集するマジック変数(インベントリのホスト名)だ。実行時にはサーバーごとのホスト名に自動で置き換わる。

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

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

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

変数 enable_ssltrue の場合のみ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回だけ実行

Play途中でhandlerを強制実行したい場合は、タスクに ansible.builtin.meta: flush_handlers を挿入する。Apache再起動後にヘルスチェックタスクを実行するような依存関係のある処理では有効なテクニックだ。

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を実行できる(クリーンアップ処理などに活用する)。

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.ymlhost_vars/web01.yml に必要な変数を明示的に定義することが根本的な解決策だ。変数の優先順位はhost_vars > group_vars > defaults(Role)の順になる。

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

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

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

本記事のまとめ

やりたいこと 使う機能 ポイント
環境別設定ファイルを動的生成 templateモジュール + .j2ファイル Jinja2で変数・条件を埋め込む
変数が未定義の場合にデフォルト値を使う {{ 変数 | default(値) }} フィルター AnsibleUndefinedVariableを防ぐ
設定変更時だけサービスを再起動 notify + handler Play終了後に1回だけ実行
設定ファイルの構文を転送前にチェック templateモジュールのvalidate httpd -t -f でApache構文検証
複数タスクから同じhandlerを1回だけ呼ぶ 同名のnotifyを複数設定 Ansibleがキューで重複を自動排除
Ansibleのtemplateとhandlerを組み合わせることで、設定ファイルの配布から変更検知・サービス再起動までを一貫して自動化できる。copyモジュールで固定ファイルを配布しているPlaybookがある場合は、今回のパターンに置き換えることで環境差異の吸収と再起動タイミングの制御を同時に実現できる。

「template + handler」は構成管理の設計パターンとして覚えておく価値が高い。どのサービス(Apache・Postfix・Nginxなど)でも同じ構造で応用できる汎用パターンだからだ。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

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

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

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

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

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

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

登録10秒/合わなければ解除3秒 / 詳細はこちら

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

この記事を書いた人

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

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

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