Ansible Api 实战案例

项目需要的Python包

amqp==2.5.1
ansible==2.7.8
asn1crypto==1.1.0
bcrypt==3.1.7
billiard==3.6.1.0
celery==4.3.0
certifi==2019.9.11
cffi==1.12.3
chardet==3.0.4
Click==7.0
cryptography==2.7
Flask==1.1.1
Flask-Script==2.0.6
gunicorn==19.9.0
idna==2.8
importlib-metadata==0.23
itsdangerous==1.1.0
Jinja2==2.10.3
kombu==4.6.5
MarkupSafe==1.1.1
more-itertools==7.2.0
paramiko==2.6.0
pycparser==2.19
PyNaCl==1.3.0
pytz==2019.3
PyYAML==5.1.2
redis==3.3.11
requests==2.22.0
six==1.12.0
urllib3==1.25.6
vine==1.3.0
Werkzeug==0.16.0
zipp==0.6.0

ansible api封装

# filename: ansible_api.py
# -*- coding: utf-8 -*-
import requests
import traceback
from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager
from ansible.vars.manager import VariableManager
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.plugins.callback.default import CallbackBase


class PlayBookResultsCollector(CallbackBase):
    CALLBACK_VERSION = 2.0

    def __init__(self, *args, **kwargs):
        super(PlayBookResultsCollector, self).__init__(*args, **kwargs)

    def v2_runner_on_ok(self, result, *args, **kwargs):
        print("ok: [%s]" % result._host.get_name())

    def v2_runner_on_skipped(self, result):
        print("[skip]: %s" % result._host.get_name())

    def v2_runner_on_failed(self, result, *args, **kwargs):
        print("fatal: [%s]: FAILED! => %s" % (result._host.get_name(), result._result))

    def v2_runner_on_unreachable(self, result):
        print("fatal: [%s]: UNREACHABLE! => %s" % (result._host.get_name(), result._result))

    def v2_playbook_on_task_start(self, task, is_conditional):
        name = task.get_name().strip()
        print('\n[Task] %s' % name)

    def v2_playbook_on_play_start(self, play):
        name = play.get_name().strip()
        print("PLAY [%s]" % name)


class Options(object):
    def __init__(self, connection='ssh', forks=10, listtags=False, listtasks=False, listhosts=False, syntax=False,
                 ssh_extra_args=None, sftp_extra_args=None, scp_extra_args=None, become=False, become_method=None,
                 become_user=None, verbosity=None, check=False, diff=False, host_key_checking=False, module_path=None,
                 remote_user=None, private_key_file=None, ssh_common_args=None):
        self.connection = connection
        self.forks = forks
        self.listtags = listtags
        self.listtasks = listtasks
        self.listhosts = listhosts
        self.syntax = syntax
        self.ssh_extra_args = ssh_extra_args
        self.sftp_extra_args = sftp_extra_args
        self.scp_extra_args = scp_extra_args
        self.become = become
        self.become_method = become_method
        self.become_user = become_user
        self.verbosity = verbosity
        self.check = check
        self.diff = diff
        self.host_key_checking = host_key_checking
        self.module_path = module_path
        self.remote_user = remote_user
        self.private_key_file = private_key_file
        self.ssh_common_args = ssh_common_args


class PlaybookRunner(object):
    def __init__(self, playbook, hosts, ecs_info):
        self.playbook = playbook
        self.loader = DataLoader()
        # 如果不想动态生成,也可以指定 inventory文件
        self.inventory = InventoryManager(loader=self.loader, sources=[hosts, ])
        self.options = Options(
            connection='ssh',
            forks=30,
            remote_user='root',
            private_key_file='/home/admin/.ssh/scops_id_rsa'
        )
        self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory)
        self.result_callback = PlayBookResultsCollector()
        self.passwords = {}
        self.ecs_info = ecs_info

    def runner(self):
        try:
            pbex = PlaybookExecutor(
                playbooks=[self.playbook],
                inventory=self.inventory,
                variable_manager=self.variable_manager,
                loader=self.loader,
                options=self.options,
                passwords=self.passwords
            )

            pbex._tqm._stdout_callback = self.result_callback
            result = pbex.run()
            if str(result) == '0':
                # callback_post
                send_callback(self.ecs_info, 'success')
                print("=================================")
                print("初始化成功!")
            else:
                send_callback(self.ecs_info, 'fail')
                print("初始化失败!")

        except Exception as e:
            traceback.print_exc()
            # callback_post
            print("=================================")
            print("初始化失败!")
            print(e)
            send_callback(self.ecs_info, 'fail')

