php实现简易版ansible
- 快速搭建php环境
- 从bin.dixyes.cn/下载php
- 用static-php-cli 编译一个带swow扩展的php
simple-ansible.php
<?php declare(strict_types=1); ini_set('display_errors', 'on'); ini_set('display_startup_errors', 'on'); ini_set('memory_limit', '1G'); error_reporting(E_ALL); date_default_timezone_set('Asia/Shanghai'); require 'vendor/autoload.php'; use Swow\Coroutine; use Swow\Sync\WaitReference; use phpseclib3\Net\SSH2; use phpseclib3\Crypt\PublicKeyLoader; $hosts = $argv[1] ?? ''; if (empty($hosts)) { throw new \Exception('请输入主机'); } if (str_ends_with($hosts, '.php') && is_file($hosts)) { $hosts = require $hosts; } else { $hosts = explode(',', $hosts); } $command = $argv[2] ?? ''; if (empty($command)) { throw new \Exception('请输入命令'); } if (str_ends_with($command, '.php') && is_file($command)) { require $command; } else { function run(SSH2 $ssh, string $host) { global $command; try { $result = $ssh->exec($command); } catch (\Throwable $e) { $result = 'error: ' . $e->getMessage(); } echo PHP_EOL, $host . ':', PHP_EOL, $result, PHP_EOL; } } $timeout = (int)($argv[3] ?? 30); $privateKey = PublicKeyLoader::load(file_get_contents('/root/.ssh/id_rsa')); $s = microtime(true); $wr = new WaitReference(); foreach ($hosts as $host) { Coroutine::run(function () use ($wr, $host, $privateKey, $timeout) { try { $ssh = new SSH2($host); $ssh->setTimeout($timeout); if (!$ssh->login('root', $privateKey)) { throw new \Exception('登录失败'); } run($ssh, $host); } catch (\Throwable $e) { echo $host . '.error: ' . $e->getMessage(), PHP_EOL; } }); } WaitReference::wait($wr); $cost = (int)((microtime(true) - $s) * 1000); echo 'cost ' . ($cost / 1000) . ' s', PHP_EOL; /** * 写日志 * @param string $message * @param string $level * @return void */ function logger(string $message, string $level = 'log'): void { echo sprintf( '%s %s %s %s%s', '[' . microdate('Y-m-d Hs.v') . ']', '[' . $level . ']', '[' . Coroutine::getCurrent()->getId() . ']', $message, "\n" ); } /** * 精确到微秒的date方法 * @param string $format * @return string */ function microdate(string $format = 'c'): string { return \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($format); }
example-hosts.php
<?php // 可搭配/etc/hosts使用hostname代替Ip return [ '127.0.0.1', '192.168.1.2', ];
example-scripts.php
<?php declare(strict_types=1); use phpseclib3\Net\SSH2; function run(SSH2 $ssh, string $host) { $hostname = rtrim($ssh->exec('hostname')); // 检查网卡错误 $results = array_map(fn($e) => preg_split('/\s+/', $e), explode("\n", rtrim($ssh->exec('netstat -ni')))); if (count($results) < 2 || count($results[1]) < 8 || $results[1][3] != 'RX-ERR' || $results[1][7] != 'TX-ERR') { throw new \Exception($hostname . ' netstat字段不正确: ' . implode(',', $results[1] ?? $results)); } array_shift($results); array_shift($results); foreach ($results as $line) { if (count($line) > 1 && !empty($line[0])) { $interface = $line[0]; if (in_array($interface, ['eno1', 'io'])) { continue; } $rxErrors = isset($line[3]) ? intval($line[3]) : 0; $txErrors = isset($line[7]) ? intval($line[7]) : 0; if ($rxErrors > 1 || $txErrors > 1) { logger($hostname . "-{$interface} 存在错误: 接收错误 {$rxErrors}, 发送错误 {$txErrors}\n", 'error'); } } } }
使用示例
php index.php hosts/example.hosts "echo hello"
php index.php 192.168.1.2 scripts/detect-neterr.php
- 说明
static-php
的出现让搭建php环境变得非常简单,而且不会污染环境,swoole/swow
让php并发执行变得非常容易,很适合对主机批量执行命令。
写这个工具是因为我不是运维,不熟悉ansible那套,平时基本用php搭配简单的shell代码管理服务器,比如用php实现的批量移动工具batch-mv
本作品采用《CC 协议》,转载必须注明作者和本文链接