【無料ツール】画像をPDFに変換するPhotoPDF Appを公開しました!

【Ansible応用講座 第2回】ロジックを極める。Loop制御、Block例外処理、Jinja2フィルタの魔術

Playbookは、ただの「手順書」から「プログラム」へ進化する。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回は、Dynamic Inventoryを使って、クラウド上のサーバー情報を自動的に取得する方法を学びました。

しかし、自動取得した情報は、往々にして「複雑な構造(ネストしたJSON)」をしています。
「取得した全サーバーの中から、”Status=Running” のサーバーだけを抜き出して、そのプライベートIPリストを作り、設定ファイルに書き込みたい」
…そんな時、あなたは手作業でコピペしますか? それとも、スマートなフィルタ処理を書きますか?

コウ君

先生、前回AWSから取得したサーバーリスト、便利なんですけど情報量が多すぎて…。
欲しいのはIPアドレスだけなのに、タグとかIDとか全部ついてきて、どうやって取り出せばいいか分かりません。
あと、ループの中でさらにループしたい時とか、item が被っちゃってわけわからなくなります!

リナックス先生

それはAnsibleの「ロジック」と「データ加工」の知識が足りていない証拠ね。
Ansibleは単にモジュールを呼ぶだけじゃないわ。Jinja2フィルタを使いこなせば、Pythonコードを書くのと同じくらい自由自在にデータを加工できるの。
今回は、Ansibleを「プログラミング言語」として使いこなすための、深遠なるテクニックを伝授するわ!

本記事では、AlmaLinux 9 をベースに、ループ処理の高度な制御、エラーハンドリングの応用、そしてエンジニアを最も悩ませる(そして最も強力な)Jinja2フィルタの活用法について徹底解説します。

🚀 Ansible応用講座(全8回)

現在地:【第2回】ロジックを極める。Loop制御、Block例外処理、Jinja2フィルタの魔術

  • 【第1回】静的ファイルは捨てろ。Dynamic InventoryによるAWS/クラウド環境の自動追従
  • 【第2回】ロジックを極める。Loop制御、Block例外処理、Jinja2フィルタの魔術
  • 【第3回】爆速化チューニング。Forks、Pipelining、Mitogenによるパフォーマンス改善
  • 【第4回】標準機能で足りないなら。PythonによるCustom Module開発入門
  • 【第5回】品質を保証する。MoleculeによるRoleの自動テストとCI連携
  • 【第6回】Windowsも支配する。WinRM接続とPowerShell操作の完全ガイド
  • 【第7回】ネットワーク機器の自動化。Cisco/Juniperスイッチの設定管理
  • 【第8回】新時代の実行環境。Ansible Builder/RunnerによるExecution Environment (EE)

※入門編の復習はこちら:【入門 第3回】Playbookの基礎。YAMLの作法と「モジュール」


第1章:ループの進化論 (loop vs with_*)

Ansibleのループといえば with_items が有名ですが、現在は loop キーワードの使用が推奨されています。
単に書き方が変わっただけでなく、制御の幅が広がっています。

1. loop_control で変数を支配する

標準のループでは、取り出した要素は自動的に item という変数に入ります。
しかし、二重ループ(ネスト)する場合、内側のループでも item を使うと、外側の item が上書きされてしまいます。

loop_control を使えば、変数名を自由に変更できます。

- name: Create users and assign groups
  ansible.builtin.user:
    name: "{{ user_item.name }}"
    groups: "{{ group_item }}"
    append: yes
  loop: "{{ users }}"
  loop_control:
    loop_var: user_item  # 変数名を 'item' から 'user_item' に変更
  
  # ここで内側のループなどを書く場合、名前が被らない!

2. インデックス(連番)の取得

「1つ目のサーバーはmaster、2つ目以降はslave」のように、順番に応じた処理をしたい場合、index_var が便利です。

- name: List servers with index
  ansible.builtin.debug:
    msg: "Server #{{ my_idx }} is {{ item }}"
  loop:
    - web01
    - web02
    - web03
  loop_control:
    index_var: my_idx  # 0から始まる連番が入る

3. 辞書(ハッシュ)のループ

辞書型データをループさせたい場合、dict2items フィルタを使ってリスト型に変換するのが定石です。

vars:
  users:
    alice: 
      uid: 1001
      shell: /bin/bash
    bob:
      uid: 1002
      shell: /bin/zsh

