Red Hat Certified Engineer (RHCE) #6 변수와 fact: 우선순위, magic vars, custom facts

9 분 소요

#5 Playbook 기초에서 task와 handler로 멱등성 있는 플레이북을 작성하는 법을 잡았습니다. 다만 그때까지의 플레이북은 값이 코드에 박혀 있었습니다. 호스트마다 다른 패키지명, 포트, 경로를 한 플레이북으로 다루려면 변수가 필요합니다. 그리고 호스트 자신의 정보(IP, 메모리, OS 버전)를 플레이북이 알아야 조건 분기를 할 수 있는데, 그 정보를 Ansible이 자동으로 모아 주는 것이 fact입니다.

이번 글은 변수를 정의하는 여러 위치와 그 우선순위, fact의 수집과 활용, task 결과를 담는 register, 인벤토리 전체를 들여다보는 magic 변수, 그리고 직접 만드는 custom facts까지 실기 관점으로 정리하겠습니다.

변수가 필요한 이유 #

같은 플레이북을 web 그룹과 db 그룹에 돌릴 때, 설치할 패키지나 열 포트가 다릅니다. 값을 task 안에 직접 적으면 그룹마다 플레이북을 따로 만들어야 합니다. 변수는 값을 한곳에 모으고 task에서는 이름으로 참조하게 만들어, 같은 플레이북이 호스트마다 다른 값으로 동작하도록 합니다.

변수 이름은 영문자,숫자,밑줄만 쓰고 숫자로 시작하지 않습니다. http-port처럼 하이픈을 쓰면 안 되고 http_port처럼 밑줄을 씁니다. 시험에서 변수명을 임의로 정해도 되지만, 문제에 변수명이 지정되어 있으면 그 이름을 그대로 써야 채점됩니다.

변수를 정의하는 위치 #

Ansible은 변수를 여러 곳에서 정의할 수 있습니다. 실기에서 자주 쓰는 위치를 정리하겠습니다.

1) play의 vars #

플레이북 안에 직접 적는 가장 단순한 방법입니다.

play의 vars
---
- name: 변수를 play에 직접 정의
  hosts: web
  vars:
    http_port: 8080
    web_package: httpd
  tasks:
    - name: 패키지 설치
      ansible.builtin.dnf:
        name: "{{ web_package }}"
        state: present

2) vars_files로 외부 파일 참조 #

변수를 별도 파일로 빼고 플레이북에서 불러옵니다. 변수가 많아질 때 정리가 쉽습니다.

vars_files로 로드
- name: 변수를 외부 파일에서 로드
  hosts: web
  vars_files:
    - vars/web_vars.yml
  tasks:
    - name: 패키지 설치
      ansible.builtin.dnf:
        name: "{{ web_package }}"
        state: present
vars/web_vars.yml
# vars/web_vars.yml
http_port: 8080
web_package: httpd

3) group_vars와 host_vars #

#2에서 다룬 인벤토리 기반 변수입니다. 플레이북 옆에 디렉터리를 두면 Ansible이 자동으로 읽습니다.

프로젝트 구조
project/
├── inventory
├── playbook.yml
├── group_vars/
│   ├── all.yml      # 모든 호스트 공통
│   └── web.yml      # web 그룹 전용
└── host_vars/
    └── node1.yml    # node1 호스트 전용
group_vars/web.yml
# group_vars/web.yml
http_port: 8080
web_package: httpd

호스트별로 값을 달리해야 하면 host_vars가 group_vars보다 우선합니다. web 그룹 전체는 8080을 쓰되 node1만 8443을 써야 한다면, host_vars/node1.yml에 http_port: 8443을 적습니다.

4) 명령줄의 extra-vars #

ansible-playbook 실행 시 -e(또는 --extra-vars)로 넘기는 변수입니다. 다른 모든 정의를 덮어쓰는 최우선 변수입니다.

extra-vars로 덮어쓰기
ansible-playbook playbook.yml -e "http_port=9090"
ansible-playbook playbook.yml -e "@vars/override.yml"

5) register로 task 결과 저장 #

task의 실행 결과를 변수에 담는 방법입니다. 뒤에서 따로 다루겠습니다.

변수 참조 문법 #

