Deployer — удобный и гибкий деплой приложений

    Несомненно, тема, думаю, многими заезжена до дыр — всё-таки, деплой надо делать для каждого проекта — но я всё же подниму её и расскажу об одном замечательном инструменте, о котором, по какой-то странной причине, до сих пор ничего не написали на Хабре, да и вообще в русскоязычном сегменте как-то о нём мало что написано. Исправим это недоразумение.


    Deployer хорош во многих отношениях. Код скрипта для деплоя получается коротким. Написан на старом добром Пыхчанском — то бишь, скорее всего, ставить отдельно какие-то другие инструменты на сервер вам не придётся. Если же и придётся — то PHP обычно устанавливается одной командой на любом сервере. Почему-бы и не заюзать его в своих проектах?


    Написал утилиту некий Антон Медведев, у него кстати довольно приятный блог есть. Спасибо тебе, Антон :)


    Первый коммит был сделан аж в 2013-м году, и до сих пор инструмент потихоньку развивается. У него также есть приятный сайт, на котором можно найти о нём всю документацию.


    Что лично мне нравится больше всего из того, что даёт данный инструмент — это возможность быстро откатиться на прошлый "рабочий" релиз, если новый релиз оказался неудачным. Также довольно удобно то, что если при попытке "выкатить" новый релиз что-то пойдёт не так (миграции не применились, фронтенд файлы не скомпилировались, тесты не прогнались..) — то ваше текущее работающее приложение это никак не затронет — оно будет работать как ни в чём ни бывало. Дело в том, что Deployer не изменит ссылку у директории, обозначающей текущий активный релиз, до того момента, пока ваш новый "релиз" не будет полностью установлен и готов к работе.


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


    Структура папок релизов


    Весь проект разделяется на три папки: current, releases и shared. В общем, это довольно распространённый вид для подобных инструментов, и он действительно удобен. Скажем, в одном из моих проектов на Laravel эта структура выглядит вот так:



    current — ссылка на папку последнего успешно собранного релиза, т.е. текущее приложение.
    releases — все релизы, которые были собраны. По умолчанию сохраняется только последние три релиза, и это значение можно легко поменять.
    shared — папка, в которой находятся все "общие" файлы, которые относятся ко всем релизам одновременно и не должны создаваться каждый раз заново. К примеру — файл .env, загруженые пользователями файлы, и так далее.


    Пример скрипта деплоя Laravel приложения


    Я люблю лично зайти на свой сервер, запустить скрипт деплоя и наблюдать за процессом его работы. Просто, мне так намного спокойнее жить, так как я всегда могу предпринять какие-то срочные меры, если при деплое что-то пойдёт не так. А так, как я знаю, люди обычно запускают подобный скрипт со своей локальной машины, которая подключается по SSH к серверу и производит деплой. Если это надо сделать сразу на нескольких машинах — то такой подход конечно будет удобнее. К слову, Deployer позволяет выполнять деплой сразу на нескольких машинах в том числе.


    Естественно, перед тем как получить возможность выполнить данный скрипт, вам необходимо сначала установить Deployer на свою систему.


    В одном из моих проектов на Laravel 5 скрипт деплоя deploy.php выглядит следующим образом:


    <?php
    
    // Подключим основные рецепты из Deployer'а
    require 'recipe/common.php';
    require 'recipe/laravel.php';
    
    // Укажем основные параметры деплоя
    localServer('local', 'localhost')
        ->user('{ваш-пользователь}')
        ->env('deploy_path', '/path/to/project/dir')
        ->stage('local')
    ;
    
    set('repository', '{ваш-git-репозиторий.git}');
    env('branch', '{ветка-для-релизов}');
    
    env('git_cache', true);
    
    // Общие папки для вашего проекта, которые будут прозрачно доступны всем релизам
    // Они не будут создаваться заново при новом релизе, вместо них будут созданы
    // ссылки на их одноимённые папки в папке shared
    set('shared_dirs', [
        'storage/app',
        'storage/framework/cache',
        'storage/framework/sessions',
        'storage/framework/views',
        'storage/logs',
        'public/uploads',
        'node_modules',
    ]);
    
    // Общие файлы. Принцип точно такой же, как с папками
    // В случае с Laravel нам необходимо сделать "общим" лишь один
    // файл - .env
    set('shared_files', ['.env']);
    
    // Папки, в которые приложение должно иметь возможность
    // писать данные. В нашем случае - это три директории
    set('writable_dirs', ['storage', 'vendor', 'public/uploads' ]);
    
    set('http_user', '{ваш-пользователь}');
    set('composer_command', '/usr/local/bin/composer'); // Путь к расположение Composer'а
    
    // Задача для деплоя. Установить NPM компоненты
    task('deploy:install-npm', function() {
        run('cd {{release_path}} && npm i');
    });
    
    // Ещё одна задача: скомпилировать все фронтенд файлы, в моём случае
    // это делается через Grunt.js
    task('deploy:compile-assets', function() {
        run('cd {{release_path}} && grunt deploy-production');
    });
    
    // Выполнить миграции
    task('deploy:migrations', function() {
        run('cd {{release_path}} && php artisan migrate --force');
    });
    
    // Создать кеш для правил роутинга
    task('deploy:create-route-cache', function() {
        run('cd {{release_path}} && php artisan route:cache');
    });
    
    // Создать кеш для файлов конфигураций
    task('deploy:create-config-cache', function() {
        run('cd {{release_path}} && php artisan config:cache');
    });
    
    // Очистить все закешированные данные
    task('deploy:clean-cached-data', function() {
        run('cd {{release_path}} && rm bootstrap/cache/*');
    });
    
    // Перезапустить PHP после успешного деплоя
    task('reload:php-fpm', function() {
        run('sudo /usr/sbin/service php7.0-fpm restart');
    });
    
    task('deploy', [
        'deploy:prepare',
        'deploy:release',
        'deploy:update_code',            // Скачать последний код с гитхаба
        'deploy:shared',                 // Создать ссылки на общие данные
        'deploy:vendors',                // Обновить компоненты композера
        'deploy:clean-cached-data',      // Очистить все закешированные данные
        'deploy:create-route-cache',     // Создать кеш для правил роутинга
        'deploy:create-config-cache',    // Создать кеш для файлов конфигураций
        'deploy:install-npm',            // Обновить NPM компоненты
        'deploy:compile-assets',         // Скомпилировать фронтенд файлы
        'deploy:migrations',             // Применить миграции
        'deploy:symlink',                // создать ссылку текущего релиза на этот
        'cleanup',                       // Удалить старые релизы
    ])->desc('Deploy your project');
    
    after('deploy', 'success');
    
    // После деплоя перезапустим php
    after('deploy', 'reload:php-fpm');
    
    // После отката на прошлый релиз - тоже перезапустим его
    after('rollback', 'reload:php-fpm');

    Также у меня есть пара маленьких файлов, лежащих рядом с вышеуказанным файлом: start-deploy.sh и rollback-deploy.sh. Для того чтобы быстро запустить деплой или, соответственно, откатить его.


    Файл start-deploy.sh:


    dep deploy local

    Файл rollback-deploy.sh:


    dep rollback local

    Следовательно, чтобы запустить процесс деплоя, нам остаётся набрать лишь одну команду в Bash'е:


    ./start-deploy.sh

    Таким образом, как мы видим, введя всего одну команду, мы заставим сервер выполнить все необходимые шаги для разворачивания нашего проекта. И только если всё прошло хорошо, папка current сменит ссылку на новый релиз и перезапустит PHP после всего этого.


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

    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 27
      0
      Если развертывать локально на серваке, то деплойер так то особо и не нужен. Его идеология предполагает, что он используется для удаленного деплоя на различных сервера, включая staging и production серверы.

      Хотя, я считаю, что вместо deployer вполне можно использовать capistrano, так как у него сообщество все равно больше развито, чем у deployer и капистрано есть плагины для разворачивания проектов как на руби, так и на пхп и так на ноде.

      github.com/capistrano/laravel
      Вот плагин для ларавел
        +2
        Как бы сказать, «особо и не нужен». Одно дело вбить одну команду; другое дело — ворох этих команд, и в правильном порядке, и ничего не забыв.

        Да и об идеологии такой я не слышал. Есть инструмент, есть его варианты использования. Какая разница, как ты его будешь использовать, если от него в любом случае есть польза, и в том, и в другом случае?

        И чем конкретно капистрано лучше деплоера? Вот сижу я сейчас на деплоере, вроде счастливый и довольный. Зачем мне капистрано?
          –1
          И чем конкретно капистрано лучше деплоера?

          Тем что написан раньше, отлажен лучше, больше готовых плагинов. Основан на rake (ruby's make) и в-принципе делает всё, что надо.


          Для капистраны не надо ставить на удалённой ноде ничего — только нужен ssh-доступ.

            +2

            Написан всего на пол года раньше. Да и вообще — не очень это сильный довод, смотреть, кто когда был написан.


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


            Основан на rake — ну это вообще не довод и не повод. Каждый инструмент на чём-то там основан.


            Для деплоера тоже, при желании, не надо ставить на удалённой ноде ничего — он точно также может работать по ssh, это просто я лично люблю сам заходить на сервер и делать деплой руками, мне так спокойнее.


            По суди, разница по функциям между инструментами совсем небольшая. Деплоер ещё попроще будет: для его работы достаточно всего одного файла-конфига. Но, при желании, можно и "расширить" конфигурацию, разбив её несколько файлов.

            +3
            Использовал и тот, и другой. Деплоер неплохо решает свои задачи на большинстве проектов, ничем не хуже капистрано. Но, на мой взгляд, если проект написан на PHP — лучше использовать деплоер. Чем меньше разных технологий — тем проще поддерживать проект.
            0
            И Деплоер точно также подходит и для любых других проектов. Причем, что удобно — можно его бинарник прямо в корень проекта положить и запускать без проблем на любой машине: он ведь ничего не требует для своей работы, даже пхп.

            Лаконично и очень даже удобно, как по мне.

            Что насчет капистрано — то это обязательная установка руби, бандлера, и так далее. А разницы как минимум никакой, как я вижу на данный момент.
              +1
              А вот по поводу этого, сэр, хочу поспорить. Deployer написан на PHP и успакован в исполняемый Phar. А чтобы запустить Phar обязательно нужен PHP. Даже на официальном сайте написано:

              «Deployer is a deployment tool written in PHP»
                0
                Я полагал, что phar архив становится исполняемым приложением :) Да, я ошибся. PHP для работы Deployer'а нужен.
                +1
                Разве phar архивы не требуют php?
                  0
                  Как долго проходил коммент модерацию) Уже успели даже ответить
              0
              Когда последний раз пользовался, то столкнулся с тем, что например для dev'а нельзя выбрать другую ветку, или не внимательно читал доки, или нет такой возможности.
                +1
                Есть параметр --tag=branch|tag

                И тогда будет брать из нужно ветки. По умолчанию master.

                Но опять же, значение по умолчанию можно задать в конфиге
                0
                Рекомендую еще попробовать Rocketeer
                  –3
                  … Или докер.
                    0
                    Лично, мне не нравится в rocketeer, то, что он тянет вендоры laravel, для laravel проекта — никаких проблем.
                    Но, например, я работаю с symfony и мне не очень нравится, что тянуться ненужные зависимости.

                    deployer в этом плане мне понрвавился больше. хоть мы и пользовались capifony до этого.
                    + в том, что не нужно ставить руби, чтобы деплоить.
                      0
                      Ставьте rocketeer глобально и тогда в vendor проекта ничего не попадет.
                    0
                    Да, это гораздо лаконичнее того, что я когда-то писал xml-ем под phing :) Правда под ним был еще dbdeploy, позволявший управлять миграциями в БД.
                    Надо попробовать.
                      0
                      В более менее крупном проекте билдить проект на боевых серверах – не очень хорошая затея. Допустим, у нас 10 серверов. Во-первых, билдиться будет 10 раз на каждом из серверов. Во-вторых при билде проекта выполняются ресурсоемкие операции. Например, разогрев кеша крупного symfony-проекта нормально так догружает сервер. Т.е. у нас все сервера, которые под боевой нагрузкой, одновременно начинают параллельно нагружаются деплой-процессом, а это может привести к печальному результату, например к просадке времени отдачи страниц у пользователей.

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

                        Здравствуйте) Очень интересно. А каким образом вы еще и кеш разогретый умудряетесь положить прямо в билд?

                          0
                          Я про кеш Symfony, тот набор сгенерированных php-файлов, лежащих в папке app/cache/.
                            0

                            Я думал про opcache и apc :) Спасибо, теперь понятно.

                        0
                        Давать пользователю выполнять /usr/bin/service это очень плохая идея.
                        task('reload:php-fpm', function() {
                        run('sudo /usr/sbin/service php7.0-fpm restart');
                        });

                        Потому что помимо upstart/systemd сервисов, /usr/bin/service может стартовать и обычные SysV инит скрипты:
                        # Otherwise, use the traditional sysvinit
                        if [ -x "${SERVICEDIR}/${SERVICE}" ]; then
                        exec env -i LANG="$LANG" PATH="$PATH" TERM="$TERM" "$SERVICEDIR/$SERVICE" ${ACTION} ${OPTIONS}
                        else
                        echo "${SERVICE}: unrecognized service" >&2
                        exit 1
                        fi

                        и единственная проверка для таких файлов — это проверка на существование!

                        Как следствие можно легко и непринужденно можно поднять права до рута, имея ограниченный доступ к серверу:
                        $ cd /tmp; echo «id» > awesome_service.sh; chmod +x awesome_service.sh
                        $ sudo /usr/bin/service ../../tmp/awesome_service.sh
                        uid=0(root) gid=0(root) группы=0(root)
                          0

                          Здравствуйте) Но ведь всё это делается только через судо, т.е. нужно пароль ввести для выполнения подобной операции? И как ещё такие вещи давать ему выполнять? Или как вы это делаете у себя, чтобы было реально безопасно?

                            0
                            То есть при деплое на десяток серверов вы будете каждый раз вводить пароль? А если серверов больше десяти и пароли разные? Я предполагал что у вас в /etc/sudoers разрешен запуск service без пароля (что как раз таки не безопасно).

                            Для себя я пока не нашел красивого решения, завел отдельного пользователя (от которого не запускаются приложения), разрешил ему в sudoers запускать service. Тогда конфиг deployer'а можно исправить на
                            runLocally("ssh specialuser@server service php5-fpm restart");
                            


                            P.S. не разобрался как тег source использовать, в превью все хорошо, а в самом сообщении — нет. А нет, это видимо из-за предмодерации было.
                              0

                              У нас пока нет десятка серверов :) Поэтому это пока не стоит проблемой. Спасибо за разъяснения.

                                0
                                Для себя я пока не нашел красивого решения

                                Запускать php-fpm не от рута не вариант?
                                  0
                                  в /etc/sudoers можно разрешить запуск service только с конкретным аргументом (php7.0-fpm restart')

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое