Red Hat Certified Engineer (RHCE) #9 Tag と条件分岐: when、loop、until

読了 8分

#8 Error handling で失敗の扱い方を押さえたなら、今回は いつ task を実行するか、何回反復するか、どの部分だけ選んで回すか を決める流れの制御を整理します。when で条件をかけ、loop でリストを反復し、until で成功するまで再試行し、tags でプレイブックの一部だけを実行します。この 4 つは RHCE 実技でほぼ毎回登場するツールです。

when: 条件付き実行 #

when は task を実行するかしないかを決めるキーです。条件が真なら実行し、偽ならスキップします。値は Jinja2 式ですが、when の中では変数名を {{ }} なしでそのまま書きます。

when の基本
- name: Apache パッケージのインストール
  ansible.builtin.dnf:
    name: httpd
    state: present
  when: ansible_facts['os_family'] == "RedHat"

条件が偽のホストでは task が skipping と表示され、冪等性に影響を与えません。

fact ベースの分岐 #

試験で最もよくあるパターンは OS の種類やバージョンによって違う作業 をする分岐です。ansible_facts で収集された値を条件に使います。

fact ベースの分岐
- name: Debian 系で apache2 をインストール
  ansible.builtin.apt:
    name: apache2
    state: present
  when: ansible_facts['os_family'] == "Debian"

- name: RHEL 9 以上でのみ実行
  ansible.builtin.debug:
    msg: "RHEL 9 以上です"
  when:
    - ansible_facts['distribution'] == "RedHat"
    - ansible_facts['distribution_major_version'] | int >= 9

distribution_major_version は文字列なので、| int フィルターで整数に変換してから比較します。この変換を忘れると数値比較がずれることがあります。

and、or の組み合わせ #

複数の条件をまとめるとき 2 つの方式があります。リストで並べると すべての条件が真 (and) のときに実行されます。

and 条件(リスト)
- name: 2 つの条件がどちらも真のとき
  ansible.builtin.debug:
    msg: "条件を満たした"
  when:
    - ansible_facts['os_family'] == "RedHat"
    - ansible_facts['memtotal_mb'] > 2048

or や複合論理は 1 行の式で書きます。括弧で優先順位を明確にします。

or 条件(式)
- name: RHEL または Fedora のとき
  ansible.builtin.debug:
    msg: "Red Hat 系"
  when: >
    ansible_facts['distribution'] == "RedHat" or
    ansible_facts['distribution'] == "Fedora"

変数が定義されているかの検査 #

変数が定義されているかで分岐することもよくあります。is definedis undefinedis none を使います。

変数定義の有無を検査
- name: extra_pkg 変数が定義されている場合のみインストール
  ansible.builtin.dnf:
    name: "{{ extra_pkg }}"
    state: present
  when: extra_pkg is defined

値の真偽で分岐するときは変数名をそのまま置くか、when: enable_service | bool のように | bool フィルターを使います。

loop: リストの反復 #

loop は 1 つの task をリストの各項目に対して反復実行します。反復中の現在の項目は item 変数で参照します。

loop の基本
- name: 複数のパッケージをインストール
  ansible.builtin.dnf:
    name: "{{ item }}"
    state: present
  loop:
    - httpd
    - mariadb-server
    - php

パッケージのインストールのようにモジュールがリストを直接受け取る場合は name: "{{ packages }}" で一度に渡すほうが効率的ですが、loop はユーザー作成のように項目ごとに独立した呼び出しが必要なときに真価を発揮します。

ユーザー一覧の一括作成 #

試験定番の ユーザー一覧の一括作成 は loop の代表例です。各項目を dict にすれば、項目内の値に item.key でアクセスできます。

ユーザー一括作成
- name: ユーザーの一括作成
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    state: present
  loop:
    - { name: alice, groups: wheel }
    - { name: bob, groups: developers }
    - { name: carol, groups: developers }

変数ファイルにユーザーリストを置いて loop で参照すれば、プレイブック本文がすっきりします。

変数で定義したユーザーの作成
- name: 変数で定義したユーザーを作成
  ansible.builtin.user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    state: present
  loop: "{{ users }}"

with_items は旧表記 #

