Ansible Callback Plugin 소개

Ansible Plugin 중에서 Callback Plugin 에 관련한 부분만 정리합니다. Callback Plugin 은 Ansible 에서 특정 이벤트 발생 시 데이터를 로깅한다거나 Slack, Mail 등의 외부 채널로 Write 하는 등의 다양한 목적을 달성하기 위해 사용하는 모듈입니다. 참고로 이 내용은 Ansible 2.2.1.0 기준으로 작성되었습니다.

소개

Ansible Callback Plugin 은 Ansible 의 각종 이벤트를 Hooking 해서 해당 시점에 원하는 로직을 수행할 수 있는 플러그인을 말합니다. 이 콜백 플러그인은 Ansible Task, Playbook 등에 대해 “실행 직전”, “실행 종료” 등의 이벤트에 대한 콜백 함수를 정의할 수 있도록 지원합니다.

기본적으로 Callback Plugin 들은 callback_whitelist 라는 Ansible 환경 변수에 등록된 플러그인에 대해서만 콜백 함수가 동작하도록 되어 있습니다. 단, 콜백 모듈을 CALLBACK_NEEDS_WHITELIST = False 로 설정한 경우에는 무관합니다.

그리고, Callback Plugin 의 실행 순서는 Alphanumeric 순으로 실행됩니다. (e.g. 1.py → 2.py → a.py) 설정에 등록된 콜백 리스트 순서와는 무관합니다.

환경 설정

Callback Plugin 을 사용하기 위한 각종 Ansible 환경 설정을 정리합니다. 이 환경변수들은 ansible.cfg 파일에 정의해서 사용해도 되며, 커맨드라인을 통해 전달하는 방식도 가능합니다.

  • callback_plugins : 콜백 플러그인이 있는 디렉토리 위치를 지정합니다.

(ex) callback_plugins = ~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback

  • stdout_callback : stdout 에 대한 기본 콜백을 변경합니다. CALLBACK_TYPE = stdout 인 콜백 플러그인 모듈만 지정이 가능합니다.

(ex) stdout_callback = skippy

  • callback_whitelist : 콜백을 동작시킬 플러그인 이름을 지정합니다. CALLBACK_NEEDS_WHITELIST = False 인 콜백 플러그인 모듈은 무관합니다.

(ex) callback_whitelist = timer,mail

이벤트 후킹 (Event Hooking)

Ansible 프로젝트의 “lib/ansible/plugins/callback/__init__.py” 의 소스에 존재하는 CallbackBase 클래스의 Public Method가 이벤트 후킹 가능한 콜백 함수를 의미 합니다.

Callback Plugin 을 구현하는 경우, CallbackBase 클래스를 상속해서 사용할 이벤트를 Override 하시면 됩니다. 만약, Ansible 2.0 버전 이상에 해당하는 이벤트에만 콜백 함수가 동작하기를 원하하는 경우에는 함수에 “v2_” 접두어를 붙인 메소드를 Override 하시면 됩니다.

 1# 아래는 오버라이딩 가능한 메소드 리스트 입니다.
 2# Ansible 2.0 이상의 콜백 플러그인을 구현하시는 경우에는 v2_ 접두사를 추가로 붙여주시면 됩니다. (e.g.
 3def set_play_context(self, play_context):
 4    pass
 5def on_any(self, *args, **kwargs):
 6    pass
 7def runner_on_failed(self, host, res, ignore_errors=False):
 8    pass
 9def runner_on_ok(self, host, res):
10    pass
11def runner_on_skipped(self, host, item=None):
12    pass
13def runner_on_unreachable(self, host, res):
14    pass
15def runner_on_no_hosts(self):
16    pass
17def runner_on_async_poll(self, host, res, jid, clock):
18    pass
19def runner_on_async_ok(self, host, res, jid):
20    pass
21def runner_on_async_failed(self, host, res, jid):
22    pass
23def playbook_on_start(self):
24    pass
25def playbook_on_notify(self, host, handler):
26    pass
27def playbook_on_no_hosts_matched(self):
28    pass
29def playbook_on_no_hosts_remaining(self):
30    pass
31def playbook_on_task_start(self, name, is_conditional):
32    pass
33def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None):
34    pass
35def playbook_on_setup(self):
36    pass
37def playbook_on_import_for_host(self, host, imported_file):
38    pass
39def playbook_on_not_import_for_host(self, host, missing_file):
40    pass
41def playbook_on_play_start(self, name):
42    pass
43def playbook_on_stats(self, stats):
44    pass
45def on_file_diff(self, host, diff):
46    pass

