server deployment using pecl::ssh2

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Это форк мемкеш-срача.

Я сделал проект, в котором есть центральный управляющий сервер и группа рабочих.
Была задача автоматически настраивать рабочие сервера, т.е. так, чтобы "добавлять" сервер через форму на сайте, вводя логин/пароль рута, остальное само.
В принципе, руками все делается несложно: nginx, php, 5 файлов-конфигов, и вперед. Скриптом намного сложней.

Для доступа к серверу я использовал http://pecl.php.net/package/ssh2
Он в бете, в нем есть ошибки. Я смотрю, в этом году его вдруг решили продолжить. Может быть, в нем исправляют то, на что я наткнулся.

Можно было сделать на python/shell/exec().
* shell не устраивал потому что мне надо было делать обработку ошибок и реакцию системы в связке с базой.
* python, скорее всего, был бы лучше, но я выбрал единую среда, один инструмент. PHP хорошо делает почти все, что надо.
* exec тут не подходит потому что не хочется открывать соединение для каждой команды
* proc_open() я не взял потому что не знаю как через него аплоадить файлы, открывать новые соединения не хочется, а расширение, с ограничениями, работает нормально


Работа по ssh тривиальна.
Так я получаю архитектуру сервера (32/64):
PHP:
$SERVER_COMMANDS = array(
    'arch' => array(
                    'command' => 'uname -i',
                    'result' => array('i386','x86_64'),
                    ),
);
$connection = ssh2_connect($ssh_server, SSH_REMOTE_PORT);
if (!$connection){
    //error handling
}
if (!@ssh2_auth_password($connection, $username, $pass)) {
// если авторизация публичным ключем - то
//if (!@ssh2_auth_pubkey_file($connection, $username, $SSH_PUBLIC_KEY_FILE, $SSH_PRIVATE_KEY_FILE)) {
    //error handling
}
$stream = ssh2_exec($connection, $SERVER_COMMANDS['arch']['command']);
stream_set_blocking($stream, true);
$arch = strtolower(trim(stream_get_contents($stream)));
fclose($stream);
if (!in_array($arch,$SERVER_COMMANDS['arch']['result'])){
    //error handling
}
Теперь о главном - о проблемах и решениях.

* проблема сборки расширения. В исходнике pecl::ssh2 надо (было) подправить руками 1 строку, чтоб собрать под 5.3. Вероятно, уже исправили.
* pecl::ssh2 не умел (щас не знаю) ловить таймаут соединения. Если порт закрыт, скрипт будет висеть. Перед соединением проверяю доступность порта:
PHP:
if (!fsockopen($ssh_server,SSH_REMOTE_PORT,$errno,$timeout)){ ...
* Процесс настройки - долгий, держать соединение с браузером - плохо. Я задачу разделил на 2 части: web-скрипт проверяет доступность сервера и логин, пишет информацию о сервере в базу и запускает другой скрипт, который выполняет фоном остальное.
* Следствие предыдущих двух. Если связь падает во время аплоада файла, скрипт подвисает.
Решение одно: kill -9. У меня есть менеджер демонов, который умеет отслеживать, убивать и перезапускать жестко подвисшие демоны. Могу выложить.
* Не хочется хранить рутовский пароль, а соединяться с сервером надо будет многократно.
а) Я решил, что могу пойти на риск и писать в .ssh/authenticated_keys. Да, опасно, что кто-то залезет на центральный сервер и получит сразу все, но tradeoff неизбежен.
б) /etc/sudoers позволяет выполнять определенные команды от веб-скрипта с рутовскими пермиссиями. Релоадить nginx, например:
Код:
                    Defaults:nobody !requiretty.
                    nobody ALL=(root) NOPASSWD: /sbin/service nginx reload.
* Откуда брать софт? Лучше всего - свой репозиторий. Если не хотите/не умеете его настраивать - можно просто аплоадить по ssh пакеты и ставить их командами.
* Надо принять во внимание архитектуру и версию OS. Это можно решить или через репозиторий, или через разные наборы команд.

