php实现简易版ansible

  1. 快速搭建php环境
  1. 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);
    }
  2. example-hosts.php

    <?php
    // 可搭配/etc/hosts使用hostname代替Ip
    return [
     '127.0.0.1',
     '192.168.1.2',
    ];
  3. 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');
             }
         }
     }
    }
  4. 使用示例

  • php index.php hosts/example.hosts "echo hello"
  • php index.php 192.168.1.2 scripts/detect-neterr.php
  1. 说明
    static-php的出现让搭建php环境变得非常简单,而且不会污染环境,swoole/swow让php并发执行变得非常容易,很适合对主机批量执行命令。
    写这个工具是因为我不是运维,不熟悉ansible那套,平时基本用php搭配简单的shell代码管理服务器,比如用php实现的批量移动工具batch-mv
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!