PHP项目批量执行SSH命令如何汇总结果

wen PHP项目 28

本文目录导读:

PHP项目批量执行SSH命令如何汇总结果

  1. 方案一:使用 phpseclib 库(纯PHP实现,无需SSH2扩展)
  2. 方案二:使用 SSH2 扩展(性能更好)
  3. 方案三:并行执行(提高效率)
  4. 方案四:使用 Symfony Process 组件(推荐生产环境)
  5. 结果汇总与展示
  6. 推荐使用场景
  7. 建议

在PHP项目中批量执行SSH命令并汇总结果,常见的有以下几种方案:

使用 phpseclib 库(纯PHP实现,无需SSH2扩展)

安装

composer require phpseclib/phpseclib:~3.0

批量执行示例

<?php
require_once 'vendor/autoload.php';
use phpseclib3\Net\SSH2;
class SSHCommandExecutor
{
    private $results = [];
    public function executeBatch($servers, $commands)
    {
        foreach ($servers as $index => $server) {
            $ssh = new SSH2($server['host'], $server['port'] ?? 22);
            if (!$ssh->login($server['username'], $server['password'])) {
                $this->results[] = [
                    'server' => $server['host'],
                    'status' => 'failure',
                    'error' => 'Login failed'
                ];
                continue;
            }
            $serverResults = [];
            foreach ($commands as $cmd) {
                $output = $ssh->exec($cmd);
                $serverResults[] = [
                    'command' => $cmd,
                    'output' => $output,
                    'exit_code' => $ssh->getExitStatus()
                ];
            }
            $this->results[] = [
                'server' => $server['host'],
                'status' => 'success',
                'commands' => $serverResults
            ];
            $ssh->disconnect();
        }
        return $this->results;
    }
    public function summarizeResults()
    {
        $summary = [
            'total_servers' => count($this->results),
            'successful' => 0,
            'failed' => 0,
            'server_details' => []
        ];
        foreach ($this->results as $result) {
            if ($result['status'] === 'success') {
                $summary['successful']++;
            } else {
                $summary['failed']++;
            }
            $summary['server_details'][] = $result;
        }
        return $summary;
    }
}
// 使用示例
$executor = new SSHCommandExecutor();
$servers = [
    ['host' => '192.168.1.100', 'username' => 'root', 'password' => 'pass1'],
    ['host' => '192.168.1.101', 'username' => 'root', 'password' => 'pass2']
];
$commands = [
    'uptime',
    'free -h',
    'df -h'
];
$results = $executor->executeBatch($servers, $commands);
$summary = $executor->summarizeResults();
// 输出结果
echo json_encode($summary, JSON_PRETTY_PRINT);

使用 SSH2 扩展(性能更好)

<?php
class SSH2BatchExecutor
{
    private $connections = [];
    private $results = [];
    public function connectBatch($servers)
    {
        foreach ($servers as $key => $server) {
            $connection = ssh2_connect($server['host'], $server['port'] ?? 22);
            if (!$connection) {
                $this->results[$key] = [
                    'status' => 'failure',
                    'error' => 'Connection failed'
                ];
                continue;
            }
            if (!ssh2_auth_password($connection, $server['username'], $server['password'])) {
                $this->results[$key] = [
                    'status' => 'failure',
                    'error' => 'Authentication failed'
                ];
                continue;
            }
            $this->connections[$key] = $connection;
        }
    }
    public function executeCommands($commands)
    {
        foreach ($this->connections as $key => $connection) {
            $commandResults = [];
            foreach ($commands as $cmd) {
                $stream = ssh2_exec($connection, $cmd);
                stream_set_blocking($stream, true);
                $stdout = stream_get_contents($stream);
                fclose($stream);
                // 获取stderr
                $stderr = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
                $errorOutput = stream_get_contents($stderr);
                fclose($stderr);
                $commandResults[] = [
                    'command' => $cmd,
                    'stdout' => $stdout,
                    'stderr' => $errorOutput
                ];
            }
            $this->results[$key] = [
                'server' => $key,
                'status' => 'success',
                'commands' => $commandResults
            ];
        }
        return $this->results;
    }
    public function generateReport($format = 'table')
    {
        $report = [];
        foreach ($this->results as $server => $result) {
            if ($result['status'] === 'success') {
                foreach ($result['commands'] as $cmdResult) {
                    $report[] = [
                        'server' => $server,
                        'command' => $cmdResult['command'],
                        'status' => 'success',
                        'output' => $cmdResult['stdout']
                    ];
                }
            } else {
                $report[] = [
                    'server' => $server,
                    'command' => 'N/A',
                    'status' => 'failed',
                    'output' => $result['error']
                ];
            }
        }
        return $report;
    }
    public function disconnectAll()
    {
        foreach ($this->connections as $connection) {
            ssh2_disconnect($connection);
        }
    }
}

并行执行(提高效率)

