从PHP调用Ansible Playbook执行远程脚本并获取完成通知及输出
实现PHP调用Ansible Playbook并获取执行反馈的最佳方案
我来给你梳理几个落地性很强的方案,完美匹配你的三个需求,都是实际项目里验证过的路子:
一、基础:PHP调用Ansible Playbook的两种方式
1. 同步调用(适合短耗时Playbook)
如果你的远程脚本执行时间不长,直接用PHP的proc_open函数调用Ansible是最直接的——它能实时捕获stdout、stderr,还能获取执行状态码,比exec或者shell_exec灵活太多。
举个实际代码例子:
<?php // 配置你的Playbook和Inventory路径 $playbookPath = '/opt/ansible/playbooks/run_remote_script.yml'; $inventoryPath = '/opt/ansible/inventory/prod.ini'; // 构建Ansible命令(可以加上-vvv参数调试,生产环境去掉) $command = "ansible-playbook -i {$inventoryPath} {$playbookPath}"; // 定义进程管道:stdin、stdout、stderr $descriptorspec = [ 0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"] ]; // 启动进程 $process = proc_open($command, $descriptorspec, $pipes, null, null); $stdout = ''; $stderr = ''; if (is_resource($process)) { // 实时读取输出(如果要在PHP页面实时展示进度,这里可以echo+flush) while (!feof($pipes[1])) { $line = fgets($pipes[1]); $stdout .= $line; // 实时输出到页面 echo htmlspecialchars($line) . "<br>"; flush(); } // 读取错误输出 while (!feof($pipes[2])) { $stderr .= fgets($pipes[2]); } // 关闭管道并获取退出码 fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); $exitCode = proc_close($process); } // 执行完成后展示完整结果 echo "<hr>"; echo "<h4>执行状态:" . ($exitCode === 0 ? "成功" : "失败(状态码:{$exitCode})") . "</h4>"; echo "<h4>标准输出:</h4><pre>" . htmlspecialchars($stdout) . "</pre>"; echo "<h4>错误输出:</h4><pre>" . htmlspecialchars($stderr) . "</pre>"; ?>
2. 异步调用(适合长耗时Playbook)
如果远程脚本要跑很久,总不能让PHP页面一直挂着。这时候可以把Ansible放到后台执行,再通过「主动通知」的方式让PHP知道执行完成:
步骤1:后台执行Ansible并保存输出
用nohup把命令丢到后台,同时把stdout和stderr写到日志文件:
<?php $logDir = '/opt/ansible/logs/'; $stdoutLog = $logDir . uniqid('playbook_') . '_stdout.log'; $stderrLog = $logDir . uniqid('playbook_') . '_stderr.log'; // 构建后台执行命令 $command = "nohup ansible-playbook -i {$inventoryPath} {$playbookPath} > {$stdoutLog} 2> {$stderrLog} & echo $!"; // 获取后台进程ID(可选,用来后续监控) $pid = shell_exec($command); // 把日志路径和PID存到数据库,方便后续查询 // 这里假设你有一个task_logs表,字段包括id, pid, stdout_log, stderr_log, status $pdo->exec("INSERT INTO task_logs (pid, stdout_log, stderr_log, status) VALUES ('{$pid}', '{$stdoutLog}', '{$stderrLog}', 'running')"); // 给用户返回任务ID,让页面轮询状态 echo json_encode(['task_id' => $pdo->lastInsertId()]); ?>
步骤2:让Ansible执行完成后主动通知PHP
在你的Playbook最后加一个任务,调用PHP的API接口,把执行结果传回去:
- name: 获取Playbook退出码 set_fact: playbook_exit_code: "{{ ansible_playbook_results.stats | json_query('*.failures') | sum | int }}" - name: 通知PHP执行完成 uri: url: "http://your-php-domain.com/api/playbook-complete" method: POST body: task_id: "{{ lookup('env', 'TASK_ID') }}" # 可以在调用Ansible时通过环境变量传入任务ID stdout: "{{ lookup('file', '{{ stdout_log_path }}') }}" stderr: "{{ lookup('file', '{{ stderr_log_path }}') }}" exit_code: "{{ playbook_exit_code }}" body_format: json status_code: 200
然后PHP的/api/playbook-complete接口接收数据,更新数据库里的任务状态和输出,前端页面通过轮询这个接口来展示结果。
二、你提到的「远程插入数据库」方案可行吗?
完全可行!其实就是上面异步方案的变种——你可以在Playbook最后加一个任务,直接连接你的数据库插入执行记录,不需要走PHP接口。比如用Ansible的mysql_db或者postgresql_query模块,前提是远程服务器能访问你的数据库,并且有对应的写入权限。
举个MySQL的例子:
- name: 写入执行记录到数据库 mysql_query: login_host: your-db-host login_user: db-user login_password: db-pass db: your-db-name query: "INSERT INTO task_results (stdout, stderr, exit_code, created_at) VALUES ('{{ stdout_content }}', '{{ stderr_content }}', {{ exit_code }}, NOW())"
不过这种方式需要把数据库密码写到Playbook里(或者用Ansible Vault加密),安全性不如调用PHP接口,所以更推荐前面的API通知方式。
三、关键注意事项
- 权限问题:PHP运行的用户(比如
www-data)需要有执行Ansible的权限,并且能免密登录远程服务器(可以给www-data生成SSH密钥对,放到远程服务器的authorized_keys里)。 - 日志权限:如果用日志文件保存输出,要确保
www-data有日志目录的读写权限。 - Ansible配置:确保
www-data能访问Ansible的配置文件和Inventory文件,必要时可以在命令里指定--config参数指定配置路径。
内容的提问来源于stack exchange,提问作者user3347598
相关产品推荐
相关产品推荐

