ansible api adhoc模式的动态调用

文章目录

    • 吐槽
    • 需求
    • 实现方式
    • 环境要求
    • 环境安装
    • 代码实现

吐槽

先吐槽一下ansible api文档,它是我目前为止遇到的编写最烂的,让人没法按照官方文档直接进行二次开发,官方竟然还回复说"api是给他们内部调用的",外人使用概不负责,有点不想让人使用ansible、要让人花钱买ansible tower的意思。

需求

批量控制那些还未上生产环境、未上监控、尚未纳管的机器,执行sh脚本、简单命令以及上传文件等。我希望可以做到全动态,使用人员在网页端输入ip、用户密码、操作方式、命令等参数即可完成远程控制。

实现方式

以web的方式来看,后端将ansible注册成接口,前端通过url请求来访问该接口。接口这块,使用adhoc模式,先动态生成inventory,再生成plays,最后taskqueuemanager运行play并返回结果。

环境要求

python3.6.1、ansible2.9.10、flask1.1.2

环境安装

我为什么要大费周章的搞离线安装呢?是因为公司无法连外网,如果你也一样,请参考另外一篇文章《离线安装ansible》会介绍。

代码实现

写代码之前,千万要记得在/etc/ansible/ansible.cfg配置文件中自定义一些配置,如下

[defaults]
#指定模块路径
library         = /usr/local/lib/python3.6/site-packages/ansible/modules
module_utils    = /usr/local/lib/python3.6/site-packages/ansible/module_utils
#指定管理节点的python路径
interpreter_python = /usr/local/bin/python
#使用账号密码登录,禁用主机检查
host_key_checking = False
#指定远程执行用户
remote_user = root
[ssh_connection]
#解决老版本的openssh不支持ControlPersist问题
ssh_args = “”

导入代码需要的依赖

import os
import json
import shutil
import logging
import subprocess
import ansible.constants as C

from ansible import context
from ansible.playbook.play import Play 
from flask import Flask, request, jsonify
from ansible.vars.manager import VariableManager
from ansible.plugins.callback import CallbackBase 
from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager 
from flask_restful import reqparse, abort, Api, Resource
from ansible.executor.task_queue_manager import TaskQueueManager 
from ansible.module_utils.common.collections import ImmutableDict

回调的返回结果需要再进一步优化,后续再补上,暂时先笼统的返回执行结果。

app = Flask(__name__)
api = Api(app)

'''
定义全局exec_result是为了返回多ip的执行结果,因为self.data默认返回hosts列表中最后一个
'''
class ResultCallback(CallbackBase):

    def v2_runner_on_ok(self, result, **kwargs):
        global exec_result
        host = result._host         
        self.data = json.dumps({
     host.name: result._result}, indent=4)
        exec_result = dict(exec_result,**json.loads(self.data)) 

    def v2_runner_on_failed(self,result, **kwargs):
        global exec_result
        host = result._host
        self.data = json.dumps({
     host.name: result._result}, indent=4)
        exec_result = dict(exec_result,**json.loads(self.data))

从request中获取参数,送给ansible,并返回执行结果。其中,_interpreter参数指定远程机器的python路径,从环境变量中寻找python,如果找不到会报错的,任务无法执行。

'''
ansible服务,接收并处理post请求
'''    
class ServiceAnsible(Resource):
    def post(self):
        global exec_result
        exec_result = {
     }
        try:
            _ip      = request.form.get('ip')    # 多个ip用逗号分开
            ips = _ip
            if not isinstance(_ip,list):
                ips = _ip.split(',')
            
            params = {
     
                '_module': request.form.get('type', default='shell'),
                '_margs' : request.form.get('content',default=''),
                '_ips'   : ips,
                '_user'  : request.form.get('user'),
                '_pwd'   : request.form.get('pwd'),
                '_group' :  'icbc',
                '_interpreter' : '/usr/bin/env python',
                '_modulepath' :"/usr/local/lib/python3.6/site-packages/ansible/modules"
            }
            result = exec_ansible(params)
            print(exec_result)
        except Exception as e:
            print(e)

exec_ansible这块是ad-hoc模式的核心了,需要额外注意verbosity,inventory添加主机时指定主机分组。
(1)verbosity
官方api中context里没加verbosity,会报错“not supported between instances of ‘NoneType’ and ‘int’”。
ansible api adhoc模式的动态调用_第1张图片

(2)主机添加分组
ansible默认会将host加到ungroup,但是添加Host不能识别,所以需要利用
data.py的add_host(self, host, group=None, port=None)指定分组
host.py的set_variable(self, key, value)设置变量
ansible api adhoc模式的动态调用_第2张图片
稍微改写了一下官方ansible api ad-hoc,这里理解一下connection参数和play_source的hosts 参数含义即可。

'''
由于ansible api缺陷,context必须加上verbosity参数,verbosity默认为None,不指定会报错
'''  
def exec_ansible(params):
    _module = params['_module']
    _margs = params['_margs']
    _ips = params['_ips']
    _user = params['_user']
    _pwd = params['_pwd']
    _group = params['_group']
    _interpreter = params['_interpreter']
    _modulepath = params['_modulepath']
        
    #connection定义为smart,不要定义为local
    context.CLIARGS = ImmutableDict(connection='smart', module_path=[_modulepath], verbosity=0, forks=50, become=None,become_method=None, become_user=None, check=False, diff=False) 
    loader = DataLoader()  
    passwords = dict(vault_pass='secret')
    
    #动态创建主机清单,默认sources=None
    inventory = InventoryManager(loader=loader)
    inventory.add_group(_group)
    for ip in _ips:
        inventory.add_host(ip)
        inventory.add_host(ip, group=_group)
        inventory.get_host(ip).set_variable('ansible_ssh_user', _user)
        inventory.get_host(ip).set_variable('ansible_ssh_pass', _pwd)
        inventory.get_host(ip).set_variable('ansible_python_interpreter', _interpreter)
    
    #自定义回调函数
    results_callback = ResultCallback()
    
    #初始化vm
    variable_manager = VariableManager(loader=loader, inventory=inventory)
    
    #初始化任务play
    play_source =  dict(
        name = "Ansible play",
        hosts = _group, #指定_group组中的主机列表
        gather_facts = 'no',
        tasks = [
            dict(action=dict(module=_module, args=_margs), register='shell_out')
        ]
    )
    play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
    
    #tqm执行任务,并返回结果
    tqm = None
    global exec_result
    try:
        tqm = TaskQueueManager(
            inventory=inventory,
            variable_manager=variable_manager,
            loader=loader,
            passwords=passwords,
            stdout_callback=results_callback,
        )
        result = tqm.run(play)
    except Exception as e:
        print(e)
    finally:
        #清理子进程
        if tqm is not None:
            tqm.cleanup()
        
        #删除临时目录
        shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)    
        return exec_result

# api
api.add_resource(ServiceAnsible, '/serviceAnsible')

# 主入口
if __name__ == '__main__':
    try:
       app.run(host='0.0.0.0',debug=True, threaded=True)  # 启动服务
    except Exception as e:
        print(e)

你可能感兴趣的:(智能运维,运维,ansible,api,ad-hoc,python)