前言:
ansible的结果默认是输出到cli终端和日志里面的,用惯了saltsatck的returners数据回调后,也很是喜欢ansible也有,一开始不知道有这个功能,自己也简单实现了这样的功能。
我的实现方式是,在模块里面做一些输出的逻辑。当使用ansible runner api的时候,是在后面runner代码,最后加了一段往redis输出的逻辑。 这里实现数据的输出有些独特,但是只能是在模块和 api方面搞 。 如果是用playbook的话,按照我以前的思路的话,再继续改ansbile的源码。 这两天听沈灿说,ansible有个callback_plugins的功能,可以对于执行的状态做一些判断,比如,执行成功,执行失败,异步执行,异步执行失败,playbook开始,结束等等。
我也不说复杂了,就简单说一个例子,把执行的结果,都推到redis里面,也可以暂存到sqlite数据库里面,只是这段代码我给屏蔽了,有兴趣的朋友再搞搞。对于redis里面的数据可以写一个页面展现下,专门记录错误的问题,成功的就pass掉。
#xiaorui.cc import os import time import sqlite3 import redis import json dbname = '/tmp/setup.db' TIME_FORMAT='%Y-%m-%d %H:%M:%S' try: con = sqlite3.connect(dbname) cur = con.cursor() except: pass def log(host, data): # if type(data) == dict: # invocation = data.pop('invocation', None) # if invocation.get('module_name', None) != 'setup': # return # # facts = data.get('ansible_facts', None) # # now = time.strftime(TIME_FORMAT, time.localtime()) # # try: # # `host` is a unique index # cur.execute("REPLACE INTO inventory (now, host, arch, dist, distvers, sys,kernel) VALUES(?,?,?,?,?,?,?);", # ( # now, # facts.get('ansible_hostname', None), # facts.get('ansible_architecture', None), # facts.get('ansible_distribution', None), # facts.get('ansible_distribution_version', None), # facts.get('ansible_system', None), # facts.get('ansible_kernel', None) # )) # con.commit() # except: # pass # class CallbackModule(object): def runner_on_ok(self, host, res): r = redis.Redis(host='127.0.0.1', port=6379, db=0) r.set(host,str(res)) f = open('/tmp/11','a') f.write(str(host)) f.write(str(res)) f.close() log(host, res) def runner_on_failed(self, host, res, ignore_errors=False): f = open('/tmp/11','a') f.write('\nbad\n') f.close() log(host, res)
还是可以接收所有的facts数据的。
原文:http://rfyiamcool.blog.51cto.com/1030776/1440624
原文:http://rfyiamcool.blog.51cto.com/1030776/1440624
虽然我上面的例子用了redis,sqlite数据库,其实我个人推荐用mongodb这样的文档数据库的。因为ansible主runner函数,给callbacks传递了一个叫res的变量,他本身就是一个dict对象,如果放到redis的hash,sqlite的各种字段,够你烦的了,如果直接mongo,那就简单了,直接insert ! 欧了
这里在show一个邮件的callbacks代码,场景是,非常消耗时间的任务,当执行完成后,查看结果咋办? 但是你也可以在终端继续看,既然咱们讲了callbacks_plugins,就可以把结果push到你的邮箱里面,当然只给你发错误的,有问题的。 下面的callback代码需要自己替换成自己用的邮箱、密码、smtp服务器。
#xiaorui.cc 原文:http://rfyiamcool.blog.51cto.com/1030776/1440624 import smtplib def mail(subject='Ansible error mail', sender='<root>', to='root', cc=None, bcc=None, body=None): if not body: body = subject smtp = smtplib.SMTP('localhost') content = 'From: %s\n' % sender content += 'To: %s\n' % to if cc: content += 'Cc: %s\n' % cc content += 'Subject: %s\n\n' % subject content += body addresses = to.split(',') if cc: addresses += cc.split(',') if bcc: addresses += bcc.split(',') for address in addresses: smtp.sendmail(sender, address, content) smtp.quit() class CallbackModule(object): """ This Ansible callback plugin mails errors to interested parties. """ def runner_on_failed(self, host, res, ignore_errors=False): if ignore_errors: return sender = '"Ansible: %s" <root>' % host subject = 'Failed: %(module_name)s %(module_args)s' % res['invocation'] body = 'The following task failed for host ' + host + ':\n\n%(module_name)s %(module_args)s\n\n' % res['invocation'] if 'stdout' in res.keys() and res['stdout']: subject = res['stdout'].strip('\r\n').split('\n')[-1] body += 'with the following output in standard output:\n\n' + res['stdout'] + '\n\n' if 'stderr' in res.keys() and res['stderr']: subject = res['stderr'].strip('\r\n').split('\n')[-1] body += 'with the following output in standard error:\n\n' + res['stderr'] + '\n\n' if 'msg' in res.keys() and res['msg']: subject = res['msg'].strip('\r\n').split('\n')[0] body += 'with the following message:\n\n' + res['msg'] + '\n\n' body += 'A complete dump of the error:\n\n' + str(res) mail(sender=sender, subject=subject, body=body) def runner_on_unreachable(self, host, res): sender = '"Ansible: %s" <root>' % host if isinstance(res, basestring): subject = 'Unreachable: %s' % res.strip('\r\n').split('\n')[-1] body = 'An error occured for host ' + host + ' with the following message:\n\n' + res else: subject = 'Unreachable: %s' % res['msg'].strip('\r\n').split('\n')[0] body = 'An error occured for host ' + host + ' with the following message:\n\n' + \ res['msg'] + '\n\nA complete dump of the error:\n\n' + str(res) mail(sender=sender, subject=subject, body=body) def runner_on_async_failed(self, host, res, jid): sender = '"Ansible: %s" <root>' % host if isinstance(res, basestring): subject = 'Async failure: %s' % res.strip('\r\n').split('\n')[-1] body = 'An error occured for host ' + host + ' with the following message:\n\n' + res else: subject = 'Async failure: %s' % res['msg'].strip('\r\n').split('\n')[0] body = 'An error occured for host ' + host + ' with the following message:\n\n' + \ res['msg'] + '\n\nA complete dump of the error:\n\n' + str(res) mail(sender=sender, subject=subject, body=body) 如果不想发邮件,又不想搞到数据库里面,怎么办? 那来点低端的。 直接写入到文件里面。 官方给出一个例子,大家照着模板写就行了。 import os import time import json TIME_FORMAT="%b %d %Y %H:%M:%S" MSG_FORMAT="%(now)s - %(category)s - %(data)s\n\n" if not os.path.exists("/var/log/ansible/hosts"): os.makedirs("/var/log/ansible/hosts") def log(host, category, data): if type(data) == dict: if 'verbose_override' in data: # avoid logging extraneous data from facts data = 'omitted' else: data = data.copy() invocation = data.pop('invocation', None) data = json.dumps(data) if invocation is not None: data = json.dumps(invocation) + " => %s " % data path = os.path.join("/var/log/ansible/hosts", host) now = time.strftime(TIME_FORMAT, time.localtime()) fd = open(path, "a") fd.write(MSG_FORMAT % dict(now=now, category=category, data=data)) fd.close() class CallbackModule(object): """ logs playbook results, per host, in /var/log/ansible/hosts """ def on_any(self, *args, **kwargs): pass def runner_on_failed(self, host, res, ignore_errors=False): log(host, 'FAILED', res) def runner_on_ok(self, host, res): log(host, 'OK', res) def runner_on_skipped(self, host, item=None): log(host, 'SKIPPED', '...') def runner_on_unreachable(self, host, res): log(host, 'UNREACHABLE', res) def runner_on_no_hosts(self): pass def runner_on_async_poll(self, host, res, jid, clock): pass def runner_on_async_ok(self, host, res, jid): pass def runner_on_async_failed(self, host, res, jid): log(host, 'ASYNC_FAILED', res) def playbook_on_start(self): pass def playbook_on_notify(self, host, handler): pass def playbook_on_no_hosts_matched(self): pass def playbook_on_no_hosts_remaining(self): pass def playbook_on_task_start(self, name, is_conditional): pass def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): pass def playbook_on_setup(self): pass def playbook_on_import_for_host(self, host, imported_file): log(host, 'IMPORTED', imported_file) def playbook_on_not_import_for_host(self, host, missing_file): log(host, 'NOTIMPORTED', missing_file) def playbook_on_play_start(self, name): pass def playbook_on_stats(self, stats): pass
也可以把结果以webhooks钩子的方式,做些你想做的东西。
callbacks的各种状态还是很多的,每个函数的字眼还是很好理解的。
比如:
on_any 哪都有他 !任何的状态他触发。
runner_on_failed 失败
runner_on_ok 成功
runner_on_unreachable 网络不可达
runner_on_no_hosts 没有主机
runner_on_async_poll 任务的异步执行
playbook_on_start playbook执行的时候
等等。。。。 自己尝试吧 !
class CallbackModule(object): def on_any(self, *args, **kwargs): pass def runner_on_failed(self, host, res, ignore_errors=False): log(host, 'FAILED', res) def runner_on_ok(self, host, res): log(host, 'OK', res) def runner_on_skipped(self, host, item=None): log(host, 'SKIPPED', '...') def runner_on_unreachable(self, host, res): log(host, 'UNREACHABLE', res) def runner_on_no_hosts(self): pass def runner_on_async_poll(self, host, res, jid, clock): pass def runner_on_async_ok(self, host, res, jid): pass def runner_on_async_failed(self, host, res, jid): log(host, 'ASYNC_FAILED', res) def playbook_on_start(self): pass def playbook_on_notify(self, host, handler): pass def playbook_on_no_hosts_matched(self): pass def playbook_on_no_hosts_remaining(self): pass def playbook_on_task_start(self, name, is_conditional): pass def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None): pass def playbook_on_setup(self): pass def playbook_on_import_for_host(self, host, imported_file): log(host, 'IMPORTED', imported_file) def playbook_on_not_import_for_host(self, host, missing_file): log(host, 'NOTIMPORTED', missing_file) def playbook_on_play_start(self, name): pass def playbook_on_stats(self, stats): pass
咱们可以简单看看ansible的callbacks源码。
规定了两个类,一个是供应ansible-playbook用的,还有一个是供应ansible,也就是cli。 根据各种的情况,调用不同的函数,首先会打到终端,再log日志,最后是自定义的callbacks的插件。
好了,就这样了 !!!!
大神 如何指定用哪个callbacks 当目录中有多个callback脚本时
如果你是在命令行执行的话,默认是使用ansible的callbacks.CliRunnerCallbacks(),你是没办法在参数中指定的,要传的话,可以自己改源码实现。 如果你是调用runner.Runner接口的话,是可以传入相应的参数进行指定的。比如这样:from ansible import utilsyou_callback = utils.callback_loader.get(‘your_callback_name’)#在callback_plugins目录下面,要有你定义好的callback插件才行。
比如这样使用playbook的api playbook = playbook params = params stats = callbacks.AggregateStats() playbook_cb = utils.callback_loader.get(‘your_callback_name’,verbose=utils.VERBOSITY) runner_cb = callbacks.PlaybookRunnerCallbacks(self.stats, verbose=utils.VERBOSITY) pb = ansible.playbook.PlayBook( playbook = self.playbook, stats = self.stats, callbacks = self.playbook_cb, runner_callbacks = self.runner_cb, check=False, extra_vars = self.params ) result = pb.run()对了这是ansible 1.9.1 版本的方式,其他的我没看不懂。
峰云果然屌,学习
求友情链接。 专家。。