TT Lab
はじめる
学ぶ 学習パス コース

Ansible実戦

loopとwhen — タスクではなくデータを増やす

TT Labで続きを見る

一言でいうと

同じタスクをコピーして10個作る代わりに、タスクは1つだけ置いて、データを10行にします。

なぜ必要なのか

ユーザー3人を作るプレイブックをコピー・ペーストで書くと、4人目のユーザーができたときにタスクを1つ追加します。ところが、そのうちの1つだけが権限の引数を抜かしていたら、そのユーザーだけ設定が違います。このエラーは、コードをいくら見ても目に留まりにくいです。4つがほとんど同じに見えるからです。

loopは、この構造的なリスクをなくします。タスクが1つだけなので、引数を抜かす場所も1か所だけで、データは表のように見えるので、抜けたマスが目立ちます。

どう動くのか

loopはリストを受け取り、itemに1つずつ入れてタスクを繰り返します。リストの要素がディクショナリなら、item.nameのようにフィールドでアクセスします。

whenは条件です。loopとwhenを一緒に使うと、条件は繰り返しの各項目ごとに評価されます。つまり、条件に合う項目だけが処理されます。この組み合わせが、「条件付きの繰り返し」を作ります。

注意すべき組み合わせがいくつかあります。

ツール 用途 落とし穴
loop_control.label ログに出力する省略名を指定 指定しないと、ディクショナリ全体がログに露出する
until / retries / delay 条件が真になるまでリトライ untilはloopと一緒に使うのが厄介
productフィルター 2つのリストのすべての組み合わせ 組み合わせの数が掛け算で増える
fileglobルックアップ パターンに合うファイルの一覧 順序が保証されない

loop_control.labelはセキュリティの問題でもあります。ディクショナリにパスワードが入っていると、デフォルトのログにそのまま出力されます。ラベルを指定するかno_logを使うのが安全です。

untilは「そうなるまで待つ」を表現します。サービスが起動するのを待つ状況に使いますが、リトライは失敗を遅らせるだけで、なくしはしません。リトライの回数と間隔を決めるときには、「この時間内にできなければ本当に問題だ」という判断が一緒に入っている必要があります。

現場での姿

1つ目は、繰り返しの中の冪等性です。繰り返すタスクも、各項目に対して冪等でなければなりません。繰り返しが10回動けば、非冪等の被害も10倍です。

2つ目は、組み合わせ爆発です。環境3 × コンポーネント2 = 6なら問題ありませんが、ここにリージョン5を掛けると30になります。組み合わせの繰り返しは便利ですが、実行時間とログの長さが掛け算で増えることを、計算に入れる必要があります。

3つ目は、順序に依存しないことです。ファイルのパターンで調べた結果の順序は、保証されません。順序が重要なら、ソートするか、明示的なリストを使います。

loopの形とその代償

# 목록을 그대로
- name: 패키지 설치
  apt: {name: "{{ item }}", state: present}
  loop: [git, curl, jq]           # ❌ 태스크가 세 번 돈다

# 모듈이 목록을 받으면 한 번에
- name: 패키지 설치
  apt: {name: [git, curl, jq], state: present}   # ✅ apt 한 번

モジュールがリストを受け取るなら、loopを使いません。apt・yum・pipはリストを受け取って一度に処理しますが、loopで回すとパッケージごとにaptを呼び出し直します。100個なら100回です。

複雑なデータを回すときは、loop_controlで出力を読みやすくします。

- name: 사용자 만들기
  user: {name: "{{ item.name }}", groups: "{{ item.groups }}"}
  loop: "{{ users }}"
  loop_control:
    label: "{{ item.name }}"      # 로그에 딕셔너리 전체 대신 이름만
    pause: 1                       # 항목 사이 1초 — API 속도 제한이 있을 때

labelがないと、ログがディクショナリの全文で埋め尽くされます。シークレットが入っていれば、それがそのまま出力されるので、セキュリティの問題でもあります(no_log: trueと一緒に)。

whenが評価される時点

whenは各項目ごとに評価されます。loopと一緒に使うと、項目ごとにふるい分けられます。

- name: 프로덕션 호스트에만
  copy: {src: prod.conf, dest: /etc/app.conf}
  when: env == 'prod'              # 호스트마다 평가

whenの中では{{ }}を使いません。すでにJinja2の式のコンテキストなので、二重に囲むと警告が出るか、文字列として評価されます。

when: env == 'prod'                # ✅
when: "{{ env == 'prod' }}"        # ❌ 되기는 하지만 경고

定義されていない変数を扱うときは、is definedを先に置きます。Pythonのように短絡評価されるので、後ろの条件が安全になります。

when: extra_port is defined and extra_port | int > 1024

失敗を扱う3つの方法

- command: /opt/check.sh
  register: result
  failed_when: result.rc not in [0, 2]    # 2도 성공으로 본다
  changed_when: false                      # 상태를 안 바꾸는 조회
  ignore_errors: true                      # 실패해도 계속 (최후의 수단)

ignore_errorsは乱用しやすいです。失敗を無視すると、そのあとのタスクが誤った前提の上で動きます。たいていは、failed_whenで「何が本当の失敗か」を定義するほうが適切です。

block/rescue/alwaysを使うと、例外処理のように使えます。

- block:
    - command: /opt/migrate.sh
  rescue:
    - command: /opt/rollback.sh
    - fail: {msg: "마이그레이션 실패 — 되돌렸습니다"}
  always:
    - command: /opt/unlock.sh

次のラボですること

リストとディクショナリのリストを回してディレクトリを作り、whenで条件に合う項目だけを処理します。loop_control.labelでログを整理し、untilで待機ロジックを作ります。2つのリストの組み合わせで6つの設定ファイルを作り、パターンでそれらをもう一度集めたあと、全体をまとめたレポートで締めくくります。