この記事では、AnsibleのJinja2フィルターの設計入門として、defaultフィルター・型変換・文字列操作・リスト操作・ternaryの各パターンを、実際の実行例とrole設計への組み込み方まで含めて解説します。実行環境はAnsible 2.17 / RHEL 9.4です。
この記事のポイント
・defaultフィルターで未定義変数を安全な既定値で処理できる
・int・bool・stringフィルターで型の不一致エラーを防げる
・select・map・combineでリスト・辞書を動的に変換する設計ができる
・ternaryフィルターで条件分岐をインラインに書きPlaybookを短くできる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜJinja2フィルターを覚えるとPlaybookの設計が変わるのか
Ansibleの変数は「文字列・数値・真偽値・リスト・辞書」として扱われます。しかし、インベントリファイルのINI形式から渡した値は基本的に文字列として受け取られます。これが型エラーの元凶になります。例えば次のPlaybookは実行するとエラーになります。
--- - name: ポート設定の確認 hosts: localhost vars: port: "8080" # INI形式インベントリから文字列として渡される tasks: - debug: msg: "次のポート番号: {{ port + 1 }}" # エラー: TypeError: can only concatenate str (not "int") to str
{{ port | int + 1 }} と書くだけで解決します。これがJinja2フィルターの役割です。Jinja2フィルターは
{{ 変数 | フィルター名 }} の形で変数を加工する仕組みで、「変数を受け取る段階で型変換・加工まで完結させる」という設計思想を実現します。複数のフィルターをパイプ | で連結でき、{{ port | int + 1 | string }} のように連鎖させることも可能です。Jinja2フィルターには、Pythonのjinja2ライブラリ由来のものとAnsible固有のものの2種類があります。Ansible固有のフィルター(
combine・dict2items・ternaryなど)はjinja2本体には存在せず、Ansibleを使う際にのみ利用できます。設計の観点で重要なのは「どの段階で型変換・加工するか」を意識することです。defaultフィルターで未定義変数を安全に処理する
Ansible roleの設計でまず覚えるべきフィルターがdefault です。呼び出し側がオプション変数を渡し忘れた場合でも、フォールバック値を返してPlaybookを安全に継続させます。# roles/webapp/tasks/main.yml --- - name: アプリのポートを確認 debug: msg: "使用ポート: {{ webapp_port | default(8080) }}"
# webapp_portを指定した場合 $ ansible-playbook -i hosts site.yml -e 'webapp_port=9000' TASK [アプリのポートを確認] ok: [web01] => { "msg": "使用ポート: 9000" } # webapp_portを指定しなかった場合 $ ansible-playbook -i hosts site.yml TASK [アプリのポートを確認] ok: [web01] => { "msg": "使用ポート: 8080" }
default フィルターの第2引数に true を渡すと、変数が定義されていても空文字・null・false・[] などのFalsy値の場合にデフォルト値を使います。# 未定義・空文字・falseのいずれでもデフォルト値8080を使う {{ webapp_port | default(8080, true) }}
defaults/main.yml ですべてのオプション変数にデフォルト値を宣言している場合は、default フィルターは通常不要です。default フィルターが真価を発揮するのは、「タスク内で動的に組み立てた値のフォールバック」や「外部変数ファイルから読み込んだ値に対する追加の防御」が必要な場面です。
Ansible実践ハンズオンの詳細を見る >>
型変換フィルター(int・float・bool・string)の使い分け
インベントリのINI形式は値をすべて文字列として渡します。算術演算・条件比較・モジュールの引数に渡す前に、型変換フィルターを通す習慣をつけると型エラーを防げます。# hosts(INI形式インベントリ) [webservers] web01 app_workers=4 webapp_port=8080 use_ssl=yes
app_workers・webapp_port・use_ssl はすべて文字列として渡されます。# tasks/main.yml --- - name: ワーカー数の計算 debug: msg: "総接続数上限: {{ app_workers | int * 100 }}" - name: SSL設定の判定 debug: msg: "SSL: {{ 'enabled' if use_ssl | bool else 'disabled' }}"
TASK [ワーカー数の計算] ok: [web01] => { "msg": "総接続数上限: 400" } TASK [SSL設定の判定] ok: [web01] => { "msg": "SSL: enabled" }
・
| int:文字列→整数変換。変換できない場合は 0 を返す・
| int(default=1):変換失敗時のデフォルト整数を指定可能・
| float:文字列→浮動小数点変換・
| bool:"true"/"yes"/"1"/1 → True、"false"/"no"/"0"/0 → False(大文字も可)・
| string:整数・リスト等を文字列に変換bool フィルターはYAMLの "yes" や "True" といった文字列を正しく真偽値に変換します。when: use_ssl と書いた場合、"yes" は文字列として評価されて常に真になりますが、when: use_ssl | bool と書くと正しく True として評価されます。文字列操作フィルター(upper・lower・replace・regex_replace)
ホスト名・環境名・ファイルパスを動的に生成するroleでは、文字列操作フィルターで命名規則を統一できます。--- - name: ログディレクトリのパスを生成 vars: app_name: "MyWebApp" environment: "Production" debug: msg: "ログ先: /var/log/{{ app_name | lower }}/{{ environment | lower }}" # 出力: "ログ先: /var/log/mywebapp/production"
# "prod"・"production"・"PROD" をすべて "production" に統一 {{ env_name | lower | regex_replace('^prod(uction)?$', 'production') }}
$ ansible -i hosts localhost -m debug \ -a "msg={{ 'prod' | lower | regex_replace('^prod(uction)?$', 'production') }}" ok: [localhost] => { "msg": "production" } $ ansible -i hosts localhost -m debug \ -a "msg={{ 'PROD' | lower | regex_replace('^prod(uction)?$', 'production') }}" ok: [localhost] => { "msg": "production" } $ ansible -i hosts localhost -m debug \ -a "msg={{ 'staging' | lower | regex_replace('^prod(uction)?$', 'production') }}" ok: [localhost] => { "msg": "staging" }
・
| upper:全文字を大文字・
| lower:全文字を小文字・
| capitalize:先頭文字のみ大文字・
| replace('old', 'new'):固定文字列の置換・
| regex_replace('正規表現', '置換文字列'):正規表現での置換・
| trim:前後の空白を削除・
| truncate(30):指定文字数で切り詰め(末尾に "..." が付く)フィルターをパイプで連結できるのがJinja2の強みです。
| lower | trim | regex_replace(...) のように、複数のフィルターを順番に適用できます。リスト・ディクショナリ操作(select・map・reject・combine)
複数ホストへの設定配布でリストや辞書を動的に加工するフィルターを活用すると、複雑な条件分岐をシンプルに書けます。select・reject フィルター: リストから条件に合う/合わない要素を抽出する
--- - name: 1000以上のポートのみ取得 vars: all_ports: [80, 443, 8080, 8443, 22, 3306] debug: msg: "{{ all_ports | select('ge', 1000) | list }}" # 出力: "[8080, 8443]"
ge はgreater than or equal(以上)を意味するJinja2のテスト名です。gt(超)・lt(未満)・le(以下)・eq(等しい)も使えます。map フィルター: リストの各要素を変換する
--- - name: サーバー名にドメインを付ける vars: servers: ["web01", "web02", "db01"] debug: msg: "{{ servers | map('regex_replace', '$', '.example.com') | list }}" # 出力: "['web01.example.com', 'web02.example.com', 'db01.example.com']"
defaults/main.yml の設定をユーザー側の変数で上書きするパターンに使います。--- - name: デフォルト設定とユーザー設定をマージ vars: default_config: port: 80 timeout: 30 debug: false user_config: port: 8080 debug: true debug: msg: "{{ default_config | combine(user_config) }}" # 出力: "{'port': 8080, 'timeout': 30, 'debug': True}" # user_configで上書きされたport・debugだけが変わり、timeoutはデフォルト値が維持される
--- - name: パッケージをバージョン指定でインストール vars: packages: nginx: "latest" php-fpm: "8.2" mariadb: "10.11" ansible.builtin.dnf: name: "{{ item.key }}" state: "{{ item.value }}" loop: "{{ packages | dict2items }}"
dict2items で [{'key': 'nginx', 'value': 'latest'}, ...] の形式に変換してから loop に渡すと、辞書をそのままループできます。ternaryと条件制御フィルターのパターン
ternary フィルターは三項演算子をJinja2で実現します。when がタスクの実行可否を制御するのに対して、ternary はタスクを実行しつつ引数の値だけを環境によって切り替える設計に使います。--- - name: ログレベルを環境別に設定 vars: is_production: true debug: msg: "ログレベル: {{ is_production | bool | ternary('warn', 'debug') }}" # 出力: "ログレベル: warn"(is_production が true なので)
ternary はテンプレートファイル(.j2)の中で特に使い勝手が良く、設定ファイルの値を環境によって切り替える場面に頻繁に登場します。# templates/nginx.conf.j2 worker_processes {{ ansible_processor_vcpus | default(1) }}; error_log /var/log/nginx/error.log {{ nginx_log_level | lower | default('warn') }}; gzip {{ nginx_enable_gzip | default(true) | bool | ternary('on', 'off') }};
defined テスト(フィルターではなくテスト)との組み合わせ: 変数が定義されているときだけ実行する設計--- # backup_enabledが未定義か false なら when を満たさずスキップ - name: バックアップ先を確認 debug: msg: "バックアップ先: {{ backup_path | default('/var/backup') }}" when: backup_enabled | default(false) | bool
backup_enabled が未定義の場合は false をデフォルト値として使い、bool で変換してから when で評価します。「未定義変数でのエラーを防ぎつつ、条件分岐も安全に書く」という2つの問題をフィルターの連鎖で解決しています。role設計でJinja2フィルターを活かす実践パターン
実際のroleでJinja2フィルターを組み合わせた設計例を示します。Webサーバー(Nginx)の設定を管理するroleです。roleのディレクトリ構成:
roles/ nginx/ defaults/ main.yml # デフォルト変数の宣言(型も明示) tasks/ main.yml # フィルターを使った処理 templates/ nginx.conf.j2 # テンプレート内フィルター
defaults/main.yml でデフォルト変数を宣言するとき、型が明確な値で初期化しておくと呼び出し側の混乱を防げます。# roles/nginx/defaults/main.yml --- nginx_port: 80 # int nginx_worker_processes: 2 # int nginx_enable_gzip: true # bool nginx_log_level: "warn" # string: debug/info/notice/warn/error/crit nginx_allowed_ips: [] # list(空=制限なし) nginx_server_name: "localhost" # string
tasks/main.yml で型変換とバリデーションを組み込む:# roles/nginx/tasks/main.yml --- - name: ポートをファイアウォールに登録 ansible.builtin.firewalld: port: "{{ nginx_port | int }}/tcp" permanent: true state: enabled - name: ワーカー数を物理コア数上限に調整 ansible.builtin.set_fact: nginx_workers_actual: "{{ [nginx_worker_processes | int, ansible_processor_vcpus | int] | min }}" - name: Nginx設定ファイルを展開 ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' notify: reload nginx
templates/nginx.conf.j2 でのフィルター使用:worker_processes {{ nginx_workers_actual | int }}; pid /run/nginx.pid; error_log /var/log/nginx/error.log {{ nginx_log_level | lower }}; events { worker_connections {{ nginx_worker_processes | int * 1024 }}; } http { gzip {{ nginx_enable_gzip | bool | ternary('on', 'off') }}; server { listen {{ nginx_port | int }}; server_name {{ nginx_server_name }}; {% if nginx_allowed_ips | length > 0 %} {% for ip in nginx_allowed_ips %} allow {{ ip }}; {% endfor %} deny all; {% endif %} } }
defaults/main.yml で型の正解を宣言 → tasks で型変換を挟んで安全に処理 → template でternaryを使って設定値を動的生成」という流れが、Jinja2フィルターを活かしたrole設計の基本パターンです。本記事のまとめ
Jinja2フィルターの主要パターン早見表です。| フィルター | 用途 | 使用例 |
|---|---|---|
default(値) |
未定義変数のフォールバック | {{ port | default(80) }} |
default(値, true) |
Falsy値もフォールバック対象にする | {{ port | default(80, true) }} |
int |
文字列→整数変換 | {{ app_workers | int * 2 }} |
bool |
文字列→真偽値変換 | {{ use_ssl | bool }} |
float |
文字列→浮動小数点変換 | {{ ratio | float }} |
string |
整数・リスト等→文字列変換 | {{ port | string }} |
lower |
全文字を小文字化 | {{ env_name | lower }} |
upper |
全文字を大文字化 | {{ env_name | upper }} |
replace('old', 'new') |
固定文字列の置換 | {{ name | replace('-', '_') }} |
regex_replace('rx', 'new') |
正規表現での置換 | {{ env | regex_replace('^prod$', 'production') }} |
trim |
前後の空白削除 | {{ value | trim }} |
select('ge', N) |
条件に合う要素を抽出 | {{ ports | select('ge', 1000) | list }} |
reject('search', 'rx') |
条件に合わない要素を抽出 | {{ hosts | reject('search', 'maint') | list }} |
map('フィルター') |
各要素を変換 | {{ names | map('lower') | list }} |
combine(dict2) |
辞書のマージ(後者優先) | {{ defaults | combine(overrides) }} |
dict2items |
辞書→ループ用リストに変換 | {{ pkgs | dict2items }} |
ternary(T値, F値) |
条件によって値を切り替え | {{ flag | bool | ternary('on', 'off') }} |
まず
default と int・bool の3つを覚え、実際のroleで使いながら replace・select・ternary へと少しずつ拡張していくのが、現場で使えるJinja2フィルター設計の定着方法です。Ansible実践ハンズオンの詳細を見る >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:AnsibleのLookupプラグイン設計入門|ファイル・環境変数・外部データを変数として動的取得する実践パターン
- この記事の属するカテゴリ:Ansibleへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます