Ansible Role 모듈화 설계

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을 관리하면, 팀 전체가 공유할 수 있는 인프라 코드 라이브러리를 만들 수 있습니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux