【連載 第4回】テンプレート (Jinja2):設定ファイルを動的に生成する〜構成管理の真骨頂〜

「サーバーごとに設定ファイルを作って配置」なんて、ナンセンスです。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回(第3回)は、Handlersを使って「必要な時だけ再起動する」スマートな運用方法を学びました。

さて、今回はファイルの「中身」のお話です。
これまで copy モジュールを使って設定ファイルをサーバーに配布してきましたが、ここで一つ問題があります。

「サーバーごとに微妙に内容が違うファイルはどうすればいいの?」

  • 「データベースの接続先IPアドレスが、開発環境と本番環境で違う」
  • 「Webサーバーのパフォーマンス設定を、マシンのCPUコア数に合わせて自動調整したい」
  • 「許可するクライアントIPのリストを、変数から読み込んでずらっと書き出したい」

これらを解決するために、サーバーごとに別々のファイルを用意して copy するのは最悪手です。
正解は、「1つの雛形(テンプレート)から、環境に合わせたファイルを自動生成する」ことです。

コウ君

先生! まさにそれをやりたかったんです!
今は httpd.conf.devhttpd.conf.prod みたいにファイルを分けてるんですが、共通部分の設定変更を両方に書くのが面倒で…。
うっかり片方だけ直し忘れて事故ったこともあります(泣)

リナックス先生

それは大変だったわね、コウ君。
Ansibleには、Python製の強力なテンプレートエンジン「Jinja2(ジンジャーツー)」が内蔵されているの。
これを使えば、変数やFactsを埋め込んで、設定ファイルを魔法のように自動生成できるわ。
もうコピペ地獄とはサヨナラよ!

本記事では、Ansibleの最強機能の一つである template モジュールと、Jinja2記法を使った動的な設定ファイル生成テクニックを徹底解説します。

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


1. copyモジュールとtemplateモジュールの違い

まずは基本の整理です。
ファイルを配布するモジュールには、主に以下の2つがあります。

モジュール名 特徴 使いどころ
copy ファイルをそのまま(バイナリレベルで)コピーする。
変数の展開などは行われない。
SSL証明書、バイナリファイル、変更不要な静的ファイルなど。
template ファイルを「Jinja2テンプレート」として解釈し、変数を展開してから配置する。
拡張子には慣習として .j2 をつける。
各種設定ファイル(conf, ini, yaml)、HTML、シェルスクリプトなど。

設定ファイル(Configuration)を管理する場合は、基本的に template モジュールを使うのがAnsibleのベストプラクティスです。


2. Jinja2テンプレートの基本:変数の埋め込み

実際にNginxの設定ファイルを作ってみましょう。
ポート番号とサーバー名を変数化します。

2-1. 変数の定義 (Playbook)

まずはPlaybook側で変数を定義します。

---
- name: Templateモジュールの練習
  hosts: all
  vars:
    # ここで値を定義(実際はgroup_varsなどに書くことが多い)
    http_port: 8080
    server_name: "example.com"

2-2. テンプレートファイルの作成 (.j2)

設定ファイルの雛形を作成します。拡張子は .conf.j2 とします。
変数を埋め込みたい場所を {{ }} で囲みます。

ファイル名:nginx.conf.j2