tasks:
  - name: Create users from dict
    ansible.builtin.user:
      name: "{{ item.key }}"   # キー (alice, bob)
      uid: "{{ item.value.uid }}" # 値の中身
      shell: "{{ item.value.shell }}"
    loop: "{{ users | dict2items }}"

第2章:条件分岐とステータス制御 (When / Changed)

when は「実行するかどうか」を決めるだけではありません。
「実行結果をどう扱うか」を制御する failed_whenchanged_when こそが、玄人への入り口です。

1. 複雑な条件分岐

複数の条件を組み合わせる場合、リスト形式で書くと「AND(かつ)」になります。

when:
  - ansible_distribution == "AlmaLinux"
  - ansible_distribution_major_version == "9"
  - inventory_hostname in groups['web']

バージョン比較には version テストが便利です。

when: ansible_distribution_version is version('8.5', '>=')

2. changed_when で「変更なし」を装う

commandshell モジュールは、実行すると必ず「Changed(黄色)」になります。
しかし、「確認コマンドを叩いただけ」なら、本来は「Ok(緑色)」であるべきです。
これを制御することで、Playbookの冪等性(べきとうせい)を保ちます。

- name: Check if nginx is installed
  ansible.builtin.command: nginx -v
  register: nginx_check
  changed_when: false  # 常に「変更なし(緑)」にする
  ignore_errors: true

さらに高度な制御として、「出力に特定の文字が含まれていたらChangedにする」ことも可能です。

  changed_when: "'upgraded' in apt_result.stdout"

3. failed_when でエラー判定を覆す

「エラーが出るのが正常(想定内)」というケースがあります。
例えば、「grepコマンドで検索して、見つからなかった(戻り値1)」場合などです。
通常はAnsibleが止まってしまいますが、これを制御します。

- name: Check for error logs
  ansible.builtin.shell: grep "ERROR" /var/log/app.log
  register: grep_result
  # 戻り値が0(見つかった) または 1(見つからない) なら成功とする
  # 戻り値が2(ファイルがない等) なら失敗とする
  failed_when: grep_result.rc not in [0, 1]

第3章:失敗を許容する心 (Block / Rescue / Always)

入門編の付録でも少し触れましたが、実務ではより複雑なエラーハンドリング(例外処理)が求められます。
特に「DB更新に失敗したらバックアップから戻す」といったロールバック処理には必須です。

トランザクション的な処理の実装

- name: Database Upgrade Transaction
  block:
    - name: Backup Database
      community.mysql.mysql_db_dump:
        name: myapp
        target: /tmp/myapp.sql
    
    - name: Run Upgrade Script
      ansible.builtin.shell: /opt/scripts/upgrade_db.sh
      
  rescue:
    - name: Restore Database (Rollback)
      community.mysql.mysql_db_import:
        name: myapp
        target: /tmp/myapp.sql
      
    - name: Notify Slack
      community.general.slack:
        token: "..."
        msg: "DB Upgrade Failed! Rollback executed."

  always:
    - name: Remove Backup File
      ansible.builtin.file:
        path: /tmp/myapp.sql
        state: absent

always セクションは、成功・失敗に関わらず必ず実行されるため、一時ファイルの削除などに最適です。


第4章:データの魔術師 (Jinja2 Filters)

ここからが本記事の核心です。
Ansible(Jinja2)のフィルタ機能を使いこなせば、複雑なJSONデータから必要な情報だけを抽出・加工できます。
これを知っているかどうかで、Playbookの行数が10分の1になります。

1. リスト操作の基本 (map, list)

「オブジェクトのリストから、特定の属性だけを抜き出してリストにする」場合に使います。

元データ (users):

[
  {"name": "alice", "uid": 1001},
  {"name": "bob",   "uid": 1002}
]

やりたいこと: ユーザー名のリスト ['alice', 'bob'] が欲しい。

user_names: "{{ users | map(attribute='name') | list }}"

map で属性を抽出し、最後に list でリスト形式に戻します(これがないとイテレータオブジェクトになってしまいます)。

2. 条件による抽出 (selectattr)

「UIDが1002以上のユーザーだけ抜き出したい」場合。

target_users: "{{ users | selectattr('uid', '>=', 1002) | list }}"

逆に除外したい場合は rejectattr を使います。

3. パス操作 (basename, dirname)

ファイルパスからファイル名だけを取り出したい場合。

filename: "{{ '/var/log/httpd/access.log' | basename }}"
# 結果: access.log

4. JSONクエリの最終兵器 (json_query)

