Profile picture

[Ansible] Roll(롤)을 활용하여 플레이북 작성하기

JaehyoJJAng2023년 04월 03일

롤 구조

플레이북을 개발하다보면 이전에 작성한 플레이북의 코드를 재사용할 기회가 많다는 것을 깨닫게 된다.

이때 앤서블에서 제공하는 롤을 사용하면 일반적인 앤서블 코드를 더 쉽게 재사용 할 수 있다.

인프라를 프로비저닝하거나 애플리케이션을 배포하는 데 필요한 모든 작업, 변수, 파일, 기타 리소스등을 표준화된 디렉토리 구조로 패키징하는데 도움이 된다.

아니면 디렉토리를 복사해 롤을 다른 프로젝트로 복사한 다음 다음 플레이 내에서 호출할 수도 있다.


또한 플레이북에서 전달된 변수를 롤에서도 사용할 수 있다.

변수는 사이트별 호스트 이름, IP 주소, 사용자 이름, 기타 로컬 특정 세부 정보를 설정하여 롤의 동작 수정이 가능하다.

물론 롤를 호출하는 플레이북에 해당 변수 설정이 없을 시 기본값을 롤의 해당 변수에 설정하기도 한다.


앤서블 롤 구조

앤서블 롤은 하위 디렉토리 및 파일의 표준화된 구조에 의해 정의된다.

최상위 디렉토리는 롤 자체의 이름을 의미하고, 그 안은 tasks 및 handlers 등 롤에서 목적에 따라 정의된 하위 디렉토리로 구성된다.

아래 표는 최상위 디렉토리 아래에 있는 하위 디렉토리의 이름과 기능을 설명한 것이다.

하위 디렉토리 기능
defaults 이 디렉토리의 main.yaml 파일에는 롤이 사용될 때 덮어쓸 수 있는 롤 변수의 기본값이 포함되어 있다. 이러한 변수는 우선순위가 낮으며 플레이에서 변경 가능하다
files 이 디렉토리에는 롤 작업에서 참조한 정적 파일이 존재한다
handlers 이 디렉토리의 main.yaml 파일에는 롤의 핸들러 정의가 포함되어 있다
meta 이 디렉토리의 main.yaml 파일에는 작성자, 라이센스, 플랫폼 및 옵션, 롤 종속성 등을 포함한 롤에 대한 정보가 들어있다
tasks 이 디렉토리의 main.yaml 파일에는 롤의 작업 정의가 포함되어 있다
templates 이 디렉토리에는 롤 작업에서 참조할 Jinja2 템플릿이 있다
vars 이 디렉토리의 main.yaml 파일은 롤의 변수 값을 정의한다. 종종 이러한 변수는 롤 내에서 내부 목적으로 사용된다. 또한 우선순위가 높아 플레이북에서 사용될 때 변경되지 않는다

롤 생성 방법

예제를 통해 롤을 생성해보고 그 구조를 살펴보자.


앤서블에서 롤을 생성할 때는 ansible-galaxy role 명령어를 사용한다.

다음과 같이 앤서블이 설치된 앤서블 제어 서버에서 ansible-galaxy role init <role name>으로 롤 생성이 가능하다.


명령어 ansible-galaxy role init을 사용하여 my-role 이라는 롤을 생성해보자.

$ ansible-galaxy role init my-role

- Role my-role was created successfully

다음과 같이 롤이 생성되었다는 메시지 확인이 가능하다.


이번에는 tree 명령어로 my-role 디렉토리의 구조를 살펴보자.

$ tree ./my-role

./my-role
├── README.md
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

앞서 살펴본 롤 구조와 동일하게 하위 디렉토리들이 존재함을 볼 수 있다.


롤을 이용한 플레이북 개발

이번에는 앞에서 살펴본 롤 구조에 맞게 아주 심플한 롤 플레이북을 개발해보자.

심플한 롤 플레이북은 다음과 같은 프로세스를 거쳐 각 구조에 맞게 태스크를 작성한다.


프로세스

  • 롤이 호출되면 현재 호스트의 운영체제 버전이 지원 운영체제 목록에 포함되는지 확인
  • 운영체제가 Ubuntu 이면 apache2 패키지를 apt 모듈을 이용해 설치
  • 설치가 끝나면 제어 노드의 files 디렉토리 안에 있는 index.html 파일을 관리 노드의 /var/www/html 디렉토리에 복사
  • 파일 복사가 끝나면 apache2 서비스 재시작

롤 구조

  • 롤 이름: my-role
  • tasks (메인 태스크)
    • install service: apache2 관련 패키지 설치
    • copy html file: index.html 파일 복사
  • files (정적 파일)
    • index.html
  • handlers
    • restart service: apache2 서비스 재시작
  • defaults (가변 변수): 메인 태스크에서 사용된 변수 선언
    • service_title
  • vars (불변 변수): 메인 태스크와 핸들러에서 사용된 변수 선언
    • service_name: 서비스명
    • src_file_path: 복사할 파일 경로
    • dest_file_path: 파일이 복사될 디렉토리 경로
    • apache2_packages apache2 관련 패키지 목록
    • supported_distros: 지원 OS 목록

플레이북 작성

이제 프로세스와 롤의 구조에 맞게 플레이북을 작성해보자.