server {
    listen       {{ http_port }};
    server_name  {{ server_name }};

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

2-3. Playbookでの実行

template モジュールを使って配置します。

  tasks:
    - name: Nginxの設定ファイルを配置
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/conf.d/default.conf
      notify: Restart Nginx

これを実行すると、サーバー上には以下のようなファイルが生成されます。

server {
    listen       8080;
    server_name  example.com;
    ...
}

変数を書き換えるだけで設定変更ができるようになりました!


3. 制御構文:ファイル内でループや条件分岐を使う

Jinja2の真骨頂はここからです。
設定ファイルの中で、プログラミング言語のようにループ(繰り返し)や条件分岐が使えます。

3-1. ループ (for文)

「許可するIPアドレスのリスト」など、行数が可変の設定を出力する場合に使います。
構文は {% for item in list %} ... {% endfor %} です。

Playbook (vars):

    allowed_ips:
      - 192.168.1.10
      - 192.168.1.11
      - 10.0.0.5

Template (nginx.conf.j2):

# アクセス制限設定
{% for ip in allowed_ips %}
allow {{ ip }};
{% endfor %}
deny all;

生成結果:

# アクセス制限設定
allow 192.168.1.10;
allow 192.168.1.11;
allow 10.0.0.5;
deny all;

3-2. 条件分岐 (if文)

「本番環境だけSSLの設定を入れたい」「特定のフラグが立っている時だけログを出力したい」といった場合に使います。
構文は {% if condition %} ... {% endif %} です。

Playbook (vars):

    ssl_enabled: true

Template (nginx.conf.j2):

{% if ssl_enabled %}
    listen 443 ssl;
    ssl_certificate /etc/nginx/cert.pem;
    ssl_certificate_key /etc/nginx/cert.key;
{% else %}
    listen 80;
{% endif %}

💡 プロのノウハウ:空白と改行の制御
Jinja2のタグ {% ... %} は、デフォルトでは「空行」として出力に残ってしまうことがあります。
これを防ぐために、{%- ... -%} のようにマイナス記号を付けると、前後の空白や改行を除去できます。
例:{%- if ssl_enabled -%}
きれいな設定ファイルを生成したい時の必須テクニックです。


4. フィルター (Filters):変数を加工して出力する

「変数が未定義だった場合にデフォルト値を使いたい」「リストをカンマ区切りの文字列にしたい」といった場合、Jinja2の「フィルター」を使います。
変数の後ろにパイプ | を繋いで記述します。

よく使うフィルター一覧

フィルター 説明
default 変数が定義されていない場合のデフォルト値を指定 {{ my_port | default(80) }}
upper / lower 大文字 / 小文字に変換 {{ env_name | upper }}
join リストを結合して文字列にする {{ ip_list | join(',') }}
to_nice_yaml 変数をYAML形式の文字列に変換(設定ファイル生成に便利) {{ my_dict | to_nice_yaml }}
b64encode Base64エンコードする(Secretなどに使用) {{ password | b64encode }}

使用例:SSH設定ファイル

# ポート設定(変数がなければ22を使う)
Port {{ ssh_port | default(22) }}

# 許可ユーザー(リストをスペース区切りで展開)
AllowUsers {{ allowed_users | join(' ') }}

5. 【プロのノウハウ】ansible_managed で手動変更を防ぐ

構成管理あるあるトラブル第1位。
「誰かが手動で設定ファイルを書き換えてしまい、Ansible実行時に先祖返り(上書き)して障害発生」

これを防ぐために、生成されるファイルの先頭に必ず「このファイルはAnsible管理下です。手動で編集しないでください」という警告文を入れるのが鉄則です。
Ansibleには、このための特別な変数 {{ ansible_managed }} が用意されています。

テンプレートの先頭に記述

# {{ ansible_managed }}
#
# このファイルはAnsibleによって自動生成されています。
# 手動での変更は次回のデプロイ時に失われます。

server {
    ...
}

ansible.cfg でこの変数の内容をカスタマイズすることも可能です(更新日時や実行ユーザー名を含めるなど)。
この一行があるだけで、運用メンバーへの注意喚起になり、事故を未然に防ぐことができます。


6. 【応用】Factsを使ってチューニングを自動化する

第1回で学んだ「Facts(自動収集変数)」とテンプレートを組み合わせると、「サーバーのスペックに応じた自動チューニング」が可能になります。
これがIaCの醍醐味です。

例:Nginxのワーカープロセス数自動設定

Nginxの worker_processes は、通常CPUコア数と同じ値にするのが推奨されています。
Facts変数 ansible_processor_vcpus を使えば、どんなスペックのサーバーにデプロイしても最適な値が設定されます。

nginx.conf.j2:

# CPUコア数に合わせてワーカー数を自動設定
worker_processes {{ ansible_processor_vcpus }};

events {
    # メモリ量に応じて接続数を調整(例:メモリ1GBあたり1024接続)
    worker_connections {{ (ansible_memtotal_mb / 1024 * 1024) | int }};
}

解説:

  • ansible_processor_vcpus:CPUコア数が自動で入ります。
  • ansible_memtotal_mb:メモリ総量(MB)が入ります。
  • Jinja2の中で四則演算(/, *)も可能です。
  • | int フィルターで整数化しています。

これにより、4コアのサーバーなら 4、8コアなら 8 と、ファイルの中身が自動で書き換わります。
「ハイスペックな本番機」と「低スペックな検証機」で同じPlaybookを使っても、それぞれに最適な設定が適用されるわけです。


まとめ:インフラを「コード」として書き下そう

お疲れ様でした!
第4回では、設定ファイルを動的に生成する「Jinja2テンプレート」について学びました。

今回の要点まとめ:

  • 設定ファイルの管理には template モジュール を使う。
  • {{ var }} で変数を埋め込み、{% for %} / {% if %} でロジックを記述できる。
  • フィルターを使って、デフォルト値設定やフォーマット変換を行う。
  • {{ ansible_managed }} ヘッダーを付けて、手動変更を抑止する。
  • Facts と組み合わせれば、サーバースペックに応じた自動チューニングができる。
コウ君

Factsとの組み合わせ、感動しました!
今までExcelの設計書を見ながら「このサーバーはメモリが多いからバッファを増やして…」って手計算してたのが馬鹿らしくなりました。
これぞ「Infrastructure as Code」って感じですね!

Playbookが高度になってくると、今度は「ファイルが長すぎて読みづらい」「他のプロジェクトでも同じ処理を使い回したい」という悩みが出てきます。
次回は、Playbookを機能ごとに分割・部品化して管理する仕組み、「ロール (Roles)」について学びます。

次回、【第5回】ロール (Roles) の設計:Playbookを部品化して再利用する でお会いしましょう!

▼ 自動化スキルを試すなら ▼

スペックの異なるサーバーで実験
「おすすめVPS」

VPSランキングを見る

IaCエンジニアへの第一歩
「ITエンジニア転職」

転職エージェントを見る

コメント