昔のプレイブックでは with_items を使っていました。動作は同じですが、現在は loop が推奨表記であり、新しいプレイブックは loop で書きます。

with_items の旧表記
# 旧表記 (参考用)
- name: with_items 方式
  ansible.builtin.dnf:
    name: "{{ item }}"
    state: present
  with_items: [httpd, vsftpd]

with_items はリストを 1 段平坦化 (flatten) する点が loop とわずかに違います。ネストしたリストを扱わない限り結果は同じなので、試験では loop だけを一貫して使えば問題ありません。

dict の反復: dict2items #

リストではなく dict (キーと値の組) を反復するには、dict2items フィルターでリストに変換します。変換後の各項目は item.keyitem.value を持ちます。

dict2items での反復
- name: dict を項目として反復
  ansible.builtin.debug:
    msg: "{{ item.key }} = {{ item.value }}"
  loop: "{{ user_roles | dict2items }}"

user_roles が次のような dict なら 2 回反復します。

user_roles の例
user_roles:
  alice: admin
  bob: developer

loop_control: loop_var と label #

role や include で loop がネストすると、内側と外側がどちらも item を使って衝突します。このとき loop_controlloop_var で反復変数名を変えます。

loop_var で変数名を変更
- name: 反復変数名の変更
  ansible.builtin.user:
    name: "{{ user_item.name }}"
    state: present
  loop: "{{ users }}"
  loop_control:
    loop_var: user_item

また dict 項目を反復すると、出力に dict 全体が長々と出てログが散らかります。label で各反復に表示する値だけを指定すると出力がすっきりします。

label で出力を整理
- name: 出力ラベルの指定
  ansible.builtin.user:
    name: "{{ item.name }}"
    state: present
  loop: "{{ users }}"
  loop_control:
    label: "{{ item.name }}"

until: 再試行 #

until は条件が真になるまで task を反復実行します。外部サービスの起動を待ったり、一時的に失敗する作業を再試行したりするときに使います。retries で最大試行回数を、delay で試行間隔 (秒) を指定します。

until での再試行
- name: サービスの応答を待つ
  ansible.builtin.uri:
    url: http://localhost:8080/health
    status_code: 200
  register: result
  until: result.status == 200
  retries: 5
  delay: 10

上の task は応答が 200 になるまで 10 秒間隔で最大 5 回試行します。条件が最後まで真にならなければ task は失敗します。until を使うには結果を register で捕まえて条件で参照する必要がある、という点が核心です。command のように毎回 changed として捕まるモジュールを照会用途で使うときは、changed_when: false を一緒に置いて冪等性を保ちます (下の総合例で確認できます)。

tags: 部分実行 #

tags は task や play に名札を付けて、プレイブック全体ではなく 必要な部分だけ選んで実行 できるようにします。長いプレイブックで特定の作業だけを素早く回すときに役立ちます。

task への tags 付与
- name: Apache のインストール
  ansible.builtin.dnf:
    name: httpd
    state: present
  tags:
    - packages

- name: Apache 設定のデプロイ
  ansible.builtin.template:
    src: httpd.conf.j2
    dest: /etc/httpd/conf/httpd.conf
  tags:
    - config

–tags と –skip-tags #

--tags で指定したタグが付いた task だけを実行します。--skip-tags は逆に、指定したタグを除いて実行します。

--tags と --skip-tags の実行
# config タグが付いた task だけ実行
ansible-playbook site.yml --tags config

# packages タグだけスキップして残りを実行
ansible-playbook site.yml --skip-tags packages

ansible-navigator でも同じオプションを使います。

ansible-navigator での実行
ansible-navigator run site.yml -m stdout --tags config

どのタグが定義されているかを確認するには --list-tags を使います。

タグ一覧の確認
ansible-playbook site.yml --list-tags

play レベルのタグ #

play 全体にタグを付けると、その play のすべての task がタグを継承します。

play レベルのタグ
- name: Web サーバーの構成
  hosts: web
  tags:
    - web
  tasks:
    - name: httpd のインストール
      ansible.builtin.dnf:
        name: httpd
        state: present

always と never #

