4_【自动化引擎Ansible Runner】将 Runner 嵌入灵魂 - Python API 编程

课程四:将 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.yml
  • inventory/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:处理状态变化(runningsuccessful 等),参数是状态字符串。
  • finished_callback:任务结束时调用,参数是 Runner 对象。

4.4 异步执行 - ansible_runner.run_async()

当你的 Playbook 需要长时间运行,或者你希望同时执行多个任务时,可以使用 run_async()

重要run_async() 返回的是一个元组 (runner, thread),其中:

  • runnerRunner 对象,用于查询状态、获取结果。
  • threadthreading.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 是一个唯一标识符,可用于 stopis-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 任务并异步查询状态。通过事件处理器,你还可以将实时进度推送给前端。

适用场景

  1. Web UI 触发 Ansible 任务
  • 在 Web 界面上点击"执行部署"按钮,后端通过 API 调用 Ansible
  • 适用于运维平台、自动化平台的前端集成
  1. CI/CD 流水线
  • 集成到 Jenkins、GitLab CI 等持续集成工具
  • 通过 HTTP API 触发 Ansible Playbook 执行
  1. 定时任务系统
  • 实现类似 AWA、Rundeck 的任务调度系统
  • 通过 API 触发 Ansible 自动化任务
  1. 多租户 SaaS 平台
  • 为不同租户提供独立的 Ansible 执行环境
  • 通过 API 实现任务隔离和资源控制

5. 聊天机器人集成

  • 在 Slack/钉钉机器人中嵌入 Ansible 执行能力
  • 运维人员通过聊天命令触发自动化任务
相关推荐
AI茶水间管理员2 小时前
爆火的OpenClaw到底强在哪?一文了解核心架构(附一条消息的全链路流程)
人工智能·后端
Java水解2 小时前
Rust异步缓存系统的设计与实现
后端·rust
野犬寒鸦2 小时前
JVM垃圾回收机制面试常问问题及详解
java·服务器·开发语言·jvm·后端·算法·面试
用户908324602732 小时前
Spring AI + RAG + SSE 实现带搜索来源的智能问答完整方案
前端·后端
Java编程爱好者2 小时前
Java面试题及答案整理(2026年牛客网最新版)
后端
_杨瀚博2 小时前
JAVA找出哪个类import了不存在的类
java·后端
Java编程爱好者2 小时前
接口越跑越慢?保姆级 MySQL 慢 SQL 优化教程,照着做速度立刻起飞
后端
SmartBrain3 小时前
Spring Boot的高性能技术栈的工程实践
spring boot·后端·架构