정의한 변수는 {{ 변수명 }} 형태로 참조합니다. 이중 중괄호는 Jinja2 표현식이며, 자세한 활용은 #7에서 다룹니다.

변수 참조
tasks:
  - name: 변수 참조
    ansible.builtin.debug:
      msg: "포트는 {{ http_port }} 입니다"

값이 변수만으로 시작하면 YAML이 그 줄을 객체로 오해하므로, 따옴표로 감싸야 합니다.

따옴표로 감싸기
# 잘못된 예: 콜론 뒤가 바로 중괄호로 시작
- name: 오류
  ansible.builtin.debug:
    msg: {{ http_port }}

# 올바른 예: 따옴표로 감쌈
- name: 정상
  ansible.builtin.debug:
    msg: "{{ http_port }}"

딕셔너리 값과 리스트 항목은 점 표기 또는 대괄호 표기로 접근합니다.

딕셔너리와 리스트 접근
vars:
  users:
    admin:
      shell: /bin/bash
  packages:
    - httpd
    - mariadb-server
tasks:
  - name: 딕셔너리와 리스트 접근
    ansible.builtin.debug:
      msg: "{{ users.admin.shell }} / {{ packages[0] }}"

변수 우선순위 #

같은 이름의 변수가 여러 곳에 정의되면 우선순위가 높은 쪽이 이깁니다. Ansible의 우선순위 규칙은 길지만, 실기에서 기억할 핵심은 extra-vars(-e)가 항상 최우선이라는 점입니다. 자주 마주치는 위치만 대략적인 순서로 정리하겠습니다.

순위변수 위치비고
높음extra-vars (-e)무엇이든 덮어씀. 최우선
task의 vars해당 task에서만
block의 varsblock 범위
role과 include의 vars역할 호출 시 전달
play의 vars / vars_files플레이 전체
host_vars호스트 전용
group_vars (특정 그룹)그룹 전용
group_vars/all모든 호스트 공통
낮음role defaults가장 약함. 덮어쓰기 전제

전체 규칙을 외울 필요는 없습니다. 시험에서는 “이 값을 강제로 적용하라"가 나오면 extra-vars를 떠올리고, host_vars가 group_vars를 이기며, role defaults가 가장 약하다는 세 가지만 기억하면 충분합니다.

fact: 호스트의 정보를 수집 #

fact는 Ansible이 관리 노드에 접속해 자동으로 모으는 시스템 정보입니다. IP, OS 버전, 메모리, CPU, 디스크, 네트워크 인터페이스 등이 들어 있습니다. 호스트마다 다른 이 값으로 조건 분기와 템플릿을 만듭니다.

gather_facts #

play가 시작될 때 Ansible은 기본으로 fact를 수집합니다. 출력에서 보이는 Gathering Facts 단계가 그것입니다. 수집을 끄려면 gather_facts: false를 둡니다. fact가 필요 없는 짧은 플레이북은 끄면 실행이 빨라집니다.

fact 수집 끄기
- name: fact 수집을 끔
  hosts: web
  gather_facts: false
  tasks:
    - name: fact를 쓰지 않는 작업
      ansible.builtin.debug:
        msg: "빠르게 실행"

setup 모듈로 fact 확인 #

어떤 fact가 있는지 확인하려면 ad-hoc으로 setup 모듈을 실행합니다. fact 수집은 내부적으로 setup 모듈이 담당합니다.

setup 모듈로 fact 확인
ansible node1 -m setup
ansible node1 -m setup -a "filter=ansible_default_ipv4"
ansible node1 -m setup -a "filter=ansible_memory_mb"

filter로 원하는 fact만 추려 보면, 변수명을 정확히 확인할 수 있어 실기에서 시간을 아낍니다.

ansible_facts로 접근 #

수집된 fact는 ansible_facts 딕셔너리에 담깁니다. 권장 방식은 ansible_facts['키'] 형태입니다. 자주 쓰는 fact를 정리하겠습니다.

