Ansible Role이란?
Playbook이 길어지면 유지보수가 어려워집니다. Ansible Role은 태스크, 핸들러, 변수, 템플릿, 파일을 표준 디렉토리 구조로 캡슐화하는 재사용 단위입니다. 한 번 만든 Role을 여러 프로젝트에서 가져다 쓸 수 있고, Ansible Galaxy를 통해 공유할 수도 있습니다. 이 글에서는 Role의 디렉토리 구조, 변수 우선순위, 의존성 관리, 테스트까지 실전 설계 패턴을 다룹니다.
Role 디렉토리 구조
ansible-galaxy role init으로 생성하면 표준 구조가 만들어집니다:
ansible-galaxy role init nginx
nginx/
├── defaults/
│ └── main.yml # 기본 변수 (가장 낮은 우선순위)
├── vars/
│ └── main.yml # Role 내부 변수 (높은 우선순위)
├── tasks/
│ └── main.yml # 메인 태스크 진입점
├── handlers/
│ └── main.yml # 핸들러 (서비스 재시작 등)
├── templates/
│ └── nginx.conf.j2 # Jinja2 템플릿
├── files/
│ └── index.html # 정적 파일
├── meta/
│ └── main.yml # Role 메타데이터·의존성
├── tests/
│ ├── inventory
│ └── test.yml # 테스트 Playbook
└── README.md
각 디렉토리의 역할:
| 디렉토리 | 용도 | 자동 로드 |
|---|---|---|
| defaults/ | 사용자가 오버라이드할 수 있는 기본값 | ✅ |
| vars/ | Role 내부에서만 쓰는 고정 변수 | ✅ |
| tasks/ | 실행할 태스크 목록 | ✅ (main.yml) |
| handlers/ | notify로 호출되는 핸들러 | ✅ |
| templates/ | Jinja2 템플릿 파일 | template 모듈에서 참조 |
| files/ | 정적 파일 | copy 모듈에서 참조 |
| meta/ | 의존 Role, 플랫폼 정보 | ✅ |
실전 Role 설계: Nginx 예제
프로덕션 수준의 Nginx Role 전체 구현입니다:
# defaults/main.yml — 사용자가 오버라이드할 수 있는 변수
---
nginx_version: "latest"
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: "10m"
nginx_vhosts: []
# - server_name: example.com
# root: /var/www/example
# ssl: true
# ssl_cert: /etc/ssl/certs/example.crt
# ssl_key: /etc/ssl/private/example.key
nginx_upstreams: []
# - name: backend
# servers:
# - "127.0.0.1:8080 weight=3"
# - "127.0.0.1:8081"
nginx_rate_limiting_enabled: false
nginx_rate_limit_zone: "limit_per_ip:10m rate=10r/s"
nginx_log_format: |
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"'
# vars/main.yml — Role 내부 고정 변수 (OS별 분기)
---
nginx_packages:
Debian: [nginx, nginx-extras]
RedHat: [nginx]
nginx_service_name: nginx
nginx_conf_dir: /etc/nginx
nginx_log_dir: /var/log/nginx
# tasks/main.yml — 태스크를 파일별로 분리
---
- name: Include OS-specific variables
include_vars: "{{ ansible_os_family }}.yml"
tags: [nginx, nginx:install]
- name: Install Nginx
import_tasks: install.yml
tags: [nginx, nginx:install]
- name: Configure Nginx
import_tasks: configure.yml
tags: [nginx, nginx:config]
- name: Configure virtual hosts
import_tasks: vhosts.yml
tags: [nginx, nginx:vhosts]
- name: Ensure Nginx is running
import_tasks: service.yml
tags: [nginx, nginx:service]
# tasks/install.yml
---
- name: Install Nginx packages
ansible.builtin.package:
name: "{{ nginx_packages[ansible_os_family] }}"
state: present
become: true
- name: Create required directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ nginx_conf_dir }}/sites-available"
- "{{ nginx_conf_dir }}/sites-enabled"
- "{{ nginx_conf_dir }}/snippets"
- "{{ nginx_log_dir }}"
become: true
# tasks/configure.yml
---
- name: Deploy nginx.conf
ansible.builtin.template:
src: nginx.conf.j2
dest: "{{ nginx_conf_dir }}/nginx.conf"
owner: root
group: root
mode: "0644"
validate: "nginx -t -c %s"
become: true
notify: Reload Nginx
# handlers/main.yml
---
- name: Reload Nginx
ansible.builtin.systemd:
name: "{{ nginx_service_name }}"
state: reloaded
become: true
- name: Restart Nginx
ansible.builtin.systemd:
name: "{{ nginx_service_name }}"
state: restarted
become: true
템플릿 설계 패턴
Jinja2 템플릿에서 조건부 블록과 반복문을 활용합니다:
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections {{ nginx_worker_connections }};
}
http {
include mime.types;
default_type application/octet-stream;
log_format main {{ nginx_log_format }};
access_log {{ nginx_log_dir }}/access.log main;
sendfile on;
keepalive_timeout {{ nginx_keepalive_timeout }};
client_max_body_size {{ nginx_client_max_body_size }};
{% if nginx_rate_limiting_enabled %}
limit_req_zone {{ nginx_rate_limit_zone }};
{% endif %}
{% for upstream in nginx_upstreams %}
upstream {{ upstream.name }} {
{% for server in upstream.servers %}
server {{ server }};
{% endfor %}
}
{% endfor %}
include {{ nginx_conf_dir }}/sites-enabled/*;
}
Role 의존성 관리
meta/main.yml에서 다른 Role에 대한 의존성을 선언합니다:
# meta/main.yml
---
galaxy_info:
role_name: nginx
author: theokei
description: Production-grade Nginx role
license: MIT
min_ansible_version: "2.14"
platforms:
- name: Ubuntu
versions: [focal, jammy]
- name: EL
versions: [8, 9]
dependencies:
- role: common
vars:
common_packages: [curl, vim]
- role: firewall
vars:
firewall_allowed_ports: [80, 443]
# requirements.yml — 외부 Role 의존성
---
roles:
- name: geerlingguy.certbot
version: "6.1.0"
- name: geerlingguy.firewall
version: "3.1.0"
collections:
- name: community.general
version: ">=7.0.0"
# 의존성 설치
ansible-galaxy install -r requirements.yml
ansible-galaxy collection install -r requirements.yml
변수 우선순위 이해
Ansible의 변수 우선순위는 22단계로 세분화되어 있습니다. Role과 관련된 핵심만 정리하면:
# 낮은 순위 → 높은 순위
1. role defaults/main.yml # 가장 낮음 (오버라이드 목적)
2. inventory group_vars/
3. inventory host_vars/
4. play vars:
5. play vars_files:
6. role vars/main.yml # 높음 (내부 고정값)
7. include_vars 모듈
8. set_fact / registered vars
9. play vars_prompt:
10. extra vars (-e) # 가장 높음
설계 원칙:
defaults/— 사용자가 바꿀 수 있어야 하는 값 (포트, 버전, 설정값)vars/— Role 내부에서만 쓰는 고정값 (패키지 이름, 경로)- 절대
vars/에 사용자 설정을 넣지 마세요 — 오버라이드가 어렵습니다
Molecule로 Role 테스트
Molecule은 Ansible Role의 자동화된 테스트 프레임워크입니다. Docker 컨테이너에서 Role을 실행하고 검증합니다:
# Molecule 초기화
pip install molecule molecule-docker
cd roles/nginx
molecule init scenario --driver-name docker
# molecule/default/molecule.yml
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu-test
image: ubuntu:22.04
pre_build_image: true
privileged: true
command: /sbin/init
- name: rocky-test
image: rockylinux:9
pre_build_image: true
privileged: true
command: /sbin/init
provisioner:
name: ansible
playbooks:
converge: converge.yml
verifier:
name: ansible
# molecule/default/converge.yml
---
- name: Converge
hosts: all
roles:
- role: nginx
vars:
nginx_worker_connections: 2048
nginx_vhosts:
- server_name: test.example.com
root: /var/www/test
# molecule/default/verify.yml
---
- name: Verify
hosts: all
tasks:
- name: Check nginx is installed
ansible.builtin.command: nginx -v
register: nginx_version
changed_when: false
- name: Assert nginx is running
ansible.builtin.service_facts:
- name: Verify nginx service
ansible.builtin.assert:
that:
- "'nginx' in services"
- "services['nginx']['state'] == 'running'"
- name: Check config syntax
ansible.builtin.command: nginx -t
changed_when: false
- name: Verify vhost config exists
ansible.builtin.stat:
path: /etc/nginx/sites-available/test.example.com
register: vhost_config
- name: Assert vhost config
ansible.builtin.assert:
that: vhost_config.stat.exists
# 테스트 실행
molecule test # 전체 (create → converge → verify → destroy)
molecule converge # Role 적용만
molecule verify # 검증만
molecule login # 컨테이너 접속 디버깅
멀티 OS 지원 패턴
OS별로 다른 패키지명, 경로, 서비스명을 깔끔하게 처리하는 패턴입니다. Ansible Playbook 자동화 심화에서 다룬 패턴을 Role에 적용합니다:
# vars/Debian.yml
---
nginx_packages: [nginx, nginx-extras]
nginx_user: www-data
nginx_conf_path: /etc/nginx/nginx.conf
# vars/RedHat.yml
---
nginx_packages: [nginx]
nginx_user: nginx
nginx_conf_path: /etc/nginx/nginx.conf
# tasks/main.yml에서 자동 로드
- name: Include OS-specific variables
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
프로젝트 구조 베스트 프랙티스
여러 Role을 조합한 전체 프로젝트 구조:
infrastructure/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ ├── webservers.yml
│ │ └── dbservers.yml
│ └── staging/
├── playbooks/
│ ├── site.yml # 전체 배포
│ ├── webservers.yml # 웹서버만
│ └── dbservers.yml # DB만
├── roles/
│ ├── common/ # 공통 패키지, 사용자, SSH
│ ├── nginx/ # 웹서버
│ ├── postgresql/ # DB
│ └── monitoring/ # 모니터링
└── requirements.yml # 외부 Role 의존성
# playbooks/site.yml
---
- name: Apply common configuration
hosts: all
roles:
- common
- name: Configure web servers
hosts: webservers
roles:
- nginx
- { role: certbot, when: use_ssl | default(false) }
- name: Configure databases
hosts: dbservers
roles:
- postgresql
마치며
Ansible Role은 인프라 코드를 재사용 가능하고 테스트 가능한 단위로 구조화하는 핵심 패턴입니다. defaults/vars 분리로 변수 우선순위를 제어하고, OS별 변수 파일로 멀티 플랫폼을 지원하며, Molecule로 자동화 테스트를 구축하세요. meta/main.yml의 의존성 선언과 requirements.yml로 외부 Role을 관리하면, 팀 전체가 공유할 수 있는 인프라 코드 라이브러리를 만들 수 있습니다.