Red Hat Certified Engineer (RHCE) #9 Tag와 조건부: when, loop, until

8 분 소요

#8 Error handling에서 실패를 다루는 법을 잡았다면, 이번에는 언제 task를 실행할지, 몇 번 반복할지, 어떤 부분만 골라 돌릴지를 결정하는 흐름 제어를 정리하겠습니다. when으로 조건을 걸고, loop로 리스트를 반복하고, until로 성공할 때까지 재시도하며, tags로 플레이북의 일부만 실행합니다. 이 네 가지는 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 조합 #

여러 조건을 묶을 때 두 가지 방식이 있습니다. 리스트로 나열하면 모든 조건이 참(and)일 때 실행됩니다.

and 조건(리스트)
- name: 두 조건이 모두 참일 때
  ansible.builtin.debug:
    msg: "조건 충족"
  when:
    - ansible_facts['os_family'] == "RedHat"
    - ansible_facts['memtotal_mb'] > 2048

or나 복합 논리는 한 줄 표현식으로 씁니다. 괄호로 우선순위를 명확히 하겠습니다.

or 조건(표현식)
- name: RHEL 또는 Fedora일 때
  ansible.builtin.debug:
    msg: "Red Hat 계열"
  when: >
    ansible_facts['distribution'] == "RedHat" or
    ansible_facts['distribution'] == "Fedora"

변수 정의 여부 검사 #

변수가 정의되어 있는지로 분기하는 일도 잦습니다. is defined, is undefined, is none을 씁니다.

변수 정의 여부 검사
- name: extra_pkg 변수가 정의된 경우에만 설치
  ansible.builtin.dnf:
    name: "{{ extra_pkg }}"
    state: present
  when: extra_pkg is defined

값의 참/거짓으로 분기할 때는 변수 이름을 그대로 두거나 when: enable_service | bool처럼 | bool 필터를 씁니다.

loop: 리스트 반복 #

loop는 하나의 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는 리스트를 한 단계 평탄화(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라면 두 번 반복됩니다.

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: 웹 서버 구성
  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 + 재시도 #

지금까지의 도구를 한 플레이북에 모으겠습니다. OS 계열로 분기해 패키지를 설치하고, 사용자를 일괄 생성하며, 서비스가 활성화될 때까지 재시도하고, 각 단계에 태그를 붙였습니다.

종합 예제 플레이북
---
- name: 웹 서버 구성
  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는 한 줄 표현식으로 작성한다. 리스트 나열은 모든 조건이 참일 때만 실행된다.
  • is defined로 변수 존재 여부를 검사한다. 값의 참/거짓 판정에는 | bool을 붙인다.
  • loop의 현재 항목은 item, dict 항목은 item.name 식으로 접근한다. 사용자 목록 일괄 생성이 대표 출제 패턴이다.
  • dict를 반복하려면 dict2items 필터로 변환하고 item.key, item.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 참조, 사용자 일괄 생성, dict2items, loop_controlloop_varlabel
  • until. 재시도. register,retries,delay 조합으로 서비스 기동 대기
  • tags. 부분 실행. --tags,--skip-tags,--list-tags, play 수준 태그, always,never

이 네 도구는 RHCSA 자동화 편(#14〜#17)에서 OS별 분기와 목록 일괄 처리로 끊임없이 재등장합니다. 손에 익혀 두면 이후 편이 한결 수월해집니다.

다음: Ansible Vault #

흐름 제어는 잡았습니다. 이제 플레이북에 담기는 비밀번호나 API 키 같은 민감한 값을 안전하게 다루는 법으로 넘어갑니다.

#10 Ansible Vault: 비밀 관리에서는 ansible-vault로 변수를 암호화하고, --ask-vault-pass와 vault 비밀번호 파일로 플레이북을 실행하며, 평문과 암호문을 분리해 관리하는 시험 단골 패턴까지 직접 만들어 보며 정리하겠습니다.

X