【連載 第2回】ループと条件分岐 (Loop & When):複雑なロジックを組む〜Ansibleでプログラミング〜

「タスクをコピペで増やす」のは、今日で終わりにしましょう。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回(第1回)は、変数とFactsを使って「値」を外に出し、Playbookの再利用性を高める方法を学びました。

しかし、実務の現場では「値を変える」だけでは対応できないケースが多々あります。

  • 「開発チームのメンバー10人分のユーザーアカウントを作成したい」
  • 「Webサーバーの設定ファイルを、必要なパッケージと一緒にまとめてインストールしたい」
  • 「管理対象のサーバーに、CentOSとUbuntuが混在している」

もしあなたが、ユーザー作成のタスクを10回コピペしたり、OSごとに別のPlaybookファイルを作ったりしているなら、それはAnsibleのポテンシャルを浪費しています。
今回は、プログラミングの基本である「繰り返し(Loop)」「条件分岐(When)」をマスターし、スマートで短いPlaybookを書く技術を身に着けましょう。

コウ君

先生、まさにそれで困ってました!
新入社員が入るたびに、ユーザー作成のタスクをコピペして追加してたんです。
Playbookが縦に長くなりすぎて、どこを編集してるのか分からなくなっちゃって…。
あと、この前Ubuntuサーバーに誤ってyumコマンドを実行してエラーにしちゃいました。

リナックス先生

コウ君、それは典型的な「Ansibleアンチパターン」ね。
Ansibleは単なる手順書じゃなくて、プログラムなの。
「リストにある全員を作成する」「OSがUbuntuならaptを使う」といったロジックを組み込めば、Playbookは驚くほど短く、そして賢くなるわ。
今回は少し頭を使うけど、これを覚えるとAnsibleの世界が一気に広がるわよ!

本記事では、実務で頻出する「ユーザー一括管理」や「OS別対応」を題材に、LoopとWhenの具体的な使い方と、プロならではの応用テクニックを解説します。

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

  • 【第1回】変数(Variables)とFactsの活用:環境差異を吸収する
  • 【第2回】ループと条件分岐 (Loop & When)(今回):複雑なロジックを組む
  • 【第3回】ハンドラーと通知 (Handlers):無駄な再起動を防ぐ
  • 【第4回】テンプレート (Jinja2):設定ファイルを動的に生成する
  • 【第5回】ロール (Roles) の設計:Playbookを部品化して再利用する
  • 【第6回】Ansible Vault:パスワードなどの機密情報を暗号化する
  • 【第7回】エラーハンドリングとデバッグ:止まらない自動化のために
  • 【第8回】実践プロジェクト:LAMP環境の完全自動構築と総まとめ

1. 繰り返し処理「loop」の基本

同じモジュールを、異なるパラメータ(値)で何度も実行したい場合に使います。
以前のバージョンのAnsibleでは with_items が使われていましたが、現在はより柔軟な loop が推奨されています。

1-1. 最もシンプルなリストのループ

例えば、httpd, mariadb-server, git の3つのパッケージをインストールしたいとします。
これらを別々のタスクに書くのは非効率です。

悪い例(タスクのコピペ):

    - name: httpdをインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

    - name: gitをインストール
      ansible.builtin.dnf:
        name: git
        state: present
    # ...延々と続く

良い例(loopの使用):

    - name: 必要なパッケージを一括インストール
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - httpd
        - mariadb-server
        - git

解説:

  • loop: の下にリスト形式で値を記述します。
  • タスク内で "{{ item }}" と書くと、ループのたびにリストの値が順番に入ります。
  • これだけで、タスク定義は1つで済み、パッケージが増えてもリストに追加するだけで済みます。

⚠️ 注意:dnf/yumモジュールの特例
実は dnfapt モジュール自体がリストを受け取れる仕様になっているため、単純なパッケージインストールなら loop を使わず、name に直接リストを渡すほうが高速です。
しかし、userservice など多くのモジュールはリストを受け取れないため、loop が必須になります。


2. 【実践】辞書リストを使ったユーザー一括作成