fact 참조의미
ansible_facts['hostname']호스트명
ansible_facts['default_ipv4']['address']기본 IPv4 주소
ansible_facts['memtotal_mb']총 메모리(MB)
ansible_facts['distribution']배포판명(RedHat 등)
ansible_facts['distribution_major_version']메이저 버전(9 등)
ansible_facts['processor_vcpus']가상 CPU 수
fact 활용
- name: fact 활용
  hosts: web
  tasks:
    - name: 호스트 정보 출력
      ansible.builtin.debug:
        msg: >-
          {{ ansible_facts['hostname'] }} /
          {{ ansible_facts['default_ipv4']['address'] }} /
          memory {{ ansible_facts['memtotal_mb'] }}MB

구형 표기와 신형 표기 #

예전 표기는 ansible_hostname, ansible_default_ipv4.address, ansible_memtotal_mb처럼 ansible_ 접두사를 붙인 평면 변수였습니다. 최신 권장은 ansible_facts['hostname'] 형태입니다. 둘 다 동작하므로 시험에서는 익숙한 쪽을 쓰되, 문서에 나온 표기와 통일하는 편이 안전합니다.

register: task 결과를 변수에 저장 #

task의 실행 결과(반환값, stdout, 변경 여부 등)를 변수에 담아 뒤 task에서 쓰는 것이 register입니다. 명령 출력으로 분기하거나, 다음 작업의 입력으로 넘길 때 씁니다.

register 활용
- name: register 활용
  hosts: web
  tasks:
    - name: 서비스 상태 조회
      ansible.builtin.command: systemctl is-active httpd
      register: httpd_status
      ignore_errors: true
      changed_when: false

    - name: 결과 출력
      ansible.builtin.debug:
        msg: "httpd 결과 코드는 {{ httpd_status.rc }} 입니다"

    - name: stdout으로 분기
      ansible.builtin.debug:
        msg: "httpd가 실행 중입니다"
      when: httpd_status.stdout == "active"

register로 담긴 변수에는 여러 키가 들어 있습니다. 자주 보는 키를 정리하겠습니다.

의미
.rc명령의 반환 코드
.stdout표준 출력 전체(문자열)
.stdout_lines표준 출력을 줄 단위 리스트로
.changed변경 발생 여부(true/false)
.failed실패 여부

어떤 키가 있는지 모를 때는 register한 변수를 debug로 통째로 출력해 구조를 확인합니다.

register 변수 구조 확인
- name: register 변수 구조 확인
  ansible.builtin.debug:
    var: httpd_status

조건 분기 자세한 내용(when, loop)은 #9에서 다룹니다.

magic 변수 #

magic 변수는 사용자가 정의하지 않아도 Ansible이 항상 제공하는 특수 변수입니다. 인벤토리 전체와 현재 실행 맥락을 들여다볼 때 씁니다. 시험에서 “다른 호스트의 IP를 가져와 설정에 넣어라” 유형에 직결됩니다.

magic 변수의미
inventory_hostname현재 처리 중인 호스트의 인벤토리 이름
hostvars모든 호스트의 변수와 fact에 접근하는 딕셔너리
groups그룹명에서 호스트 목록으로의 매핑
group_names현재 호스트가 속한 그룹 목록
ansible_play_hosts이번 play의 대상 호스트 목록
magic 변수 활용
- name: magic 변수 활용
  hosts: web
  tasks:
    - name: 자신의 인벤토리 이름
      ansible.builtin.debug:
        msg: "나는 {{ inventory_hostname }} 입니다"

    - name: 다른 호스트의 fact가져오기
      ansible.builtin.debug:
        msg: "node1의 IP는 {{ hostvars['node1']['ansible_facts']['default_ipv4']['address'] }} 입니다"

    - name: db 그룹의 호스트 목록
      ansible.builtin.debug:
        msg: "db 멤버: {{ groups['db'] }}"

hostvars는 다른 호스트가 이미 fact를 수집한 뒤라야 그 값을 읽을 수 있습니다. 한 play에서 모든 호스트의 fact를 먼저 수집하도록 대상 그룹을 잡는 것이 안전합니다.

custom facts #

기본 fact 외에 호스트에 직접 심어 두는 사용자 정의 fact가 custom facts입니다. 관리 노드의 /etc/ansible/facts.d/ 아래에 .fact 확장자 파일을 두면, fact 수집 시 자동으로 읽혀 ansible_local에 담깁니다.

.fact 파일은 INI 형식 또는 JSON 형식으로 작성합니다. INI 예시는 다음과 같습니다.

