全网:从Python3编译到AnsibleAPI回调

* 学习环境 CentOS Linux release 7.8.2003 (Core)

编译安装Python3.8.5

1、下载Python-3.8.5源码包

下载地址1(巨慢):https://www.python.org/ftp/python/3.8.5/Python-3.8.5.tar.xz
下载地址2(推荐):https://pan.baidu.com/s/1OKeCPr3yD5fbpbILscjtGQ 提取码: 7b3w

2、更换阿里源、安装常用依赖(避免关键时刻命令不存在, 最好按顺序执行~)

yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel netstat lrzsz unzip zip sysstat ntpdate net-tools vim wget -y

mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo_bak
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum makecache fast

yum install epel-release -y
yum repolist

3、按顺序执行命令

# 切换到目录'/usr/local'
cd /usr/local

# 上传源码包'Python-3.8.5.tar.xz'到目录'/usr/local'下
rz

# 解压'Python-3.8.5.tar.xz'
tar -xvf Python-3.8.5.tar.xz

# 切换到目录'Python-3.8.5'
cd Python-3.8.5

# 配置、编译、安装(有点漫长, 稍等片刻~)
./configure --prefix=/usr/local/python3
# 如果pip3 install出现ssl异常, 那么就换成以下语句重新编译
# ./configure --with-ssl --prefix=/usr/local/python3
make && make install

# 退回上一层, 删除解压后的源码目录(防止误覆盖)
cd ..
rm -rf Python-3.8.5

# 环境变量开机生效(不会用vim保存的建议摔电脑, 或者去百度)
vim /etc/profile.d/python3.sh
-----------------------
export PYTHON_HOME=/usr/local/python3
PATH=$PATH:$PYTHON_HOME/bin
-----------------------

# 配置生效
source /etc/profile

# 检查python3安装情况
[root@localhost local]# python3 -V
Python 3.8.5

安装ansible-2.4.6的软件包、开发依赖包

1、rpm包下载

仓库:https://releases.ansible.com/ansible/rpm/release/epel-7-x86_64/

下载地址:https://releases.ansible.com/ansible/rpm/release/epel-7-x86_64/ansible-2.4.6.0-1.el7.ans.noarch.rpm

2、yum本地安装rpm包

# 切换到目录'/usr/local'
cd /usr/local

# 上传软件包'ansible-2.4.6.0-1.el7.ans.noarch.rpm'到目录'/usr/local'下
rz

# yum本地安装
yum localinstall ansible-2.4.6.0-1.el7.ans.noarch.rpm -y

# 验证安装(ok)
[root@localhost local]# ansible --version
ansible 2.4.6.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Nov 16 2020, 22:23:17) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]

# 验证功能(ok)
[root@localhost local]# ansible 127.0.0.1 -m ping
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'

127.0.0.1 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}

3、pip3 指定安装ansible开发依赖

# 从阿里镜像源,查询并安装2.4.6.0版本的ansible依赖
pip3 install ansible==2.4.6.0 -i https://mirrors.aliyun.com/pypi/simple

修改官方例子, 使之可用于自己的业务

ResultCallBack.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from ansible.plugins.callback import CallbackBase


class ResultCallBack(CallbackBase):
    """
    结果回调
    """

    def __init__(self, *args, **kwargs):
        super(ResultCallBack, self).__init__(*args, **kwargs)
        self.host_ok = {}
        self.host_unreachable = {}
        self.host_failed = {}

    def v2_runner_on_unreachable(self, result):
        self.host_unreachable[result._host.get_name()] = result

    def v2_runner_on_ok(self, result, *args, **kwargs):
        self.host_ok[result._host.get_name()] = result

    def v2_runner_on_failed(self, result, *args, **kwargs):
        self.host_failed[result._host.get_name()] = result

AdHocRunner.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
import sys
from collections import namedtuple

from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.inventory.manager import InventoryManager
from ansible.parsing.dataloader import DataLoader
from ansible.playbook.play import Play
from ansible.vars.manager import VariableManager

from ext.ansibles.ResultCallBack import ResultCallBack
from utils.FileUtils import FileUtils


