이전 포스팅에서 nginx를 설치하는 playbook을 작성했습니다. 이번에는 패키지명을 하드코딩하지 않고 **변수(vars)**로 관리하고, **조건문(when)**으로 상황에 따라 task 실행을 분기하는 방법을 실습합니다.
Chap 1에서 작성한 nginx.yml을 다시 보겠습니다.
---
- name: Ansible nginx install & execute
hosts: managed
become: true
gather_facts: false
tasks:
- name: apt install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: nginx service start
ansible.builtin.service:
name: nginx
state: started
nginx라는 패키지명이 여러 곳에 하드코딩되어 있습니다. 다른 패키지를 설치하려면 playbook 전체를 수정해야 합니다.
---
- name: "Ansible {{ package_name }} install & execute"
hosts: managed
gather_facts: false
vars:
package_name: nginx
tasks:
- name: "install {{ package_name }}"
become: true
ansible.builtin.apt:
name: "{{ package_name }}"
state: present
- name: "{{ package_name }} service start"
become: true
ansible.builtin.service:
name: "{{ package_name }}"
state: started
vars: — play 레벨에서 변수를 정의"{{ package_name }}" — Jinja2 문법으로 변수를 참조⚠️ 따옴표 필수
{{ }}를 사용할 때는 반드시 따옴표로 감싸야 합니다. YAML 파서가{를 객체 시작으로 인식하여 문법 오류가 발생하기 때문입니다.# OK name: "{{ package_name }}" # 에러 name: {{ package_name }}
변수를 정의하는 방법은 여러 가지입니다.
# 1. play 레벨 vars
- hosts: managed
vars:
port: 80
# 2. 별도 파일로 분리
- hosts: managed
vars_files:
- vars/common.yml
# 3. inventory에서 정의 (Chap 1에서 이미 사용)
[managed:vars]
ansible_user=ansible
# 4. CLI에서 주입 (가장 높은 우선순위)
ansible-playbook playbook/nginx.yml -e "package_name=curl"
Helm으로 비유하면 이렇습니다:
| Ansible | Helm |
|---|---|
vars: |
차트 내 기본값 |
vars_files: |
values.yaml |
-e (CLI) |
--set 옵션 |
우선순위도 비슷합니다. CLI(-e)로 넘긴 값이 가장 높고, vars:의 기본값이 가장 낮습니다.
변수로 리팩토링한 playbook에 CLI로 curl을 넘겨보겠습니다.
ansible-playbook playbook/install_and_execute.yml -e "package_name=curl"
TASK [install curl] ***************************************************
ok: [node1]
ok: [node3]
ok: [node2]
TASK [curl service start] *********************************************
fatal: [node2]: FAILED! => {"changed": false, "msg": "Could not find the requested service curl: host"}
fatal: [node3]: FAILED! => {"changed": false, "msg": "Could not find the requested service curl: host"}
fatal: [node1]: FAILED! => {"changed": false, "msg": "Could not find the requested service curl: host"}
curl은 설치는 성공하지만, 서비스가 아니기 때문에 service start task에서 실패합니다.
결과를 보면:
node1 : ok=1 changed=1 unreachable=0 failed=1 skipped=0
ok=1 — 첫 번째 task(설치)만 성공changed=1 — curl이 새로 설치됨failed=1 — 두 번째 task(서비스 시작)에서 실패서비스가 있는 패키지와 없는 패키지를 구분해서 처리할 방법이 필요합니다. 이것이 **조건문(when)**의 역할입니다.
when은 조건이 참일 때만 해당 task를 실행합니다.
서비스 시작이 필요한 패키지를 목록으로 정의하고, when으로 분기합니다:
---
- name: "Ansible {{ package_name }} install & execute"
hosts: managed
gather_facts: false
vars:
service_packages:
- nginx
tasks:
- name: "install {{ package_name }}"
become: true
ansible.builtin.apt:
name: "{{ package_name }}"
state: present
- name: "{{ package_name }} service start"
become: true
ansible.builtin.service:
name: "{{ package_name }}"
state: started
when: package_name in service_packages
service_packages — 서비스 시작이 필요한 패키지 목록when: package_name in service_packages — package_name이 목록에 포함될 때만 실행⚠️
when에서는{{ }}를 쓰지 않는다
when은 다른 키워드와 달리 이미 Jinja2 컨텍스트에서 평가됩니다.{{ }}를 쓰면 오히려 문자열로 변환되어 의도와 다르게 동작합니다.# OK when: package_name in service_packages # 잘못된 사용 when: "{{ package_name }} in {{ service_packages }}"
ansible-playbook playbook/install_and_execute.yml -e "package_name=nginx"
TASK [install nginx] **********************************************
ok: [node1]
ok: [node3]
ok: [node2]
TASK [nginx service start] ****************************************
ok: [node3]
ok: [node2]
ok: [node1]
PLAY RECAP ********************************************************
node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
node2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
node3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0
ok=2 — 설치와 서비스 시작 모두 성공changed=0 — 이미 설치되고 실행 중이므로 변경 없음 (멱등성)skipped=0 — nginx는 service_packages에 포함되어 있으므로 스킵 없음ansible-playbook playbook/install_and_execute.yml -e "package_name=curl"
TASK [install curl] ***********************************************
ok: [node3]
ok: [node2]
ok: [node1]
TASK [curl service start] *****************************************
skipping: [node1]
skipping: [node2]
skipping: [node3]
PLAY RECAP ********************************************************
node1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1
node2 : ok=1 changed=0 unreachable=0 failed=0 skipped=1
node3 : ok=1 changed=0 unreachable=0 failed=0 skipped=1
ok=1 — 설치만 성공skipped=1 — curl은 service_packages에 없으므로 서비스 시작 task가 스킵됨failed=0 — 에러 없이 정상 종료이전에는 failed=1로 playbook이 실패했지만, when 조건을 추가한 뒤에는 서비스가 없는 패키지도 안전하게 처리됩니다.
when: ansible_os_family == "Debian" # 문자열 비교
when: ansible_memtotal_mb >= 2048 # 숫자 비교
when: package_name is defined # 변수 존재 여부
when: package_name != "curl" # 불일치
when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "24.04" # AND
when: package_name == "nginx" or package_name == "apache2" # OR
쿠버네티스의 nodeSelector나 nodeAffinity로 특정 라벨의 노드에만 Pod을 배치하는 것처럼, when으로 특정 조건의 호스트에만 task를 실행할 수 있습니다.
특히 gather_facts: true로 수집한 시스템 정보(ansible_distribution, ansible_os_family 등)와 함께 사용하면, OS별로 다른 패키지 매니저를 분기하는 등 실용적인 활용이 가능합니다.