/etc/ansible/facts.d/custom.fact
# /etc/ansible/facts.d/custom.fact
[web]
package = httpd
port = 8080

[role]
tier = frontend

이 파일이 node1에 있으면, 수집된 값은 ansible_local을 통해 접근합니다. 구조는 ansible_local['파일명']['섹션']['키']입니다.

custom fact 접근
- name: custom fact 접근
  hosts: web
  tasks:
    - name: 로컬 fact 출력
      ansible.builtin.debug:
        msg: >-
          package {{ ansible_local['custom']['web']['package'] }} /
          tier {{ ansible_local['custom']['role']['tier'] }}

실기에서는 custom fact 파일을 플레이북으로 배포하는 작업이 함께 나오기도 합니다. copy 또는 template 모듈로 /etc/ansible/facts.d/에 파일을 두고, 같은 플레이북 뒤쪽에서 그 값을 쓰려면 setup 모듈로 fact를 다시 수집해야 그 시점에 ansible_local에 반영됩니다.

custom fact 배포와 재수집
- name: custom fact 배포 후 재수집
  hosts: web
  tasks:
    - name: facts.d 디렉터리 생성
      ansible.builtin.file:
        path: /etc/ansible/facts.d
        state: directory
        mode: '0755'

    - name: custom fact 파일 배포
      ansible.builtin.copy:
        src: files/custom.fact
        dest: /etc/ansible/facts.d/custom.fact
        mode: '0644'

    - name: fact 재수집
      ansible.builtin.setup:

    - name: 배포한 fact 확인
      ansible.builtin.debug:
        var: ansible_local['custom']

시험 포인트 #

  • extra-vars(-e)가 최우선 변수입니다. “이 값을 강제로 적용하라"가 나오면 떠올립니다.
  • host_vars가 group_vars보다 우선하고, group_vars/all이 가장 넓으며, role defaults가 가장 약합니다.
  • 변수 참조는 {{ }}로 하고, 값이 변수로 시작하면 따옴표로 감쌉니다.
  • fact는 ansible_facts['키']로 접근하고, 어떤 fact가 있는지는 ansible node -m setup -a "filter=..."로 확인합니다.
  • 자주 쓰는 fact는 hostname, default_ipv4.address, memtotal_mb, distribution_major_version입니다.
  • register로 task 결과를 담고, .rc,.stdout,.changed로 다음 task를 분기합니다. 구조가 헷갈리면 debug로 통째로 출력합니다.
  • 다른 호스트의 값은 hostvars['호스트']['...']로 가져오고, 그룹 멤버는 groups['그룹명']으로 얻습니다.
  • custom facts는 /etc/ansible/facts.d/*.fact에 두고 ansible_local로 접근합니다. 배포 직후에 쓰려면 setup 모듈로 재수집합니다.

정리 #

이번 글에서 잡은 것:

  • 변수 정의 위치. play vars, vars_files, group_vars/host_vars, extra-vars, register의 다섯 갈래
  • 우선순위. extra-vars 최우선, host_vars가 group_vars보다 우선, role defaults가 가장 약함
  • fact. gather_facts로 자동 수집, ansible_facts로 접근, setup 모듈로 확인
  • register. task 결과를 변수에 담아 .rc,.stdout,.changed로 분기
  • magic 변수. inventory_hostname, hostvars, groups로 인벤토리 전체를 들여다봄
  • custom facts. facts.d의 .fact 파일을 ansible_local로 읽고, 배포 후엔 재수집

변수와 fact로 호스트별 값을 다룰 토대를 잡았습니다. 이제 그 값을 가공해 설정 파일을 동적으로 생성하는 단계로 넘어갑니다.

다음: Jinja2 템플릿 #

변수와 fact를 모았으니, 이 값들을 조합해 호스트마다 다른 설정 파일을 만들 차례입니다.

#7 Jinja2 템플릿: 필터, 제어 흐름, lookup에서는 template 모듈과 .j2 파일, 변수 출력과 필터(default, upper, join 등), for,if 제어 흐름으로 반복 블록을 생성하는 법, 그리고 lookup으로 외부 데이터를 끌어오는 법까지 실기 예제로 정리하겠습니다.

X