実務では「単なる値のリスト」ではなく、「ユーザー名、グループ、パスワード」といった複数の属性を持つデータを扱いたい場合がほとんどです。
この場合、「辞書(ハッシュ)のリスト」をループさせます。

要件

  • ユーザー alicedevelopers グループに所属させる。
  • ユーザー bobmanagers グループに所属させる。
  • それぞれの公開鍵も設定したい。

Playbookの記述例

---
- name: ユーザー管理Playbook
  hosts: all
  become: true

  vars:
    # ユーザー情報を辞書のリストとして定義
    users_list:
      - name: alice
        groups: wheel
        ssh_key: "ssh-rsa AAAAB3NzaC1yc2E..."
      - name: bob
        groups: users
        ssh_key: "ssh-rsa BBBBB3NzaC1yc2E..."

  tasks:
    - name: ユーザーを作成する
      ansible.builtin.user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        state: present
        create_home: true
      loop: "{{ users_list }}"

    - name: SSH公開鍵を配置する
      ansible.builtin.authorized_key:
        user: "{{ item.name }}"
        key: "{{ item.ssh_key }}"
        state: present
      loop: "{{ users_list }}"

解説:

  • loop: "{{ users_list }}" で、変数をループに渡しています。
  • タスク内では item.nameitem.groups のように、ドット繋ぎで辞書のキーにアクセスできます。
  • これで、ユーザーが100人になっても、vars のリストを更新するだけで対応できます。

💡 プロのノウハウ:変数の外出し
第1回で学んだ group_varsusers_list を定義しておけば、Playbook本体は一切変更せずに、インベントリごとに作成するユーザーを変えることができます。
「本番環境には管理者だけ、開発環境には全員」といった運用が容易になります。


3. 条件分岐「when」の基本

「特定の条件を満たすときだけタスクを実行したい」場合に使うのが when です。
プログラミングの if 文に相当します。

3-1. 基本的な構文

タスクの最後に when: 条件式 を記述します。

    - name: 特定のホストだけで実行するタスク
      ansible.builtin.debug:
        msg: "このメッセージは web01 サーバーでのみ表示されます"
      when: inventory_hostname == "web01"

3-2. 比較演算子と論理演算子

条件式には、Pythonライクな演算子が使えます。

演算子 意味
== 等しい ansible_distribution == "Ubuntu"
!= 等しくない ansible_os_family != "RedHat"
>, < 大小比較 ansible_memtotal_mb < 1024
in リストに含まれる inventory_hostname in groups['webservers']
not 否定 not result.stat.exists

3-3. 複数条件(AND / OR)

複数の条件を組み合わせることも可能です。

AND(かつ):リスト形式で書く

      when:
        - ansible_distribution == "CentOS"
        - ansible_distribution_major_version == "8"

OR(または):1行で or を使う

      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

4. 【実践】OSファミリーによる処理の分岐

実務で最もよく使うのが、「RedHat系ならdnf、Debian系ならaptを使う」という分岐です。
Ansible Facts の ansible_os_family 変数を使います。

クロスプラットフォームなPlaybookの例

---
- name: Webサーバー構築 (Multi OS)
  hosts: all
  become: true

  tasks:
    # RedHat系 (CentOS, AlmaLinux, RHEL, Rocky) 用の処理
    - name: Install Apache (RedHat)
      ansible.builtin.dnf:
        name: httpd
        state: present
      when: ansible_os_family == "RedHat"

    # Debian系 (Ubuntu, Debian) 用の処理
    - name: Install Apache (Debian)
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: yes
      when: ansible_os_family == "Debian"

    # サービス名は変数で切り替えるのも手だが、ここではシンプルに分岐
    - name: Start Apache (RedHat)
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: true
      when: ansible_os_family == "RedHat"

    - name: Start Apache (Debian)
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true
      when: ansible_os_family == "Debian"

これにより、対象ホストがAlmaLinuxでもUbuntuでも、適切なコマンドが実行される「賢いPlaybook」になります。
条件に合致しないタスクは skipping と表示されてスキップされます。


5. 「register」と「when」のコンボ技

