芝麻HTTP:Ansible扩展
Ansible简介
Ansible是由Python开发的一个运维工具,因为工作需要接触到Ansible,经常会集成一些东西到Ansible,所以对Ansible的了解越来越多。
那Ansible到底是什么呢?在我的理解中,原来需要登录到服务器上,然后执行一堆命令才能完成一些操作。而Ansible就是来代替我们去执行那些命令。并且可以通过Ansible控制多台机器,在机器上进行任务的编排和执行,在Ansible中称为playbook。
那Ansible是如何做到的呢?简单点说,就是Ansible将我们要执行的命令生成一个脚本,然后通过sftp将脚本上传到要执行命令的服务器上,然后在通过ssh协议,执行这个脚本并将执行结果返回。
那Ansible具体是怎么做到的呢?下面从模块和插件来看一下Ansible是如何完成一个模块的执行
PS:下面的分析都是在对Ansible有一些具体使用经验之后,通过阅读源代码进一步得出的执行结论,所以希望在看本文时,是建立在对Ansible有一定了解的基础上,最起码对于Ansible的一些概念有了解,例如inventory,module,playbooks等
Ansible模块
模块是Ansible执行的最小单位,可以是由Python编写,也可以是Shell编写,也可以是由其他语言编写。模块中定义了具体的操作步骤以及实际使用过程中所需要的参数
执行的脚本就是根据模块生成一个可执行的脚本。
那Ansible是怎么样将这个脚本上传到服务器上,然后执行获取结果的呢?
Ansible插件
connection插件
连接插件,根据指定的ssh参数连接指定的服务器,并切提供实际执行命令的接口
shell插件
命令插件,根据sh类型,来生成用于connection时要执行的命令
strategy插件
执行策略插件,默认情况下是线性插件,就是一个任务接着一个任务的向下执行,此插件将任务丢到执行器去执行。
action插件
动作插件,实质就是任务模块的所有动作,如果ansible的模块没有特别编写的action插件,默认情况下是normal或者async(这两个根据模块是否async来选择),normal和async中定义的就是模块的执行步骤。例如,本地创建临时文件,上传临时文件,执行脚本,删除脚本等等,如果想在所有的模块中增加一些特殊步骤,可以通过增加action插件的方式来扩展。
Ansible执行模块流程
- ansible命令实质是通过ansible/cli/adhoc.py来运行,同时会收集参数信息
- 设置Play信息,然后通过TaskQueueManager进行run,
- TaskQueueManager需要Inventory(节点仓库),variable_manager(收集变量),options(命令行中指定的参数),stdout_callback(回调函数)
- 在task_queue_manager.py中找到run中
- 初始化时会设置队列
- 会根据options,,variable_manager,passwords等信息设置成一个PlayContext信息(playbooks/playcontext.py)
- 设置插件(plugins)信息callback_loader(回调), strategy_loader(执行策略), module_loader(任务模块)
- 通过strategy_loader(strategy插件)的run(默认的strategy类型是linear,线性执行),去按照顺序执行所有的任务(执行一个模块,可能会执行多个任务)
- 在strategy_loader插件run之后,会判断action类型。如果是meta类型的话会单独执行(不是具体的ansible模块时),而其他模块时,会加载到队列_queue_task
- 在队列中会调用WorkerProcess去处理,在workerproces实际的run之后,会使用TaskExecutor进行执行
- 在TaskExecutor中会设置connection插件,并且根据task的类型(模块。或是include等)获取action插件,就是对应的模块,如果模块有自定义的执行,则会执行自定义的action,如果没有的会使用normal或者async,这个是根据是否是任务的async属性来决定
- 在Action插件中定义着执行的顺序,及具体操作,例如生成临时目录,生成临时脚本,所以要在统一的模式下,集成一些额外的处理时,可以重写Action的方法
- 通过Connection插件来执行Action的各个操作步骤
扩展Ansible实例
执行节点Python环境扩展
实际需求中,我们扩展的一些Ansible模块需要使用三方库,但每个节点中安装这些库有些不易于管理。ansible执行模块的实质就是在节点的python环境下执行生成的脚本,所以我们采取的方案是,指定节点上的Python环境,将局域网内一个python环境作为nfs共享。通过扩展Action插件,增加节点上挂载nfs,待执行结束后再将节点上的nfs卸载。具体实施步骤如下:
扩展代码:
重写ActionBase的execute_module方法
# execute_module from __future__ import (absolute_import, division, print_function) __metaclass__ = type import json import pipes from ansible.compat.six import text_type, iteritems from ansible import constants as C from ansible.errors import AnsibleError from ansible.release import __version__ try: from __main__ import display except ImportError: from ansible.utils.display import Display display = Display() class MagicStackBase(object): def _mount_nfs(self, ansible_nfs_src, ansible_nfs_dest): cmd = ['mount',ansible_nfs_src, ansible_nfs_dest] cmd = [pipes.quote(c) for c in cmd] cmd = ' '.join(cmd) result = self._low_level_execute_command(cmd=cmd, sudoable=True) return result def _umount_nfs(self, ansible_nfs_dest): cmd = ['umount', ansible_nfs_dest] cmd = [pipes.quote(c) for c in cmd] cmd = ' '.join(cmd) result = self._low_level_execute_command(cmd=cmd, sudoable=True) return result def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=True): ''' Transfer and run a module along with its arguments. ''' # display.v(task_vars) if task_vars is None: task_vars = dict() # if a module name was not specified for this execution, use # the action from the task if module_name is None: module_name = self._task.action if module_args is None: module_args = self._task.args # set check mode in the module arguments, if required if self._play_context.check_mode: if not self._supports_check_mode: raise AnsibleError("check mode is not supported for this operation") module_args['_ansible_check_mode'] = True else: module_args['_ansible_check_mode'] = False # Get the connection user for permission checks remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user # set no log in the module arguments, if required module_args['_ansible_no_log'] = self._play_context.no_log or C.DEFAULT_NO_TARGET_SYSLOG # set debug in the module arguments, if required module_args['_ansible_debug'] = C.DEFAULT_DEBUG # let module know we are in diff mode module_args['_ansible_diff'] = self._play_context.diff # let module know our verbosity module_args['_ansible_verbosity'] = display.verbosity # give the module information about the ansible version module_args['_ansible_version'] = __version__ # set the syslog facility to be used in the module module_args['_ansible_syslog_facility'] = task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY) # let module know about filesystems that selinux treats specially module_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS (module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars) if not shebang: raise AnsibleError("module (%s) is missing interpreter line" % module_name) # get nfs info for mount python packages ansible_nfs_src = task_vars.get("ansible_nfs_src", None) ansible_nfs_dest = task_vars.get("ansible_nfs_dest", None) # a remote tmp path may be necessary and not already created remote_module_path = None args_file_path = None if not tmp and self._late_needs_tmp_path(tmp, module_style): tmp = self._make_tmp_path(remote_user) if tmp: remote_module_filename = self._connection._shell.get_remote_filename(module_name) remote_module_path = self._connection._shell.join_path(tmp, remote_module_filename) if module_style in ['old', 'non_native_want_json']: # we'll also need a temp file to hold our module arguments args_file_path = self._connection._shell.join_path(tmp, 'args') if remote_module_path or module_style != 'new': display.debug("transferring module to remote") self._transfer_data(remote_module_path, module_data) if module_style == 'old': # we need to dump the module args to a k=v string in a file on # the remote system, which can be read and parsed by the module args_data = "" for k,v in iteritems(module_args): args_data += '%s=%s ' % (k, pipes.quote(text_type(v))) self._transfer_data(args_file_path, args_data) elif module_style == 'non_native_want_json': self._transfer_data(args_file_path, json.dumps(module_args)) display.debug("done transferring module to remote") environment_string = self._compute_environment_string() remote_files = None if args_file_path: remote_files = tmp, remote_module_path, args_file_path elif remote_module_path: remote_files = tmp, remote_module_path # Fix permissions of the tmp path and tmp files. This should be # called after all files have been transferred. if remote_files: self._fixup_perms2(remote_files, remote_user) # mount nfs if ansible_nfs_src and ansible_nfs_dest: result = self._mount_nfs(ansible_nfs_src, ansible_nfs_dest) : raise AnsibleError("mount nfs failed!!! {0}".format(result['stderr'])) cmd = "" in_data = None if self._connection.has_pipelining and self._play_context.pipelining and not C.DEFAULT_KEEP_REMOTE_FILES and module_style == 'new': in_data = module_data else: if remote_module_path: cmd = remote_module_path rm_tmp = None if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if not self._play_context.become or self._play_context.become_user == 'root': # not sudoing or sudoing to root, so can cleanup files in the same step rm_tmp = tmp cmd = self._connection._shell.build_module_command(environment_string, shebang, cmd, arg_path=args_file_path, rm_tmp=rm_tmp) cmd = cmd.strip() sudoable = True if module_name == "accelerate": # always run the accelerate module as the user # specified in the play, not the sudo_user sudoable = False res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data) # umount nfs if ansible_nfs_src and ansible_nfs_dest: result = self._umount_nfs(ansible_nfs_dest) : raise AnsibleError("umount nfs failed!!! {0}".format(result['stderr'])) if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if self._play_context.become and self._play_context.become_user != 'root': # not sudoing to root, so maybe can't delete files as that other user # have to clean up temp files as original user in a second step tmp_rm_cmd = self._connection._shell.remove(tmp, recurse=True) tmp_rm_res = self._low_level_execute_command(tmp_rm_cmd, sudoable=False) tmp_rm_data = self._parse_returned_data(tmp_rm_res) ) != : display.warning('Error deleting remote temporary files (rc: {0}, stderr: {1})'.format(tmp_rm_res.get('rc'), tmp_rm_res.get('stderr', 'No error string available.'))) # parse the main result data = self._parse_returned_data(res) # pre-split stdout into lines, if stdout is in the data and there # isn't already a stdout_lines value there if 'stdout' in data and 'stdout_lines' not in data: data['stdout_lines'] = data.get('stdout', u'').splitlines() display.debug("done with _execute_module (%s, %s)" % (module_name, module_args)) return data
集成到normal.py和async.py中,记住要将这两个插件在ansible.cfg中进行配置
from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible.plugins.action import ActionBase from ansible.utils.vars import merge_hash from common.ansible_plugins import MagicStackBase class ActionModule(MagicStackBase, ActionBase): def run(self, tmp=None, task_vars=None): if task_vars is None: task_vars = dict() results = super(ActionModule, self).run(tmp, task_vars) # remove as modules might hide due to nolog del results['invocation']['module_args'] results = merge_hash(results, self._execute_module(tmp=tmp, task_vars=task_vars)) # Remove special fields from the result, which can only be set # internally by the executor engine. We do this only here in # the 'normal' action, as other action plugins may set this. # # We don't want modules to determine that running the module fires # notify handlers. That's for the playbook to decide. for field in ('_ansible_notify',): if field in results: results.pop(field) return results
- 配置ansible.cfg,将扩展的插件指定为ansible需要的action插件
- 重写插件方法,重点是execute_module
- 执行命令中需要指定Python环境,将需要的参数添加进去nfs挂载和卸载的参数
ansible -m mysql_db -a "state=dump name=all target=/tmp/test.sql" -i hosts -u root -v -e "ansible_nfs_src=172.16.30.170:/web/proxy_env/lib64/python2.7/site-packages ansible_nfs_dest=/root/.pyenv/versions/2.7.10/lib/python2.7/site-packages ansible_python_interpreter=/root/.pyenv/versions/2.7.10/bin/python"
芝麻HTTP:Ansible扩展的更多相关文章
- ansible基础-Jinja2模版 | 过滤器
Jinja2模版介绍 注:本文demo使用ansible2.7稳定版 在ansible基础-变量的「8.2 模版使用变量」章节中关于模版与变量也有所提及,有兴趣的同学可以去回顾一下. ansible通 ...
- 【Ansible 文档】【译文】模版(Jinja2)
Templating (Jinja2) 正如在 variables 部分描述的那样, Ansible 使用Jinja2模版来启用动态表达式和访问变量. Ansible 扩展了许多 filtes 和 t ...
- openshift3.10集群部署
简介 openshift是基于k8s的开源容器云. 要求 系统环境:CentOS 7.5 搭建一个master节点,两个node节点 注意: openshift3 依赖docker的版本为1.13.1 ...
- 《Ansible权威指南》笔记(4)——Playbook
七.Playbook1.语法特性如下:(1)"---"首行顶格开始(2)#号注释(3)缩进统一,不同的缩进代表不同的级别,缩进要对齐,空格和tab不能混用(4)区别大小写,键值对k ...
- 使用ansible编译安装运维工具tmux
实验系统:CentOS 6.6_x86_64 实验前提:提前准备好编译环境,防火墙和selinux都关闭 软件介绍:tmux是一个优秀的终端复用软件,类似GNU Screen,但来自于OpenBSD, ...
- PHP 接入芝麻信用 注意 事项
芝麻官方下载的SDK,跑不起来,百度搜索一番也没有发现太多的文章 ,只有一个CSDN博客写的一篇文章,比较有参考价值 详细查阅文档+几天测试整理以下几点注意事项: 接入芝麻API接口,应该分2步: 第 ...
- Ansible 学习笔记
最近因为需要管理很多台机器,而这些机器又需要频繁重新安装,实在受不了Puppet需要在每个客户机上都安装一遍,于是转头开始学些Ansible.根据这段时间的使用,这个确实是神器,唯一的感觉就是相见恨晚 ...
- 翻译-Salt与Ansible全方位比较
原文链接:http://jensrantil.github.io/salt-vs-ansible.html 作者: Jens Rantil 之前某些时候我需要评估配置管理系统.结合从他人得到的意见,我 ...
- ansible入门
前言 最近看了一下ansible,挺火的一个配置管理工具,对比老大哥puppet,使用起来要简单一些,并且可以批量执行命令,对比同是python语言编写的saltstack,不需要安装客户端(基于pa ...
随机推荐
- 洛谷 [P2825] 游戏
二分图匹配的匈牙利算法 这道题,如果没有硬石头的限制,那么就与ZJOI 2007矩阵游戏完全一样,但是如果有了硬石头的限制,我们就不能将整行整列作为元素建图,我们可以以硬石头为边界,将每一行.每一列分 ...
- testlink用例的导出到Excel
一直在网上寻找怎么把testlink的用例导出到Excel中,以及把Excel中已经写好的用例导入到Testlink中的方法.根据现网的经验,然后修改了一下.贴出来,以飨有这方面需求的测试同仁. Te ...
- Windows Azure Virtual Machine (34) Azure VM挂载WebDAV
<Windows Azure Platform 系列文章目录> 之前使用Azure VM,挂载box网盘.发现不能正常挂载,这里简单记录一下. 1.WebDAV的网络映射,需要WebCli ...
- 2018/1/19 Netty学习笔记(一)
这段时间学了好多好多东西,不过更多是细节和思想上的,比如分布式事物,二次提交,改善代码质量,还有一些看了一些源码什么的; 记录一下真正的技术学习,关于Netty的学习过程; 首先说Netty之前先说一 ...
- C++ 内存分配(new,operator new)面面观 (转)
本文主要讲述C++ new运算符和operator new, placement new之间的种种关联,new的底层实现,以及operator new的重载和一些在内存池,STL中的应用. 一 new ...
- WPF 使用DMSkin for WPF 快速搭建漂亮的WPF程序
DMSkin-for-WPF是一个基于WPF的.Net WPF开源界面库,实现了无边框的WPF开发方案,内置部分控件模板. 你可以参照模板自行修改完善.(以下简称DFW). 核心 DFW实现了比较完美 ...
- js 中的一些小技巧
js 数字操作: 1.1 取整: 取整有很多方法如: parseInt(a,10); Math.floor(a); a>>0; ~~a; a|0; 前面2种是经常用到的,后面3中算是比较偏 ...
- 如何用vue-cli初始化一个vue项目
单文件组件和vue-loader 解析打包.vue文件 vue为了能够使我们在项目开发中对组件进行更好的维护,提供了一个单文件组件系统,vue把每一个独立的组件放在一个.vue的文件中,在这个文件中提 ...
- Math Jax开源数学编辑器的使用
首先,这是一个开源免费,同时也可以支持扩展的软件. 使用API文档: 中文网站(http://mathjax-chinese-doc.readthedocs.io/en/latest/index.ht ...
- Redmine基础: 邮件配置
1.用文本编辑器打开 D:\Bitnami\redmine-2.6.5-0\apps\redmine\htdocs\config\configuration.yml 文件,找到以下内容: 2.配置邮件 ...