구현 예제

아래의 Ansible Plugin 은 [jlafon/ansible-profile] 프로젝트를 차용하였다는 점을 먼저 알려드립니다.

이 플러그인에 대해서 간략하게 설명하자면, playbook의 task의 수행 시간을 메모리에 적재한 뒤에 playbook이 종료되기 전 태스크의 수행 시간을 Display 해주는 간단한 플러그인 입니다. 코드의 내용을 참고해주시면 이해가 될 것으로 생각되고, 플러그인에 대해서 설명이 필요한 부분은 주석을 참고하시면 됩니다.

 1import datetime
 2import os
 3import time
 4from ansible.plugins.callback import CallbackBase
 5
 6class CallbackModule(CallbackBase):
 7    """
 8    A plugin for timing tasks
 9    """
10    # 아래는 콜백 플러그인에 필수로 정의되어야 하는 클래스 속성 입니다.
11    CALLBACK_VERSION = 2.0 # 콜백 플러그인 버전을 명시 합니다.
12    CALLBACK_TYPE = 'notification' # 'stdout', 'notification', 'aggregate' 중에 하나를 사용합니다
13    CALLBACK_NAME = 'profile_tasks' # 콜백 모듈의 이름을 정의 합니다. Whitelist 에 등록할 때 사용됩니다.
14    CALLBACK_NEEDS_WHITELIST = True
15    
16    # 콜백 플러그인의 초기화 부분이 들어갑니다.
17    def __init__(self):
18        super(CallbackModule, self).__init__()
19        self.stats = {}
20        self.current = None
21    
22    # Playbook 내의 각 Task가 실행 될 때, 수행되는 로직을 구현합니다.
23    def playbook_on_task_start(self, name, is_conditional):
24        """
25        Logs the start of each task
26        """
27        if os.getenv("ANSIBLE_PROFILE_DISABLE") is not None:
28            return
29        if self.current is not None:
30            # Record the running time of the last executed task
31            self.stats[self.current] = time.time() - self.stats[self.current]
32        # Record the start time of the current task
33        self.current = name
34        self.stats[self.current] = time.time()
35    
36    # Playbook이 완료되었을 때, 수행되는 로직을 구현합니다.
37    def playbook_on_stats(self, stats):
38        """
39        Prints the timings
40        """
41        if os.getenv("ANSIBLE_PROFILE_DISABLE") is not None:
42            return
43        # Record the timing of the very last task
44        if self.current is not None:
45            self.stats[self.current] = time.time() - self.stats[self.current]
46        # Sort the tasks by their running time
47        results = sorted(
48            self.stats.items(),
49            key=lambda value: value[1],
50            reverse=True,
51        )
52        # Just keep the top 10
53        results = results[:10]
54        # Print the timings
55        for name, elapsed in results:
56            print(
57                "{0:-<70}{1:->9}".format(
58                    '{0} '.format(name),
59                    ' {0:.02f}s'.format(elapsed),
60                )
61            )
62        total_seconds = sum([x[1] for x in self.stats.items()])
63        print("\nPlaybook finished: {0}, {1} total tasks.  {2} elapsed. \n".format(
64                time.asctime(),
65                len(self.stats.items()),
66                datetime.timedelta(seconds=(int(total_seconds)))
67                )
68          )

아래는 ‘profile_task’ Callback Plugin 의 출력 예시 입니다.

 1ansible <args here>
 2<normal output here>
 3PLAY RECAP ********************************************************************
 4really slow task | Download project packages-----------------------------11.61s
 5security | Really slow security policies-----------------------------------7.03s
 6common-base | Install core system dependencies-----------------------------3.62s
 7common | Install pip-------------------------------------------------------3.60s
 8common | Install boto------------------------------------------------------3.57s
 9nginx | Install nginx------------------------------------------------------3.41s
10serf | Install system dependencies-----------------------------------------3.38s
11duo_security | Install Duo Unix SSH Integration----------------------------3.37s
12loggly | Install TLS version-----------------------------------------------3.36s

참고 링크

comments powered by Disqus