class ConnectionEnum:
    """
    连接类型(枚举)
    """
    # LOCAL = 'local'
    SSH = 'ssh'
    PARAMIKO = 'paramiko'


class BecomeMethodEnum:
    """
    权限升级方法(枚举)
    """
    SUDO = 'sudo'
    SU = 'su'
    PBRUN = 'pbrun'
    PFEXEC = 'pfexec'
    DOAS = 'doas'
    DZDO = 'dzdo'
    KSU = 'ksu'
    RUNAS = 'runas'
    PMRUN = 'pmrun'
    ENABLE = 'enable'


class AdHocRunner(object):
    """
    This is a General object for parallel execute modules.
    """

    def __init__(self, sources, *args, **kwargs):
        self.sources = sources
        self.inventory = None
        self.variable_manager = None
        self.loader = None
        self.options = None
        self.passwords = None
        self.__initialize_data()

    def __initialize_data(self):
        """
        初始化ansible
        """

        Options = namedtuple('Options', [
            # 连接类型
            'connection',
            # 要执行的模块的路径,默认为/usr/share/ansible/
            'module_path',
            # 在与主机通信时的默认并行进程数
            'forks',
            # 远程SSH端口
            'remote_port',
            # 用于指定远程主机上的执行任务的用户(您登录时使用的用户)
            'remote_user',
            # 是否允许身份切换
            'become',
            # 使用哪个权限升级方法,默认为sudo
            'become_method',
            # 使用哪个用户运行(默认为root, 设置为具有所需特权的用户-您想要成为的用户,而不是您登录时使用的用户)
            'become_user',
            # 只是测试一下会改变什么内容,不会真正去执行;相反,试图预测一些可能发生的变化
            'check',
            # 当更改文件和模板时,显示这些文件得差异,比check效果好
            'diff',
            # 检查主机密钥
            'host_key_checking'
        ])
        self.loader = DataLoader()

        self.options = Options(
            connection=ConnectionEnum.SSH,
            module_path='/path/to',
            forks=100,
            remote_port=22,
            remote_user='root',
            become='yes',
            become_method=None,
            become_user='root',
            check=False,
            diff=False,
            host_key_checking=False,
        )

        self.passwords = dict(vault_pass='secret')
        self.inventory = InventoryManager(loader=self.loader, sources=self.sources)
        self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)

    def run(self, host_list, module_name, module_args, extra_vars):
        """
        run module from andible ad-hoc
        :param host_list 主机列表
        :param module_name ansible模块名称
        :param module_args ansible模块参数
        :param extra_vars 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' "
        """
        # create play with tasks
        play_source = dict(
            name='AnsiblePlay',
            hosts=host_list,
            # 除非明确说明不需要在远程主机上执行setup模块,否则默认自动执行。如果确实不需要setup模块传递过来的变量,则可以将该选项设置为False
            gather_facts='no',
            tasks=[dict(action=dict(module=module_name, args=module_args))]
        )

        # join vars
        if extra_vars:
            self.variable_manager.extra_vars = extra_vars

        # create play
        play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)

        # actually run it
        tqm = None
        callback = ResultCallBack()
        try:
            tqm = TaskQueueManager(
                inventory=self.inventory,
                variable_manager=self.variable_manager,
                loader=self.loader,
                options=self.options,
                passwords=self.passwords,
                stdout_callback=callback,
            )
            tqm.run(play)
            return self.__get_result(callback)
        finally:
            if tqm is not None:
                tqm.cleanup()

    def runByTaskList(self, host_list, task_list, extra_vars):
        """
        run module from andible ad-hoc
        :param host_list 主机列表
        :param task_list 任务对象列表
            {'moduleName': 'ansible模块名称', 'moduleArgs': ''}
        :param extra_vars 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' "
        """
        # create play with tasks
        if not isinstance(task_list, list):
            sys.exit()

        tasks = list()
        for item in task_list:
            tasks.append(dict(action=dict(module=item['moduleName'], args=item['moduleArgs'])))

        play_source = dict(
            name='AnsiblePlay',
            hosts=host_list,
            # 除非明确说明不需要在远程主机上执行setup模块,否则默认自动执行。如果确实不需要setup模块传递过来的变量,则可以将该选项设置为False
            gather_facts='no',
            tasks=tasks
        )

        # join vars
        if extra_vars:
            self.variable_manager.extra_vars = extra_vars

        # create play
        play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)

        # actually run it
        tqm = None
        callback = ResultCallBack()
        try:
            tqm = TaskQueueManager(
                inventory=self.inventory,
                variable_manager=self.variable_manager,
                loader=self.loader,
                options=self.options,
                passwords=self.passwords,
                stdout_callback=callback,
            )
            tqm.run(play)
            return self.__get_result(callback)
        finally:
            if tqm is not None:
                tqm.cleanup()

    def run_playbook(self, host_list, extra_vars, yaml_filename):
        """
        run ansible playbook
        :param host_list 主机列表
        :param extra_vars 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' "
        :param yaml_filename yaml文件名称
        """
        try:
            callback = ResultCallBack()

            yaml_path = FileUtils.get_root_path('ansible2player') + 'resources' + os.sep + 'ansible' + yaml_filename + '.yaml'

            if not os.path.exists(yaml_path):
                print('%s 文件不存在 ' % yaml_path)
                sys.exit()

            # join vars
            if extra_vars:
                self.variable_manager.extra_vars = extra_vars

            # create host str
            host_list_str = ','.join([item for item in host_list])

            self.variable_manager.extra_vars['host_list'] = host_list_str

            print('playbook 额外参数:%s' % self.variable_manager.extra_vars)

            # yaml file list
            filenames = [yaml_path]

            # actually run it
            executor = PlaybookExecutor(
                playbooks=filenames,
                inventory=self.inventory,
                variable_manager=self.variable_manager,
                loader=self.loader,
                options=self.options,
                passwords=self.passwords,
            )
            executor._tqm._stdout_callback = callback
            executor.run()
            return self.__get_result(callback)
        except Exception as e:
            print('run_playbook:%s' % e)
            return None

    def __get_result(self, callback):
        results_raw = {'success': {}, 'failed': {}, 'unreachable': {}}
        for host, result in callback.host_ok.items():
            results_raw['success'][host] = result._result

        for host, result in callback.host_failed.items():
            results_raw['failed'][host] = result._result

        for host, result in callback.host_unreachable.items():
            results_raw['unreachable'][host] = result._result['msg']
        # print('Ansible执行结果集:%s' % results_raw)
        return results_raw

