项目需要的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 # 关闭服务
- 实现效果
异步任务效果: