핸들러 — 변경이 있을 때만 실행되는 태스크
핸들러 — 변경이 있을 때만 실행되는 태스크
가끔은 태스크가 머신에서 변경을 만들 때만 실행되길 원할 때가 있어요. 예를 들어 어떤 태스크가 서비스의 설정 파일을 갱신하면 그 서비스를 재시작하고 싶지만, 설정이 바뀌지 않았다면 재시작하고 싶지 않죠. Ansible은 이 상황을 핸들러(handler)로 해결해요. 핸들러는 통지(notify)를 받았을 때만 실행되는 태스크예요.
이 글에서는 핸들러의 기본 사용법, 이름 짓는 법, 실행 시점을 제어하는 방법을 살펴볼게요.
출처: 공식문서
핸들러 예시
verify-apache.yml 플레이북은 핸들러 하나를 담은 단일 플레이로 구성돼요.
---
- name: Verify apache installation
hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
notify:
- Restart apache
- name: Ensure apache is running
ansible.builtin.service:
name: httpd
state: started
handlers:
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
이 예시에서 Apache 서버는 플레이의 모든 태스크가 끝난 뒤 핸들러에 의해 재시작돼요.
핸들러 이름 짓기
태스크가 notify 키워드로 통지할 수 있으려면 핸들러는 이름이 있어야 해요.
대신 핸들러는 listen 키워드도 사용할 수 있어요. listen을 쓰면 여러 핸들러를 묶어주는 주제(topic)에 귀를 기울일 수 있어요.
tasks:
- name: Restart everything
command: echo "this task will restart the web services"
notify: "restart web services"
handlers:
- name: Restart memcached
service:
name: memcached
state: restarted
listen: "restart web services"
- name: Restart apache
service:
name: apache
state: restarted
listen: "restart web services"
restart web services 주제를 통지하면 이름과 상관없이 그 주제를 듣는 모든 핸들러가 실행돼요. 이 방식은 여러 핸들러를 훨씬 쉽게 트리거하게 해주고, 핸들러를 이름에서 분리해 플레이북과 역할(role) 사이에서 공유하기 쉽게 만들어요. 특히 Ansible Galaxy 같은 공유 소스의 서드파티 역할을 쓸 때 유용하죠.
각 핸들러는 전역적으로 유일한 이름을 가져야 해요. 같은 이름의 핸들러가 여러 개 정의되면, 플레이에 마지막으로 로드된 핸들러만 통지·실행될 수 있고 이전 핸들러들은 사실상 가려져요.
핸들러가 플레이에 들어가는 순서
핸들러는 어디에 정의돼 있든(handlers: 섹션이든 역할이든) 전역적인 단일 플레이 수준 스코프만 있어요. 핸들러가 플레이에 추가되는 순서는 다음과 같아요.
roles:섹션의 역할에서 온 핸들러handlers:섹션의 핸들러import_role태스크로 정적으로 가져온 역할의 핸들러include_role태스크로 동적으로 포함된 역할의 핸들러(런타임에include_role태스크가 실행된 뒤에만 사용 가능)
위 순서에 따라 같은 이름의 핸들러가 있으면 마지막에 로드된 핸들러가 통지·실행될 수 있어요.
핸들러가 실행되는 시점 제어하기
기본적으로 핸들러는 특정 플레이의 모든 태스크가 끝난 뒤 실행돼요. 통지된 핸들러는 각 섹션(pre_tasks, roles/tasks, post_tasks)이 끝난 뒤 이 순서대로 자동 실행돼요. 이 방식은 효율적이에요. 몇 개의 태스크가 통지하든 핸들러는 한 번만 실행되거든요. 예를 들어 여러 태스크가 설정 파일을 갱신하고 Apache 재시작 핸들러를 통지해도, Ansible은 불필요한 재시작을 피하려고 Apache를 한 번만 재시작해요.
플레이가 끝나기 전에 핸들러를 실행해야 한다면, 메타(meta) 모듈로 flush하는 태스크를 추가해요.
tasks:
- name: Some tasks go here
ansible.builtin.shell: ...
- name: Flush handlers
meta: flush_handlers
- name: Some other tasks
ansible.builtin.shell: ...
meta: flush_handlers 태스크는 그 시점까지 플레이에서 통지된 모든 핸들러를 트리거해요.
핸들러는 각 섹션 뒤에 자동으로, 또는 flush_handlers 메타 태스크로 수동으로 실행된 뒤에는, 플레이의 이후 섹션에서 다시 통지되어 다시 실행될 수 있어요.
태스크가 언제 변경되는지 정의하기
changed_when 키워드로 핸들러가 태스크 변경에 대해 언제 통지받을지 제어할 수 있어요.
다음 예시에서 핸들러는 설정 파일을 복사할 때마다 서비스를 재시작해요.
tasks:
- name: Copy httpd configuration
ansible.builtin.copy:
src: ./new_httpd.conf
dest: /etc/httpd/conf/httpd.conf
# The task is always reported as changed
changed_when: True
notify: Restart apache
changed_when에 대한 자세한 내용은 "Defining changed" 문서를 참고해요.
핸들러에서 변수 사용하기
Ansible 핸들러가 변수를 사용하길 원할 수 있어요. 예를 들어 분포에 따라 서비스 이름이 조금씩 다르다면, 대상 머신마다 재시작한 서비스의 정확한 이름을 출력에 보여주고 싶을 거예요. 핸들러의 이름에는 변수를 두지 않는 게 좋아요. 핸들러 이름은 일찍 템플릿화되기 때문에, 이런 이름에 쓸 값이 아직 없을 수 있어요.
handlers:
# This handler name may cause your play to fail!
- name: Restart "{{ web_service_name }}"
핸들러 이름에 쓴 변수를 쓸 수 없으면 플레이 전체가 실패해요. 플레이 도중 그 변수를 바꿔도 새 핸들러가 생기지 않아요.
대신 변수는 핸들러의 태스크 매개변수에 두세요. include_vars로 값을 불러올 수 있어요.
tasks:
- name: Set host variables based on distribution
include_vars: "{{ ansible_facts.distribution }}.yml"
handlers:
- name: Restart web service
ansible.builtin.service:
name: "{{ web_service_name | default('httpd') }}"
state: restarted
핸들러 이름은 템플릿을 담을 수 있지만, listen 주제는 템플릿을 담을 수 없어요.
역할에서의 핸들러
역할의 핸들러는 역할 안에만 갇혀 있지 않고 플레이의 다른 모든 핸들러와 함께 전역 스코프에 삽입돼요. 그래서 정의된 역할 밖에서도 사용할 수 있어요. 동시에 역할 밖의 핸들러와 이름이 충돌할 수도 있다는 뜻이죠. 역할의 핸들러가 역할 밖의 같은 이름의 핸들러 대신 통지되도록 하려면 role_name:handler_name 형태로 이름을 사용해 통지해요.
roles 섹션 안에서 통지된 핸들러는 tasks 섹션 끝에 자동으로 flush돼요.
핸들러에서의 include와 import
include_task 같은 동적 include를 핸들러로 통지하면 그 include 안의 모든 태스크가 실행돼요. 동적 include 안에 정의된 핸들러는 통지할 수 없어요.
import_task 같은 정적 include를 핸들러로 쓰면, 플레이 실행 전에 그 import 안의 핸들러가 실질적으로 그 핸들러를 다시 작성해요. 정적 include 자체는 통지할 수 없지만, 그 include 안의 태스크는 개별적으로 통지할 수 있어요.
메타 태스크를 핸들러로
Ansible 2.14부터 메타 태스크를 핸들러로 사용·통지할 수 있어요. 다만 flush_handlers는 예상치 못한 동작을 막기 위해 핸들러로 쓸 수 없어요.
제한 사항
핸들러는 import_role도 include_role도 실행할 수 없어요. 핸들러는 태그를 무시해요(tags on handlers).
더 알아보기
- 플레이북 기초: 플레이와 태스크의 기본 구조
- 조건문(Conditionals):
when을 사용한 조건부 실행 changed_when: 태스크가 '변경됨'으로 보고되는 시점 정의- 역할(Roles):
roles:섹션과include_role/import_role사용법