always タグが付いた task は、--tags で別のタグを指定しても 常に実行 されます。ただし --skip-tags always では除外できます。逆に never タグが付いた task は基本的に 実行されず、当該タグやその task の別のタグを明示したときだけ実行されます。

always と never タグ
- name: 事前チェック (常に実行)
  ansible.builtin.debug:
    msg: "プレイブック開始"
  tags:
    - always

- name: デバッグ情報の出力 (明示したときだけ)
  ansible.builtin.debug:
    var: ansible_facts
  tags:
    - never
    - debug

上でデバッグ task は普段はスキップされますが、--tags debug で呼び出すと実行されます。重い診断作業を普段の実行から外しておくのに役立ちます。

総合例: OS 分岐 + loop + 再試行 #

ここまでのツールを 1 つのプレイブックにまとめます。OS 系列で分岐してパッケージをインストールし、ユーザーを一括作成し、サービスが有効になるまで再試行し、各ステップにタグを付けました。

総合例のプレイブック
---
- name: Web サーバーの構成
  hosts: web
  become: true
  vars:
    web_users:
      - { name: deploy, groups: wheel }
      - { name: app, groups: web }
  tasks:
    - name: 開始通知 (常に実行)
      ansible.builtin.debug:
        msg: "構成を開始します"
      tags:
        - always

    - name: RHEL 系パッケージのインストール
      ansible.builtin.dnf:
        name: httpd
        state: present
      when: ansible_facts['os_family'] == "RedHat"
      tags:
        - packages

    - name: 運用ユーザーの作成
      ansible.builtin.user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        state: present
      loop: "{{ web_users }}"
      loop_control:
        label: "{{ item.name }}"
      tags:
        - users

    - name: サービスの起動
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: true
      tags:
        - service

    - name: サービス有効化の確認
      ansible.builtin.command: systemctl is-active httpd
      register: httpd_state
      until: httpd_state.stdout == "active"
      retries: 5
      delay: 3
      changed_when: false
      tags:
        - service

このプレイブックを --tags users で呼び出すと、開始通知 (always) とユーザー作成だけが実行されます。

試験ポイント #

  • when は変数名を {{ }} なしで 書きます。fact ベースの分岐で ansible_facts['os_family']distribution_major_version | int の比較が定番です。
  • and はリストで、or は 1 行の式 で書きます。リスト形式で並べると、すべての条件が真のときだけ実行されます。
  • is defined で変数の存在を検査します。値の真偽判定には | bool を付けます。
  • loop の現在の項目は item、dict 項目は item.name のようにアクセスします。ユーザー一覧の一括作成が代表的な出題パターンです。
  • dict を反復するには dict2items フィルターで変換し、item.keyitem.value でアクセスします。
  • ネストした反復は loop_control.loop_var で変数の衝突を避け、label で出力を整えます。
  • until は register と一対 です。retriesdelay で再試行回数と間隔を決めます。照会用の command には changed_when: false を一緒に置きます。
  • tags で部分実行 します。--tags で選択、--skip-tags で除外、--list-tags で確認します。always は常に、never は明示したときだけ実行されます。

まとめ #

この記事で押さえたこと:

  • when。条件付き実行。fact・変数ベースの分岐、and (リスト)・or (式)、is defined
  • loop。リストの反復。item の参照、ユーザーの一括作成、dict2itemsloop_controlloop_varlabel
  • until。再試行。registerretriesdelay の組み合わせでサービス起動を待つ
  • tags。部分実行。--tags--skip-tags--list-tags、play レベルのタグ、alwaysnever

この 4 つのツールは RHCSA 自動化編 (#14〜#17) で OS ごとの分岐と一覧の一括処理として絶えず再登場します。手に馴染ませておくと、その後の編が一段とやさしくなります。

次へ: Ansible Vault #

流れの制御は押さえました。ここからはプレイブックに入るパスワードや API キーのような機微な値を安全に扱う方法に進みます。

#10 Ansible Vault: 秘密情報の管理 では、ansible-vault で変数を暗号化し、--ask-vault-pass と vault パスワードファイルでプレイブックを実行し、平文と暗号文を分けて管理する試験定番のパターンまで、自分で作りながら整理します。

X