LabHub
배우기 러닝패스 코스

Ansible実戦

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

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

같은 태스크를 복사해 열 개 만드는 대신, 태스크는 하나 두고 데이터를 열 줄로 만든다.

概念マップ: 데이터를 열 줄로・loop 와 when 을 함께 쓰면 조건은 반복의 각 항목마다 평가된다.・재시도는 실패를 늦출 뿐 없애지 않는다.・첫째, 반복 안의 멱등성.

왜 이게 필요했나

사용자 세 명을 만드는 플레이북을 복사·붙여넣기로 쓰면, 네 번째 사용자가 생겼을 때 태스크를 하나 더 붙인다. 그런데 그중 하나만 권한 인자를 빠뜨리면 그 사용자만 설정이 다르다. 이 오류는 코드를 아무리 봐도 눈에 잘 안 띈다 — 네 개가 거의 같아 보이기 때문이다.

loop 는 이 구조적 위험을 없앤다. 태스크가 하나뿐이니 인자를 빠뜨릴 자리도 하나뿐이고, 데이터는 표처럼 보여 빠진 칸이 눈에 띈다.

어떻게 동작하나

loop 는 리스트를 받아 item 에 하나씩 넣어 태스크를 반복한다. 리스트 원소가 딕셔너리면 item.name 처럼 필드로 접근한다.

when 은 조건이다. loopwhen 을 함께 쓰면 조건은 반복의 각 항목마다 평가된다. 즉 조건에 맞는 항목만 처리된다. 이 조합이 "조건부 반복"을 만든다.

주의할 조합이 몇 가지 있다.

도구 쓰임 함정
loop_control.label 로그에 찍을 축약 이름 지정 지정하지 않으면 딕셔너리 전체가 로그에 노출됨
until / retries / delay 조건이 참이 될 때까지 재시도 untilloop 와 함께 쓰기 까다로움
product 필터 두 리스트의 모든 조합 조합 수가 곱으로 늘어남
fileglob 조회 패턴에 맞는 파일 목록 순서가 보장되지 않음

loop_control.label 은 보안 문제이기도 하다. 딕셔너리에 비밀번호가 들어 있으면 기본 로그에 그대로 찍힌다. 라벨을 지정하거나 no_log 를 쓰는 것이 안전하다.

until 은 "될 때까지 기다린다"를 표현한다. 서비스가 뜨기를 기다리는 상황에 쓰지만, 재시도는 실패를 늦출 뿐 없애지 않는다. 재시도 횟수와 간격을 정할 때 "이 시간 안에 안 되면 진짜 문제"라는 판단이 함께 들어가야 한다.

현장에서 만나는 모습

첫째, 반복 안의 멱등성. 반복하는 태스크도 각 항목에 대해 멱등해야 한다. 반복이 열 번 돌면 비멱등의 피해도 열 배다.

둘째, 조합 폭발. 환경 3 × 컴포넌트 2 = 6 은 괜찮지만, 여기에 리전 5 를 곱하면 30 이 된다. 조합 반복은 편하지만 실행 시간과 로그 길이가 곱으로 늘어난다는 것을 계산에 넣어야 한다.

셋째, 순서에 의존하지 않기. 파일 패턴 조회의 결과 순서는 보장되지 않는다. 순서가 중요하면 정렬하거나 명시적 리스트를 쓴다.

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 를 앞에 둡니다. 파이썬처럼 단축 평가가 되므로 뒤 조건이 안전해집니다.

when: extra_port is defined and extra_port | int > 1024

실패를 다루는 세 가지

- 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 로 대기 로직을 만든다. 두 리스트의 조합으로 6개 설정 파일을 만들고, 패턴으로 그것들을 다시 모은 뒤, 전체를 요약한 보고서로 마무리한다.