앞에서 생성한 my-role 디렉토리로 전환한 뒤 tasks/main.yaml 파일에 다음과 같은 내용을 입력하자.

첫 번째 태스크인 install service 에는 플레이북에서 변수로 정의한 서비스명을 함께 출력한다.

그리고 ansible.builtin.apt 모듈을 이용하여 apache2 관련 패키지를 설치한다.

이때 관련 패키지는 여러 개이며 loop 문을 사용하도록 한다.

서비스 설치가 끝나면 ansible.builtin.copy 모듈을 이용하여 파일을 복사하고 복사가 끝나면 restart service 핸들러를 호출한다.

$ vi tasks/main.yaml
---
# tasks file for my-role

- name: install service {{ service_title }}
  ansible.builtin.apt:
    name: "{{ item }}"
    state: latest
  loop: "{{ apache2_packages }}"
  when: ansible_facts.distribution in supported_distros

- name: copy html file
  ansible.builtin.copy:
    src: "{{ src_file_path }}"
    dest: "{{ dest_file_path }}"
  notify:
    - restart service

그리고 files/index.html 파일을 생성하자. 내용은 Hello! Ansible. 이다.

$ echo "Hello! Ansible." | tee files/index.html
Hello! Ansible

이번에는 핸들러를 작성해보자. 핸들러는 특정 태스크가 끝나고 그 다음에 수행해야 하는 태스크를 작성하며,

롤 디렉토리의 handlers/main.yaml 파일에 작성한다.

아래 핸들러는 ansible.builtin.service 모듈을 사용하여 서비스를 재시작한다.

$ vi handlers/main.yaml
---
# handlers file for my-role

- name: restart service
  ansible.builtin.service:
    name: "{{ service_name }}"
    state: restarted

그리고 가변 변수 작성을 해줘야 하는데 이건 defaults/main.yaml 파일에서 작성하면 된다.

위 파일에 작성되는 변수는 외부로부터 재정의될 수 있는 가변 변수이다.

여기서 service_title을 외부에서 받아 수정이 가능하도록 했으며, 기본 값은 Apache Web Server 이다.

$ vi defaults/main.yaml
---
# defaults file for my-role

service_title: "Apache Web Server"

마지막으로 vars/main.yaml 파일에는 불변 변수가 정의되는데, 한 번 정의되면 외부로부터 변수 값을 수정할 수 없다.

따라서 롤 내의 플레이북에서만 사용되는 변수로 정의하는 것을 권장한다.

아래 플레이북에서는 service_name, src_file_path, dest_file_path, apache_packages, supported_distros 라는 변수가 정의되었다.

$ vi vars/main.yaml
---
# vars file for my-role

service_name: apache2
src_file_path:
dest_file_path:
apache2_packages:
  - apache2
supported_distros:
  - Ubuntu

플레이북에 롤 추가하기

롤을 생성하고 롤 구조에 맞게 플레이북을 작성했지만, 지금까지와 같이 ansible-playbook 명령어를 이용해 실행할 수는 없다.

롤을 실행하기 위해서는 롤을 호출해주는 플레이북이 필요하다.

플레이북에서 롤를 추가하려면

ansible.builtin.import_role 모듈을 이용하는 방법과

ansible.builtin.include_role 모듈을 이용하는 방법이 있다.

이 두 가지 모듈에는 차이점이 있다.

ansible.builtin.import_role 모듈은 롤을 정적으로 추가하며,

ansbile.builtin.include_role 모듈은 롤으 동적으로 추가한다.

정적으로 롤을 추가한다는 건 고정된 롤을 추가하겠다는 의미이며,

동적으로 롤을 추가한다는 건 반복문이나 조건문에 의해 롤이 변경될 수 있다는 의미이다.

이번 예제에서는 ansible.builtin.import_role를 이용하여 롤을 추가해보도록 하겠다.


그전에, 관리 노드 대상을 지정한 인벤토리(Inventory) 파일을 /my-ansible/inventory로 생성해주자.

ubuntu1
ubuntu2
[web]
ubuntu1

[db]
ubuntu2 user=ansible1

[all:children]
web
db

그리고 /my-ansible 디렉토리로 이동 후 vi 에디터를 이용해 role-example.yaml 이라는 파일을 생성하고 아래와 같은 내용을 작성하자.

---
- hosts: ubuntu1
  tasks:
    - name: Print start play
      ansible.builtin.debug:
        msg: "Let's start role play!"

    - name: Install Service by role
      ansible.builtin.import_role:
        name: my-role
      vars:
        service_title: Httpd

생성된 플레이북을 실행하면 다음과 같이 install service Httpd로 태스크명이 변경된 것을 확인할 수 있다.

이번 플레이북은 이전 게시글에서 실행되던 플레이북들과는 조금 다른 결과를 보이는데,

이는 apache2 패키지가 이전 게시글에서 플레이북 연습을 할 때 이미 설치되었기 때문이다.

따라서 이번 플레이북에서는 패키지를 재설치하거나 파일을 다시 복사하지 않는다.

$ ansible-playbook -i ./inventory role-example.yaml

TASK [Print start play] ****************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => {
    "msg": "Let's start role play!"
}

TASK [my-role : install service Httpd] *************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => (item=apache2)

TASK [my-role : copy html file] ********************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

RUNNING HANDLER [my-role : restart service] ********************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]

PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1                    : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Loading script...