def send_callback(ecs_info, status):
    # 一个回调接口,告诉回调接口我这边初始化成功与否
    url = "https://api.domain.com/callback/callback_ecs_init" 
    data = {
        "ecsId": ecs_info['instance_id'],
        "status": status,
        "ip": ecs_info['ip'],
        "hostname": ecs_info['hostname']
    }

    headers = {"Content-Type": "application/json"}
    requests.post(url=url, json=data, headers=headers)

工具类封装

# coding: utf-8
# filename: utils.py
# descript: 用于生成机器清单
import subprocess


def set_inventory(ecs_info):
    """
    生成ansible清单文件
    :param ecs_info: 阿里云机器信息
    :return:
    """
    # 清单文件
    inventory_file_path = "/home/admin/init_system/ansible-playbooks/play/inventories/ecs_init/hosts"
    # 注释以ip地址开头的行
    subprocess.getoutput(r"sed -ri '/^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/{s/(.*)/#\1/}' %s" % inventory_file_path)
    # host info
    ecs_host_info_string = "{} ansible_ssh_user=root ansible_ssh_pass={} hostname={} INSTANCE_ID={}".format(ecs_info["ip"], ecs_info["password"], ecs_info["hostname"], ecs_info["instance_id"])
    subprocess.getoutput(r"echo '{}' >> {}".format(ecs_host_info_string, inventory_file_path))

App程序

# filename run.py
# coding: utf-8
# descript: 实现异步任务和接口服务
import requests
import subprocess
from flask import Flask, request, jsonify
from celery import Celery
from celeryconfig import broker_url
from utils import set_inventory, set_szone_env
from ansible_api import PlaybookRunner


def make_celery(app):
    celery = Celery(__name__, broker=broker_url)
    celery.config_from_object('celeryconfig')

    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)

    celery.Task = ContextTask
    return celery


app = Flask(__name__)
celery = make_celery(app)


@celery.task
def playbook_task(ecs_init_info):
    set_inventory(ecs_init_info)
    set_szone_env(ecs_init_info)
    playbook = '/home/admin/init_system/ansible-playbooks/play/ecs_init_for_api.yml'
    hosts = '/home/admin/init_system/ansible-playbooks/play/inventories/ecs_init/hosts'
    playbook_runner = PlaybookRunner(playbook, hosts, ecs_init_info)
    playbook_runner.runner()


@app.route('/')
def index():
    return '<h1>首页</h1>'


@app.route('/ecs/init/', methods=['POST'])
def ecs_init():
    token = request.headers.get("Token")
    if token == "123456":
        ecs_init_info = request.get_json()
        if ecs_init_info['ip'] != '' and ecs_init_info['hostname'] != '' and ecs_init_info['password'] != '' and ecs_init_info['instance_id'] != '':
            playbook_task.delay(ecs_init_info)
            return jsonify({"message": "success", "code": "200"})
        else:
            return jsonify({"message": "params error", "code": "200"})
    else:
        return jsonify({"message": "token error", "code": "200"})

Celery 异步任务

使用Celery可以异步执行ansible playbook任务,避免ansible任务请求超时。

# filename: celeryconfig.py
# celery 配置
broker_url = 'redis://localhost:6379/0'
result_backend = 'redis://localhost:6379/0'

task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'Asia/Shanghai'
enable_utc = True

部署项目接口

  • 安装supervisor
sudo apt install supervisor
  • 修改配置
; 主要修改这一块的内容
[unix_http_server]
file=/tmp/supervisor.sock   ; (the path to the socket file)
chmod=0770               ; socket file mode (default 0700)
chown=root:supervisor       ; socket file uid:gid owner
;username=user              ; (default is no username (open server))
;password=123               ; (default is no password (open server))

; 子配置文件路径定义
[include]
files = /etc/supervisor/conf.d/*.conf
  • 添加配置

在子配置文件路径添加文件celery-task.conf,内容如下:

[program:celery-task]
directory=/home/admin/projects/ecs-init/app
environment=PYTHONOPTIMIZE=1
command=/home/admin/projects/ecs-init/pyenv35/bin/celery -A run.celery worker --loglevel info
autostart=true
autorestart=false
user=admin
redirect_stderr=true
stdout_logfile=/home/admin/projects/-ecs-init/logs/celery-task/access.log
loglevel=info

子配置文件ecs-init.conf,内容如下:

[program:ecs-init]
directory=/home/admin/projects/ecs-init/app
command=/home/admin/projects/ecs-init/pyenv35/bin/gunicorn run:app --bind 0.0.0.0:4080 --workers 4
autostart=true
autorestart=false
user=admin
redirect_stderr=true
stdout_logfile=/home/admin/projects/ecs-init/logs/ecs-init/access.log
loglevel=info
  • 启动服务
sudo supervisorctl update       # 更新服务
sudo supervisorctl restart      # 重启服务
sudo supervisorctl start        # 开启服务
sudo supervisorctl stop         # 关闭服务
  • 实现效果
    异步任务效果:
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值