Test.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-


from ext.ansibles.AdHocRunner import AdHocRunner

# 传入inventory路径, 有就传, 没有就None
runner = AdHocRunner('/etc/ansible/hosts')
# 获取服务器磁盘信息
result = runner.run('127.0.0.1', 'setup', "filter='ansible_mounts'", None)
# 执行成功的主机
success = result['success']
# 执行失败的主机
failed = result['failed']
# 不可到达的主机
unreachable = result['unreachable']

print(success)

执行结果(字典)

{
	"127.0.0.1": {
		"ansible_facts": {
			"ansible_mounts": [{
				"mount": "/",
				"device": "/dev/mapper/cl-root",
				"fstype": "xfs",
				"options": "rw,relatime,attr2,inode64,noquota",
				"uuid": "52851cc2-a9ab-4e54-9a49-0a938c2bb513",
				"size_total": 38216605696,
				"size_available": 23118983168,
				"block_size": 4096,
				"block_total": 9330226,
				"block_available": 5644283,
				"block_used": 3685943,
				"inode_total": 18669568,
				"inode_available": 18118528,
				"inode_used": 551040
			}, {
				"mount": "/boot",
				"device": "/dev/sda1",
				"fstype": "xfs",
				"options": "rw,relatime,attr2,inode64,noquota",
				"uuid": "34c2a884-3c37-40fd-a8ea-3d54ce931a2f",
				"size_total": 1063256064,
				"size_available": 901054464,
				"block_size": 4096,
				"block_total": 259584,
				"block_available": 219984,
				"block_used": 39600,
				"inode_total": 524288,
				"inode_available": 523959,
				"inode_used": 329
			}, {
				"mount": "/app",
				"device": "/dev/mapper/appvg-applv",
				"fstype": "xfs",
				"options": "rw,relatime,attr2,inode64,noquota",
				"uuid": "f9c5fab4-79ad-4e02-97ad-54aab4ca1f72",
				"size_total": 106174664704,
				"size_available": 89128210432,
				"block_size": 4096,
				"block_total": 25921549,
				"block_available": 21759817,
				"block_used": 4161732,
				"inode_total": 51853312,
				"inode_available": 51710733,
				"inode_used": 142579
			}, {
				"mount": "/home",
				"device": "/dev/mapper/cl-home",
				"fstype": "xfs",
				"options": "rw,relatime,attr2,inode64,noquota",
				"uuid": "446edbe9-a3f5-4614-a7bc-57060e39f5d9",
				"size_total": 18658361344,
				"size_available": 18300760064,
				"block_size": 4096,
				"block_total": 4555264,
				"block_available": 4467959,
				"block_used": 87305,
				"inode_total": 9115648,
				"inode_available": 9106838,
				"inode_used": 8810
			}]
		},
		"invocation": {
			"module_args": {
				"filter": "ansible_mounts",
				"gather_subset": ["all"],
				"gather_timeout": 10,
				"fact_path": "/etc/ansible/facts.d"
			}
		},
		"_ansible_parsed": true,
		"_ansible_verbose_override": true,
		"_ansible_no_log": false,
		"changed": false
	}
}

