课程四:将 Runner 嵌入灵魂 - Python API 编程
在前三课中,我们一直通过命令行使用 Ansible Runner。这一课,我们将进入更强大的领域:将 Runner 作为 Python 库直接集成到你的应用程序中。这意味着你可以构建自定义的自动化平台、Web 服务、定时任务等,以编程方式驱动 Ansible。
4.1 为什么要用 Python API?
命令行方式虽然简单,但当你需要:
- 根据程序逻辑动态决定执行什么 Playbook 或模块;
- 实时处理 Ansible 执行过程中的事件(例如,某个任务失败时立即发送告警);
- 将执行结果 存入数据库或发送到监控系统;
- 并发执行多个 Runner 任务并管理它们;
命令行就力不从心了。Python API 让你能够完全掌控 Runner 的行为,并与其他系统无缝集成。
4.2 最简单的调用:ansible_runner.run()
ansible_runner.run() 是最常用的函数,它会阻塞当前线程,直到 Ansible 执行完毕,然后返回一个 Runner 对象。
python
import ansible_runner
r = ansible_runner.run(
private_data_dir='/path/to/private_data',
playbook='site.yml'
)
print(f"执行状态: {r.status}")
print(f"返回码: {r.rc}")
4.2.2 返回的 Runner 对象
run() 返回的 Runner 对象包含了执行的各种信息:
bash
r.status # 状态字符串:'successful', 'failed', 'timeout', 'canceled' 等
r.rc # 返回码:0 成功,非 0 失败
r.stdout # 标准输出文本
r.stderr # 标准错误文本
r.events # 所有事件的列表(如果收集了)
r.stats # 统计信息字典
r.fact_cache # facts 缓存(如果启用)
4.2.3 实操案例 1:执行简单的 Playbook
假设我们有一个私有数据目录 my_project,里面包含:
project/hello.ymlinventory/hosts
hello.yml
yaml
---
- name: Hello 示例
hosts: localhost
gather_facts: no
tasks:
- name: 打印消息
debug:
msg: "Hello from Python API!"
执行脚本:
python
import ansible_runner
r = ansible_runner.run(
private_data_dir='my_project',
playbook='hello.yml'
)
print(f"状态: {r.status}")
print(f"返回码: {r.rc}")
print(f"输出:\n{r.stdout}")
4.3 实时事件处理 - event_handler
在命令行中,输出是逐行打印的。在 Python API 中,你可以注册一个回调函数,在每次事件发生时被调用。这对于实时反馈、日志记录或自定义处理非常有用。
事件处理函数的签名:
python
def my_event_handler(event_data):
# event_data 是一个字典,包含事件的所有信息
print(f"收到事件: {event_data['event']}")
注册方式:
ini
r = ansible_runner.run(
private_data_dir='my_project',
playbook='hello.yml',
event_handler=my_event_handler
)
部分输出:
markdown
[WARNING]: provided hosts list is empty, only localhost is available. Note that
收到事件: verbose
the implicit localhost does not match 'all'
收到事件: playbook_on_start
收到事件: playbook_on_play_start
PLAY [Hello demo] **************************************************************
收到事件: playbook_on_task_start
TASK [print debug] *************************************************************
收到事件: runner_on_start
收到事件: runner_on_ok
4.3.1 实操案例 2:实时打印任务执行
编写一个脚本,实时输出每个任务执行的主机和结果。
ini
import ansible_runner
def event_handler(event_data):
event = event_data.get('event')
if event == 'runner_on_ok':
host = event_data['event_data']['host']
task = event_data['event_data']['task']
print(f'任务 {task} 在主机 {host} 上执行成功')
elif event == 'runner_on_failed':
host = event_data['event_data']['host']
task = event_data['event_data']['task']
result = event_data['event_data']['res']
msg = result.get('msg', '未知错误')
print(f"❌ 任务 '{task}' 在主机 {host} 上失败: {msg}")
r = ansible_runner.run(
private_data_dir='my_project',
playbook='hello.yml',
event_handler=event_handler
)
print(f'状态: {r.status}')
4.3.2 可用的处理器类型
event_handler:处理所有事件(最常用)。status_handler:处理状态变化(running、successful等),参数是状态字符串。finished_callback:任务结束时调用,参数是Runner对象。
4.4 异步执行 - ansible_runner.run_async()
当你的 Playbook 需要长时间运行,或者你希望同时执行多个任务时,可以使用 run_async()。
重要 :run_async() 返回的是一个元组 (runner, thread),其中:
runner是Runner对象,用于查询状态、获取结果。thread是threading.Thread对象,你可以用它来等待任务完成或控制超时。
python
import ansible_runner
import time
# 启动异步任务
runner, thread = ansible_runner.run_async(
private_data_dir='my_project',
playbook='long_running.yml'
)
print(f"任务标识符: {runner.ident}") # 例如 20240320T123456.789012
# 轮询任务状态
while runner.is_alive():
print(f"任务仍在运行... ")
time.sleep(2)
# 等待线程结束(可选,因为已经通过 is_alive 检测到结束)
thread.join()
print(f"最终状态: {runner.ident}")
runner.is_alive()返回True表示任务还在后台运行。runner.ident是一个唯一标识符,可用于stop、is-alive等命令(但通常用对象方法更直接)。
4.5 自定义私有数据目录的结构
通过 Python API,你可以在内存中动态构建私有数据目录,而无需实际创建文件。这对于临时任务或动态生成内容非常有用。
ini
import ansible_runner
playbook_content = [
{'hosts': 'localhost',
'tasks': [
{'debug': {'msg': 'Hello from dynamic playbook'}}
]}
]
r = ansible_runner.run(
private_data_dir='/tmp/runner_tmp',
playbook=playbook_content, # 直接传递 playbook 对象
inventory='localhost ansible_connection=local' # 直接传递inventory清单
)
print(f"状态: {r.status}")
print(f"返回码: {r.rc}")
4.6 综合案例:构建一个简单的自动化 API
我们来编写一个 Flask 应用,通过 HTTP 请求触发 Ansible Runner 执行,并返回结果。
python
from flask import Flask, request, jsonify
import ansible_runner
import threading
import uuid
app = Flask(__name__)
# 存储任务状态(生产环境应用数据库)
tasks = {}
def run_ansible(task_id, private_data_dir, playbook, extra_vars=None):
"""异步运行ansible playbook"""
thread, runner = ansible_runner.run_async(
private_data_dir=private_data_dir,
playbook=playbook,
extravars=extra_vars
)
# 将runner保存起来,供查询使用
tasks[task_id] = {'runner': runner}
@app.route('/api/run',methods=['POST'])
def run():
"""
POST请求 - 启动playbook运行
请求体 JSON格式:
{
"private_data_dir": "项目目录路径",
"playbook": "playbook文件名.yml",
"extra_vars": {} # 可选,额外变量
}
"""
data = request.json
private_data_dir = data.get('private_data_dir')
playbook = data.get('playbook')
extra_vars = data.get('extra_vars', {})
if not private_data_dir or not playbook:
return jsonify({'error': 'missing parameters'}), 400
task_id = str(uuid.uuid4())
threading.Thread(target=run_ansible, args=(task_id, private_data_dir, playbook, extra_vars)).start()
return jsonify({
'task_id': task_id,
'message': 'Playbook started'
}), 202
@app.route('/api/status/<task_id>', methods=['GET'])
def status(task_id):
"""
GET请求 - 获取任务状态
参数: task_id (URL路径参数)
返回任务状态信息
"""
task = tasks.get(task_id)
if not task:
return jsonify({'error': 'task not found'}), 404
runner = task['runner']
return jsonify({
'task_id': task_id,
'status': runner.status,
'rc': runner.rc
})
if __name__ == '__main__':
app.run(debug=False)
结果:
arduino
# curl -X POST http://127.0.0.1:5000/api/run \
-H "Content-Type: application/json" \
-d '{
"private_data_dir":
"/usr/local/src/py_projects/automatic/ansible-runner/tip4-将Runner嵌入灵魂-PythonAPI编程/my_project",
"playbook": "long_running.yml"
}'
{"message":"Playbook started","task_id":"447b4bec-4f6f-41b3-9827-2198dae92eb5"}
# curl http://127.0.0.1:5000/api/status/447b4bec-4f6f-41b3-9827-2198dae92eb5
{"rc":null,"status":"running","task_id":"447b4bec-4f6f-41b3-9827-2198dae92eb5"}
# 等待一分钟后
# curl http://127.0.0.1:5000/api/status/447b4bec-4f6f-41b3-9827-2198dae92eb5
{"rc":0,"status":"successful","task_id":"447b4bec-4f6f-41b3-9827-2198dae92eb5"}
这个 API 允许你提交 Runner 任务并异步查询状态。通过事件处理器,你还可以将实时进度推送给前端。
适用场景
- Web UI 触发 Ansible 任务
- 在 Web 界面上点击"执行部署"按钮,后端通过 API 调用 Ansible
- 适用于运维平台、自动化平台的前端集成
- CI/CD 流水线
- 集成到 Jenkins、GitLab CI 等持续集成工具
- 通过 HTTP API 触发 Ansible Playbook 执行
- 定时任务系统
- 实现类似 AWA、Rundeck 的任务调度系统
- 通过 API 触发 Ansible 自动化任务
- 多租户 SaaS 平台
- 为不同租户提供独立的 Ansible 执行环境
- 通过 API 实现任务隔离和资源控制
5. 聊天机器人集成
- 在 Slack/钉钉机器人中嵌入 Ansible 执行能力
- 运维人员通过聊天命令触发自动化任务