mapselectattr でも対応できない複雑なネスト構造には、json_query を使います。
これは JMESPath というクエリ言語を使用します。

前提: コントロールノードに jmespath ライブラリが必要です。

pip install jmespath

元データ (aws_result):

{
  "instances": [
    {
      "tags": [{"Key": "Name", "Value": "Web01"}, {"Key": "Env", "Value": "Prod"}],
      "private_ip": "10.0.1.10"
    },
    {
      "tags": [{"Key": "Name", "Value": "Web02"}, {"Key": "Env", "Value": "Dev"}],
      "private_ip": "10.0.1.11"
    }
  ]
}

やりたいこと: “Env” タグが “Prod” であるインスタンスのプライベートIPリストが欲しい。

- name: Get Prod IPs
  ansible.builtin.debug:
    msg: "{{ aws_result | json_query(query) }}"
  vars:
    query: "instances[?tags[?Key=='Env' && Value=='Prod']].private_ip"

このクエリ一発で、複雑なJSONからピンポイントでデータを引き抜けます。
AWSやAzureのAPI戻り値を扱う際には必須のスキルです。


第5章:【実践演習】動的リストを使った設定ファイル生成

学んだ知識を組み合わせて、実践的なタスクを行いましょう。
「Dynamic Inventoryで取得した全WebサーバーのIPアドレスを抽出し、ロードバランサーの設定ファイル(HAProxy)に書き込む」というシナリオです。

1. 状況設定

  • グループ role_Web に属するホストが複数ある(前回AWSから取得したもの)。
  • それぞれのホスト変数は hostvars マジック変数に入っている。
  • HAProxyの設定ファイルに server web01 10.0.1.10:80 のように列挙したい。

2. テンプレート作成 (haproxy.cfg.j2)

global
    log /dev/log local0

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http_front
    bind *:80
    default_backend http_back

backend http_back
    balance roundrobin
    # ここでループ処理!
    {% for host in groups['role_Web'] %}
    server {{ host }} {{ hostvars[host]['ansible_host'] }}:80 check
    {% endfor %}

3. Playbook作成 (deploy_lb.yml)

---
- name: Configure Load Balancer
  hosts: loadbalancer
  become: true

  tasks:
    - name: Deploy HAProxy Config
      ansible.builtin.template:
        src: templates/haproxy.cfg.j2
        dest: /etc/haproxy/haproxy.cfg
        validate: haproxy -c -f %s
      notify: Restart HAProxy

  handlers:
    - name: Restart HAProxy
      ansible.builtin.service:
        name: haproxy
        state: restarted

解説

  • groups['role_Web']: このグループに属するホスト名のリストが取れます。
  • hostvars[host]['ansible_host']: 特定のホストの変数(ここではIPアドレス)を取得する定型句です。

これで、Webサーバーが10台に増えても、このPlaybookを実行するだけでロードバランサーの設定が自動更新されます。
わざわざIPアドレスを調べる必要はありません。


まとめ:ロジックを制する者は自動化を制す

お疲れ様でした!
今回は、Ansibleを「自動化ツール」から「インフラ開発言語」へと昇華させるためのロジックとデータ操作を学びました。

今回の重要ポイント:

  • loop_control を使って、変数を整理し、ネストに対応せよ。
  • changed_when / failed_when でステータスを自在に操れ。
  • 複雑な処理は block/rescue でトランザクション化せよ。
  • mapjson_query を使えば、どんな複雑なデータも抽出できる。

これらのテクニックを使えば、「Ansibleでは無理だからシェルスクリプトで書こう…」という妥協を減らすことができます。
Playbookだけで完結させることで、可読性と保守性が向上します。

しかし、ロジックが複雑になると気になるのが「実行速度」です。
数千台のサーバーに対して複雑なループを回すと、Ansibleは驚くほど遅くなることがあります。

次回、応用講座 第3回は「爆速化チューニング。Forks、Pipelining、Mitogenによるパフォーマンス改善」です。
SSH接続のオーバーヘッドを極限まで減らし、並列実行数を最適化し、さらには「Mitogen」という魔法のプラグインを使って実行速度を数倍〜数十倍にするチューニング術を伝授します。
大規模環境のエンジニア必見です。お楽しみに!

▼ ロジック構築力を試す ▼

複雑なPlaybookを実験
「VPS」で自分専用環境

おすすめVPSを見る

高度な自動化スキルを年収に
「ITエンジニア転職」

転職エージェントを見る

コメント