python3 + flask + celery + ansible-api

这个项目雏形后期会在博客给出的gitee地址的, 期待吧

再就是这个sdk访问方式是互信免密的,所以还是得和被控机器做下SSH互信

1、首先, 检查文件夹'/root/.ssh'是否存在; 
如果存在进去拷贝出公钥的内容:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAq7iH6+FqHhGQvfUlQugw4/BtfQKJyLzCgDzrmy3ynB0en0EVHUWzBfT4mkPxFdfZE4Vw9fOz+R6H/lFRiHy7tI4U+aWfc/fQIFmESBf6S7RRHPGNbxJ++2hnY7blJNWUPEaGR3khVRbTMP7iKDHD12DsHLCJoFg/WTqvmBSeBCqLtaGIcnh1n4BUSExGbTL4wX54CIVJQuWZNav7L0sa9O+hZ3ZS6lZfj7wSTla8eLBbHkCWdsx81i4/Y+EsQF+9xTziYYBFiWTVOjtEfJV1dI4hFSdiiokn16eEtV0YACbm16zNyJZljabcfNRSHvim6TBpL615QyEJJgm15xcZ root@localhost.localdomain

如果不存在, 则执行 'ssh-keygen -t rsa' 命令(去掉引号, 谢谢), 一路狂按回车键即可, 然后也是拷贝出公钥的内容


2、到被控机上面, 将公钥的内容拷贝到要互信的用户的.ssh目录下的authorized_keys文件中, 这里几乎是每一个初学者都特别懵逼的地方

举几个简单的例子:

'192.168.175.130'免密登陆'192.168.175.131':

a、在'192.168.175.130'执行'ssh root@192.168.175.131': 在'192.168.175.130'上以被控机root的身份登陆'192.168.175.131', 那么就需要在/root/.ssh/authorized_keys文件中加入公钥内容.

b、在'192.168.175.130'执行'ssh odboy@192.168.175.131': 在'192.168.175.130'上以被控机odboy的身份登陆'192.168.175.131', 那么就需要在/home/odboy/.ssh/authorized_keys文件中加入公钥内容, 这里又有一个问题, 如果我有odboy这个用户, 我并没有.ssh这个目录咋搞, Easy我教你; 执行 'ssh-keygen -t rsa' 命令(去掉引号, 谢谢), 一路狂按回车键即可, odboy这个用户的公私钥内容最好请空, 以免混乱.

cat /dev/null > /home/odboy/.ssh/id_rsa
cat /dev/null > /home/odboy/.ssh/id_rsa.pub
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌 烨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值