PHP实现SSH远程多Bash脚本并行执行及结果获取问询
PHP通过SSH并行执行远程Bash脚本并获取执行结果
一、前置准备:配置SSH免密登录
要实现无交互的远程执行,首先得搞定主从节点的SSH密钥信任:
- 在主节点(部署PHP的服务器)生成专用SSH密钥对:
这里ssh-keygen -t ed25519 -N "" -f ~/.ssh/php_remote_exec-N ""设置空密码,-f指定密钥文件名,方便和其他密钥区分 - 将公钥复制到所有从节点(执行脚本的服务器):
替换ssh-copy-id -i ~/.ssh/php_remote_exec.pub user@remote-node-ipuser和remote-node-ip为从节点的实际用户名和IP地址 - 验证免密登录是否生效:
直接输出ssh -i ~/.ssh/php_remote_exec user@remote-node-ip "echo 'Success'"Success就说明配置没问题了
二、核心实现代码
1. 单个远程脚本执行的完整示例(补全你提供的代码片段)
先把你中断的代码补全,同时加上关键的退出码和错误输出获取逻辑:
<?php // 连接远程节点 $conn = ssh2_connect('remote-node-ip', 22); // 使用密钥登录(避免密码交互) ssh2_auth_pubkey_file($conn, 'user', '/home/your-user/.ssh/php_remote_exec.pub', '/home/your-user/.ssh/php_remote_exec'); // 执行目标远程脚本(替换为你的脚本路径) $command = '/opt/scripts/your_task.sh'; $stream = ssh2_exec($conn, $command); // 设置流为阻塞模式,确保能获取完整输出 stream_set_blocking($stream, true); // 获取标准输出(stdout) $stdout = stream_get_contents($stream); // 获取标准错误输出(stderr) $stderrStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); stream_set_blocking($stderrStream, true); $stderr = stream_get_contents($stderrStream); // 获取命令执行的退出码(exit code) $exitCode = ssh2_get_exit_status($stream); // 清理资源 fclose($stream); fclose($stderrStream); ssh2_disconnect($conn); // 输出结果 echo "退出码: {$exitCode}\n"; echo "标准输出:\n{$stdout}\n"; echo "错误输出:\n{$stderr}\n"; ?>
这里重点补充了ssh2_fetch_stream获取错误输出、ssh2_get_exit_status获取退出码这两个你之前缺失的关键步骤。
2. 并行执行多个远程脚本的实现
因为PHP的ssh2_exec默认是阻塞的,要并行执行可以用pcntl_fork创建子进程,每个子进程处理一个远程节点的任务:
<?php // 定义所有需要执行的远程节点和脚本信息 $remoteNodes = [ ['ip' => 'node1.example.com', 'user' => 'admin', 'script' => '/opt/scripts/task1.sh'], ['ip' => 'node2.example.com', 'user' => 'admin', 'script' => '/opt/scripts/task2.sh'], ['ip' => 'node3.example.com', 'user' => 'admin', 'script' => '/opt/scripts/task3.sh'], ]; $keyPath = '/home/your-user/.ssh/php_remote_exec'; $pubKeyPath = '/home/your-user/.ssh/php_remote_exec.pub'; // 存储子进程ID与对应节点的映射 $childProcesses = []; foreach ($remoteNodes as $node) { $pid = pcntl_fork(); if ($pid == -1) { die("无法创建子进程,系统资源不足"); } elseif ($pid == 0) { // 子进程:单独处理一个远程节点的执行 $conn = ssh2_connect($node['ip'], 22); ssh2_auth_pubkey_file($conn, $node['user'], $pubKeyPath, $keyPath); $stream = ssh2_exec($conn, $node['script']); stream_set_blocking($stream, true); $stdout = stream_get_contents($stream); $stderrStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); stream_set_blocking($stderrStream, true); $stderr = stream_get_contents($stderrStream); $exitCode = ssh2_get_exit_status($stream); // 将结果写入临时文件,供父进程读取 $resultFile = "/tmp/remote_result_{$node['ip']}"; file_put_contents($resultFile, json_encode([ 'node' => $node['ip'], 'exitCode' => $exitCode, 'stdout' => $stdout, 'stderr' => $stderr ])); // 子进程执行完毕退出 exit(0); } else { // 父进程:记录子进程ID $childProcesses[$pid] = $node['ip']; } } // 父进程等待所有子进程完成,然后收集结果 foreach ($childProcesses as $pid => $nodeIp) { pcntl_waitpid($pid, $status); // 读取子进程写入的结果 $resultFile = "/tmp/remote_result_{$nodeIp}"; $result = json_decode(file_get_contents($resultFile), true); echo "=== 节点 {$result['node']} 执行结果 ===\n"; echo "退出码: {$result['exitCode']}\n"; echo "标准输出:\n{$result['stdout']}\n"; echo "错误输出:\n{$result['stderr']}\n\n"; // 清理临时文件 unlink($resultFile); } ?>
这个方案通过子进程实现并行,每个节点的执行互不阻塞,能大幅提升多节点任务的处理效率。
三、关键注意事项
- 确保PHP安装了
ssh2扩展:可以用php -m | grep ssh2检查,没装的话可以通过pecl install ssh2(需要先安装libssh2依赖)或者系统包管理器安装(比如apt install php-ssh2或yum install php-ssh2) - 必须设置流为阻塞模式:否则可能只获取到部分输出内容
- 远程脚本要加可执行权限:在从节点执行
chmod +x /path/to/your_script.sh - 如果节点数量很多,建议限制子进程数量,避免耗尽系统资源,可以通过分批fork来实现
内容的提问来源于stack exchange,提问作者user3347598
相关产品推荐
相关产品推荐