<?php
class ParallelSSHExecutor
{
    private $results = [];
    public function executeParallel($servers, $commands, $callback = null)
    {
        $processes = [];
        $pipes = [];
        foreach ($servers as $index => $server) {
            $command = sprintf(
                'sshpass -p %s ssh -o StrictHostKeyChecking=no %s@%s "%s"',
                escapeshellarg($server['password']),
                escapeshellarg($server['username']),
                escapeshellarg($server['host']),
                escapeshellarg(implode(' && ', $commands))
            );
            $processes[$index] = proc_open(
                $command,
                [
                    0 => ['pipe', 'r'],
                    1 => ['pipe', 'w'],
                    2 => ['pipe', 'w']
                ],
                $pipes[$index]
            );
        }
        // 读取结果
        foreach ($processes as $index => $process) {
            $stdout = stream_get_contents($pipes[$index][1]);
            $stderr = stream_get_contents($pipes[$index][2]);
            fclose($pipes[$index][0]);
            fclose($pipes[$index][1]);
            fclose($pipes[$index][2]);
            $exitCode = proc_close($process);
            $result = [
                'server' => $servers[$index]['host'],
                'stdout' => $stdout,
                'stderr' => $stderr,
                'exit_code' => $exitCode
            ];
            $this->results[] = $result;
            if ($callback) {
                $callback($result);
            }
        }
        return $this->results;
    }
}

使用 Symfony Process 组件(推荐生产环境)

<?php
require_once 'vendor/autoload.php';
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
class SymfonySSHExecutor
{
    private $results = [];
    public function executeWithProcessBuilder($servers, $commands)
    {
        $processes = [];
        foreach ($servers as $index => $server) {
            $sshCommand = sprintf(
                'ssh %s@%s "%s"',
                $server['username'],
                $server['host'],
                implode(' && ', $commands)
            );
            $process = new Process(['bash', '-c', $sshCommand]);
            $process->setTimeout(3600);
            $process->setIdleTimeout(60);
            $processes[$index] = $process;
        }
        // 并行执行
        foreach ($processes as $index => $process) {
            try {
                $process->mustRun();
                $this->results[] = [
                    'server' => $servers[$index]['host'],
                    'status' => 'success',
                    'output' => $process->getOutput(),
                    'error_output' => $process->getErrorOutput()
                ];
            } catch (ProcessFailedException $exception) {
                $this->results[] = [
                    'server' => $servers[$index]['host'],
                    'status' => 'failure',
                    'error' => $exception->getMessage()
                ];
            }
        }
        return $this->results;
    }
    public function exportToCSV($filename = 'ssh_results.csv')
    {
        $fp = fopen($filename, 'w');
        // CSV头部
        fputcsv($fp, ['Server', 'Status', 'Output', 'Error']);
        foreach ($this->results as $result) {
            fputcsv($fp, [
                $result['server'],
                $result['status'],
                $result['output'] ?? '',
                $result['error'] ?? ''
            ]);
        }
        fclose($fp);
        return $filename;
    }
}

结果汇总与展示

<?php
class ResultAggregator
{
    public static function aggregate($results)
    {
        $aggregated = [
            'total_commands' => 0,
            'successful' => 0,
            'failed' => 0,
            'errors' => [],
            'servers_summary' => []
        ];
        foreach ($results as $serverResult) {
            $serverKey = $serverResult['server'];
            if (!isset($aggregated['servers_summary'][$serverKey])) {
                $aggregated['servers_summary'][$serverKey] = [
                    'total' => 0,
                    'success' => 0,
                    'fail' => 0
                ];
            }
            if ($serverResult['status'] === 'success') {
                $aggregated['successful']++;
                $aggregated['servers_summary'][$serverKey]['success']++;
            } else {
                $aggregated['failed']++;
                $aggregated['servers_summary'][$serverKey]['fail']++;
                $aggregated['errors'][] = $serverResult['error'];
            }
            $aggregated['total_commands']++;
            $aggregated['servers_summary'][$serverKey]['total']++;
        }
        return $aggregated;
    }
    public static function renderHTML($results)
    {
        $html = '<table border="1">';
        $html .= '<tr><th>Server</th><th>Command</th><th>Status</th><th>Output</th></tr>';
        foreach ($results as $result) {
            $statusClass = $result['status'] === 'success' ? 'success' : 'error';
            $html .= sprintf(
                '<tr class="%s"><td>%s</td><td>%s</td><td>%s</td><td><pre>%s</pre></td></tr>',
                $statusClass,
                htmlspecialchars($result['server']),
                htmlspecialchars($result['command'] ?? 'N/A'),
                htmlspecialchars($result['status']),
                htmlspecialchars($result['output'] ?? $result['error'] ?? '')
            );
        }
        $html .= '</table>';
        return $html;
    }
}

推荐使用场景

方案 适用场景 优点 缺点
phpseclib 无SSH2扩展的环境 纯PHP实现,兼容性好 性能相对较慢
SSH2扩展 高性能需求 速度快,原生支持 需要安装扩展
并行执行 大规模服务器集群 效率高 资源消耗大
Symfony Process 生产环境 功能完善,异常处理好 依赖包较重

建议

  1. 生产环境:建议使用 phpseclibSymfony Process 组件
  2. 性能优化:对大量服务器使用并行执行
  3. 错误处理:一定要捕获并记录SSH连接失败、命令执行失败等异常
  4. 安全考虑:使用密钥认证代替密码,或使用SSH配置管理工具
  5. 结果持久化:可以将结果保存到数据库或文件系统中

抱歉,评论功能暂时关闭!