Red Hat Certified Engineer (RHCE) #9 Tag와 조건부: when, loop, until
#8 Error handling에서 실패를 다루는 법을 잡았다면, 이번에는 언제 task를 실행할지, 몇 번 반복할지, 어떤 부분만 골라 돌릴지를 결정하는 흐름 제어를 정리하겠습니다. when으로 조건을 걸고, loop로 리스트를 반복하고, until로 성공할 때까지 재시도하며, tags로 플레이북의 일부만 실행합니다. 이 네 가지는 RHCE 실기에서 거의 매번 등장하는 도구입니다.
when: 조건부 실행 #
when은 task를 실행할지 말지 결정하는 키입니다. 조건이 참이면 실행하고 거짓이면 건너뜁니다. 값은 Jinja2 표현식이지만, when 안에서는 변수 이름을 {{ }} 없이 그대로 씁니다.
- name: Apache 패키지 설치
ansible.builtin.dnf:
name: httpd
state: present
when: ansible_facts['os_family'] == "RedHat"조건이 거짓인 호스트에서는 task가 skipping으로 표시되며 멱등성에 영향을 주지 않습니다.
fact 기반 분기 #
시험에서 가장 흔한 패턴은 OS 종류나 버전에 따라 다른 작업을 하는 분기입니다. ansible_facts로 수집된 값을 조건에 씁니다.
- 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 >= 9distribution_major_version은 문자열이므로 | int 필터로 정수 변환한 뒤 비교합니다. 이 변환을 빠뜨리면 숫자 비교가 어긋날 수 있습니다.
and, or 조합 #
여러 조건을 묶을 때 두 가지 방식이 있습니다. 리스트로 나열하면 모든 조건이 참(and)일 때 실행됩니다.
- name: 두 조건이 모두 참일 때
ansible.builtin.debug:
msg: "조건 충족"
when:
- ansible_facts['os_family'] == "RedHat"
- ansible_facts['memtotal_mb'] > 2048or나 복합 논리는 한 줄 표현식으로 씁니다. 괄호로 우선순위를 명확히 하겠습니다.
- 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 변수로 참조합니다.
- 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로 작성하겠습니다.
# 구식 표기 (참고용)
- name: with_items 방식
ansible.builtin.dnf:
name: "{{ item }}"
state: present
with_items: [httpd, vsftpd]with_items는 리스트를 한 단계 평탄화(flatten)한다는 점이 loop와 미세하게 다릅니다. 중첩 리스트를 다루지 않는 한 결과는 같으므로, 시험에서는 loop만 일관되게 쓰면 됩니다.
dict 반복: dict2items #
리스트가 아니라 dict(키-값 묶음)를 반복하려면 dict2items 필터로 리스트로 변환합니다. 변환 후 각 항목은 item.key와 item.value를 가집니다.
- name: dict를 항목으로 반복
ansible.builtin.debug:
msg: "{{ item.key }} = {{ item.value }}"
loop: "{{ user_roles | dict2items }}"user_roles가 다음과 같은 dict라면 두 번 반복됩니다.
user_roles:
alice: admin
bob: developerloop_control: loop_var와 label #
role이나 include로 loop가 중첩되면 안쪽과 바깥쪽이 모두 item을 써서 충돌이 납니다. 이때 loop_control의 loop_var로 반복 변수 이름을 바꿉니다.
- name: 반복 변수 이름 변경
ansible.builtin.user:
name: "{{ user_item.name }}"
state: present
loop: "{{ users }}"
loop_control:
loop_var: user_item또한 dict 항목을 반복하면 출력에 dict 전체가 길게 찍혀 로그가 지저분해집니다. label로 각 반복에 표시할 값만 지정하면 출력이 깔끔해집니다.
- name: 출력 라벨 지정
ansible.builtin.user:
name: "{{ item.name }}"
state: present
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"until: 재시도 #
until은 조건이 참이 될 때까지 task를 반복 실행합니다. 외부 서비스가 기동되기를 기다리거나, 일시적으로 실패하는 작업을 다시 시도할 때 씁니다. retries로 최대 시도 횟수를, delay로 시도 간격(초)을 지정합니다.
- 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에 이름표를 붙여, 플레이북 전체가 아니라 필요한 부분만 골라 실행할 수 있게 합니다. 긴 플레이북에서 특정 작업만 빠르게 돌릴 때 유용합니다.
- 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는 반대로 지정한 태그를 제외하고 실행합니다.
# config 태그가 붙은 task만 실행
ansible-playbook site.yml --tags config
# packages 태그만 건너뛰고 나머지 실행
ansible-playbook site.yml --skip-tags packagesansible-navigator에서도 동일한 옵션을 씁니다.
ansible-navigator run site.yml -m stdout --tags config어떤 태그가 정의되어 있는지 확인하려면 --list-tags를 씁니다.
ansible-playbook site.yml --list-tagsplay 수준 태그 #
play 전체에 태그를 붙이면 그 play의 모든 task가 태그를 상속합니다.
- name: 웹 서버 구성
hosts: web
tags:
- web
tasks:
- name: httpd 설치
ansible.builtin.dnf:
name: httpd
state: presentalways와 never #
always 태그가 붙은 task는 --tags로 다른 태그를 지정해도 항상 실행됩니다. 단 --skip-tags always로는 제외할 수 있습니다. 반대로 never 태그가 붙은 task는 기본적으로 실행되지 않으며, 해당 태그나 그 task의 다른 태그를 명시해야만 실행됩니다.
- 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와 한 쌍이다.retries와delay로 재시도 횟수와 간격을 정한다. 조회용 command에는changed_when: false를 함께 둔다. - tags로 부분 실행한다.
--tags로 선택,--skip-tags로 제외,--list-tags로 확인한다.always는 항상,never는 명시할 때만 실행된다.
정리 #
이번 글에서 잡은 것:
- when. 조건부 실행. fact,변수 기반 분기, and(리스트),or(표현식),
is defined - loop. 리스트 반복.
item참조, 사용자 일괄 생성,dict2items,loop_control의loop_var와label - 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 비밀번호 파일로 플레이북을 실행하며, 평문과 암호문을 분리해 관리하는 시험 단골 패턴까지 직접 만들어 보며 정리하겠습니다.