第1回で紹介した、コマンド実行結果を保存する register
これと when を組み合わせることで、「前のタスクの結果に応じて次のタスクを実行する」ことができます。

例:ファイルが存在しない場合のみ作成する

通常、Ansibleのモジュールは自動的に存在確認をしてくれます(冪等性)。
しかし、command モジュールなどで独自の処理をする場合は、自分でチェックロジックを作る必要があります。

  tasks:
    # 1. ファイルの存在確認
    - name: 設定ファイルの存在確認
      ansible.builtin.stat:
        path: /etc/myapp/config.conf
      register: config_file_check

    # 2. 存在しない場合のみ、初期化コマンドを実行
    - name: 設定ファイルの初期生成
      ansible.builtin.command: /usr/local/bin/myapp-init
      when: not config_file_check.stat.exists

解説:

  • stat モジュールでファイル情報を取得し、結果を config_file_check に入れます。
  • stat.exists はファイルがあれば true、なければ false になります。
  • when: not ... とすることで、「存在しない場合(falseの時)」だけタスクを実行させます。

これにより、「何度実行しても安全な(初期化コマンドを連打しない)」Playbookが作れます。


6. 【プロのノウハウ】failed_when と changed_when

commandshell モジュールを使う際、Ansibleは「終了コードが0以外なら失敗(failed)」「実行すれば必ず変更あり(changed)」と判断します。
しかし、実務ではこの判定をカスタマイズしたい場面があります。

6-1. エラー判定を変える (failed_when)

例えば、「grepコマンドで検索して、見つからなくてもエラーにしたくない(終了コード1でもOKとしたい)」場合。

    - name: ログファイルから特定のエラーを検索
      ansible.builtin.command: grep "ERROR" /var/log/app.log
      register: grep_result
      # 終了コードが0(見つかった)でも1(見つからない)でもOKとする。それ以外(2など)はエラー。
      failed_when: grep_result.rc not in [0, 1]
      # 変更は発生しないので changed は false にする
      changed_when: false

6-2. 変更判定を変える (changed_when)

スクリプトを実行したけれど、実際には何も変更していない場合、Ansibleが「changed(黄色)」と報告してくるのはノイズになります。
条件を指定して「変更なし(緑色)」と扱わせることができます。

    - name: アプリケーションのバージョン確認
      ansible.builtin.command: /usr/local/bin/myapp --version
      register: app_version
      # このタスクは確認だけなので、常に「変更なし」とする
      changed_when: false

これらを適切に設定することで、実行結果のレポートが正確になり、「本当に何か変更があったのか?」が一目で分かるようになります。
これは「美しいPlaybook」を書くための重要な作法です。


まとめ:Ansibleに知能を与えよう

お疲れ様でした!
第2回では、Ansibleをプログラミング言語のように操るための「Loop」と「When」を学びました。

今回の要点まとめ:

  • loop を使えば、似たようなタスクを一行にまとめられる。
  • 辞書リストのループを使えば、ユーザー管理などの複雑な処理も一括化できる。
  • when を使えば、OSの違いやファイル有無に応じた条件分岐ができる。
  • Facts (ansible_os_familyなど) は条件分岐の強力な材料になる。
  • failed_when / changed_when を使いこなして、実行結果をコントロールする。
コウ君

すごい…!
あんなに長かったPlaybookが、変数とループを使ったら半分以下の行数になりました!
しかも、Ubuntuサーバーに対して実行してもエラーにならずに、勝手にaptを使ってくれて感動です!

次回は、設定ファイルを書き換えた後に自動でサービスを再起動させる仕組み、「ハンドラー (Handlers)」について学びます。
「設定を変えたのに反映されない!」というミスを防ぎ、無駄な再起動も抑える、運用の要となる機能です。

次回、【第3回】ハンドラーと通知 (Handlers):無駄な再起動を防ぐ でお会いしましょう!

▼ 複数OS環境でAnsibleを試すなら ▼

UbuntuもAlmaLinuxも選べる
「おすすめVPS」

VPSランキングを見る

インフラ自動化スキルを活かす
「ITエンジニア転職」

転職エージェントを見る

コメント