В скрипте настройки сервера самое важное - обработка ошибок.
Даже если мы знаем, какая будет OS, есть сотни вариантов, что с сервером может быть не так.
Команд для настройки сервера надо выполнить штук 30. Я их сгруппировал и описал в виде 3-мерного массива:
PHP:
$SERVER_COMMANDS = array(
    'yum' => array(
                    'command' => 'yum -y install gdb nginx-stable php php-fpm php-cli php-pdo',
                    'result' => 'error',
                    ),
    'mkdir http_root' => array(
                    'command' => 'mkdir /www/http_root -p',
                    'result' => 'cannot create',
                    ),
'result' - признак ошибки или успешности выполнения команды.

Код выполнения команд и обработки ошибок я вынес в функцию:
PHP:
function rexec($command,$result_check_indentical=false){
    $SERVER_COMMANDS = $GLOBALS['SERVER_COMMANDS'];
    $sender_id = $GLOBALS['sender_id'];
    $Log = ServerConfiguringLogs::getInstance();
    $connection = $GLOBALS['connection'];
    $Log->Log(
            'Executing command:'.$SERVER_COMMANDS[$command]['command'].'... ',
            ServerConfiguringLogs::LOG_MESSAGE_DELIMITER_NO
            );
    $stream = ssh2_exec($connection, $SERVER_COMMANDS[$command]['command']);
    stream_set_blocking($stream, true);
    $command_result = strtolower(trim(stream_get_contents($stream)));
    fclose($stream);
    if ($SERVER_COMMANDS[$command]['result']
        && (
            ($result_check_indentical && ($SERVER_COMMANDS[$command]['result'] != $command_result))
            ||
            (!$result_check_indentical && (false !== strpos($command_result,$SERVER_COMMANDS[$command]['result'])))
            )
    ){
        $Log->Log('ERROR'."\r\n".$command_result);
        DB\senders::getInstance()->setSenderStatusFailed($sender_id);
        exit;
    }
    $Log->Log('OK');
}
Ага, глобальные переменные, влом было. Перепишите нормально, когда будете писать свое.

Установка сервера сводится к последовательности вызовов:
PHP:
/** SET UP REPOSITORIES **/
rexec('epel');

/** INSTALL PACKAGES **/
rexec('yum');

/** CREATE FOLDERS **/
rexec('mkdir http_root');

/** UPLOAD WEB SERVER CONFIG FILES **/
upload('php-custom.ini');
upload('nginx.conf');
upload() - такая же функция по аплоаду файлов через ssh, используя @ssh2_scp_send()


Моя задача - писать подробный протокол процесса настройки, чтобы при любой ошибке сразу увидеть, что не так с сервером. А бывает очень много чего:

* Сервера часто идут с пустым /etc/resolv.conf
решение - добавить в него dns-сервер, и, возможно, директиву "search 127.0.0.1"
* На сервере бывают сбиты часы. Это надо проверить и записать в лог ошибку. Скрипт не знает, какая у сервера временная зона и как решать эту проблему.
* Маленький ulimit. Ну, поднять :)
echo 'php hard nofile 4000' >/etc/security/limits.d/php.conf; ulimit -H -n 4000
* на нем может стоять апач и даже Plesk! :(
Или могут пытаться установить сервер повторно.
Проще всего - не проверяя удалять кучу софта через 'yum -y remove ...'


В конце надо запустить и проверить работу всех служб и удалить временные файлы.
PHP:
    'log parser is running' => array(
                    'command' => 'ps aux |grep log_parser.php|wc -l',
                    'result' => '2',
                    ),
Если что-то не так, как надо - выставлять в базе ошибку в статусе сервера и давать доступ к логу настройки. Задача админа - открыть лог, увидеть проблему, устранить и запустить настройку заново.
 

tony2001

TeaM PHPClub
grigori, см. вот тут:
http://github.com/tony2001/libssh2 - патченая оригинальная либа с авторизацией по ключу
http://github.com/tony2001/libpssh - наша либа для асинхронных коннектов/команд
http://github.com/tony2001/pssh_extension - экстеншен к нашей либе

вкратце, это то, что мы используем для управления серверами, раскладки бинарников, кода и др.
еснно, это только самый нижний уровень, наверху масса самописных утилит на базе этого.
 

fixxxer

К.О.
Партнер клуба
grigori, см. вот тут:
http://github.com/tony2001/libssh2 - патченая оригинальная либа с авторизацией по ключу
http://github.com/tony2001/libpssh - наша либа для асинхронных коннектов/команд
http://github.com/tony2001/pssh_extension - экстеншен к нашей либе
о, круто! спасибо.

а то я ввиду убогости pecl-экстеншена плюнул и через proc_open(ssh ...) все делаю. :)
 
Сверху