Коротко: деплой — это четыре стадии: загрузить код, поставить зависимости, прогнать миграции и очистить кэш; разбираем простой рабочий вариант для Symfony.

Задеплоить приложение — это, по сути, четыре стадии: загрузить код на сервер, поставить зависимости, прогнать миграции и очистить кэш. Ниже — один из самых простых рабочих вариантов деплоя на примере Symfony-приложения: что держать в репозитории, как собрать релизный тег по git-flow и что выполнить на сервере.

! ВНИМАНИЕ ! Эта статья не истина в последней инстанции и рассчитана в основном на новичков. Я не утверждаю, что это единственно правильный способ деплоя и что остальные способы не имеют права на жизнь. Здесь описан ОДИН ИЗ МНОГИХ вариантов деплоя в продакшен в одной из самых простых вариаций.

Что должно быть в репозитории?

Общий принцип: в репозитории должна быть только кодовая база приложения. Файлов, которые меняются в зависимости от окружения, там быть не должно. К ним относятся:

  1. .env-файлы и их аналоги — конфигурация (но не yaml, не путать);
  2. папки фронтенда вроде node_modules и их аналоги;
  3. картинки, не относящиеся к вёрстке сайта: логотип нужен в репозитории, а картинки статей — нет;
  4. папки и файлы кэшей и логов — для Symfony это var/log и var/cache;
  5. любые папки с настройками IDE вроде .idea или .vscode.

Всё перечисленное не должно храниться в git.

Какие основные стадии у деплоя?

  1. загрузка кода на сервер;
  2. установка зависимостей (composer / сборка фронта) — опционально;
  3. запуск миграций;
  4. очистка кэша.

Для всех фреймворков подход примерно одинаков. Единственное исключение — пункт 2: тут есть тонкость, и мнения расходятся надвое. Есть два лагеря: те, кто держит vendor в репозитории, и те, кто нет.

Я отношусь к первому лагерю и считаю, что vendor должен лежать в репозитории. Такой подход гарантирует, что на проде окажется именно тот код, который писал программист и тестировал тестировщик, а не подтянется случайно другая версия с какого-то зеркала. На моей практике был случай: версия строго указана в composer.json, у всех всё работало — выкатили на прод, и всё сломалось. Оказалось, на проде установилась другая версия пакета с зеркала, а не с основного репозитория. Поэтому я предпочитаю катить vendor в прод вместе с кодовой базой.

Что часто требуется сделать перед деплоем?

Есть понятие Deploy Notes — заметки, которые стоит выполнять до или после деплоя. В крупных компаниях код часто пишет один человек, ревьюит другой, тестирует третий, а доставляет в прод четвёртый — причём без ведома первых трёх. Чтобы админ или девопс знал, какие дополнительные действия нужны, и существуют Deploy Notes. В них можно указать (например):

  1. запустить composer install или composer update;
  2. запустить npm install && npm build && rm -rf node_modules;
  3. добавить или удалить переменные окружения в .env;
  4. перезапустить воркеры;
  5. создать новый CRON;
  6. и так далее.

Живой пример

Пример актуален для хостинга, VPS и выделенного сервера. Понадобятся базовые навыки работы в консоли Linux (да, «виндоводам» тут будет больно, если раньше с Linux не работали), доступ к консоли сервера по ssh и настроенный git.

Приложение

Допустим, у нас приложение со стеком:

  • PHP 8.1;
  • nginx;
  • MySQL;
  • RabbitMQ;
  • memcached;
  • Symfony 6.2;
  • Vue.js;
  • Composer.

git-flow

Определяем git-flow и предполагаем, что мы работаем с git аккуратно, а не пушим всё в master. Есть ветка master — продовая, и ветка разработки develop. Есть рабочая задача TNT-1156.

Находясь на develop, создаём рабочую ветку: git checkout -b TNT-1156. Активно кодим. Допустим, сделали 3 коммита.

Теперь хотим слить 3 коммита в один, чтобы выполнить правило «1 задача = 1 ветка = 1 коммит». Используем интерактивный ребейз:

git rebase -i HEAD~3

Все коммиты, кроме первого, отмечаем буквой s (squash) — там есть подсказки. Сохраняем коммит с внятным сообщением вида 'TNT-1156 added some awesome feature'. На выходе — готовая ветка с одним коммитом.

Предположим, за спринт (неделю) мы сделали 10 задач и хотим всё задеплоить. Нужно собрать билд и тег, в который войдут 10 веток. Подтягиваем все ветки локально:

git fetch --all

Затем для каждой ветки поочерёдно выполняем:

  1. git checkout TNT-1156 — переключаемся на ветку;
  2. git rebase develop — освежаем ветку от develop и при необходимости решаем конфликты;
  3. git checkout develop — переключаемся на develop;
  4. git merge --no-ff TNT-1156 — вмерживаем ветку задачи в develop.

Так — для всех 10 веток. После того как всё слили в develop локально (напомню — ничего никуда ещё не пушим, всё локально), проверяем, что с прошлого деплоя в develop попало только нужное. Проверить можно через git log или удобный алиас git lg:

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --"

Алиас git lg делает красивую подсветку и отображение для git log в консоли.

Убедившись, что вмержили только нужные ветки, в обязательном порядке запускаем автотесты приложения (надеюсь, они у вас есть). Только при 100% прохождении пушим develop и создаём тег:

git push                 # пушим develop
git tag testing_101.0    # тег для регрессионного тестирования
git push --tags          # пушим тег

Так мы запушили ветку и создали тег для регрессионного тестирования всех 10 задач на предпродовом стенде. Как только тестировщики говорят, что всё ОК, собираем релизный тег:

git checkout master              # переключаемся на master
git merge develop --ff-only      # мержим develop в master (можно мержить тег)
git push                         # пушим master
git tag release_101.0            # релизный тег
git push --tags                  # пушим тег

Непосредственно деплой

Когда релизный тег собран, выкатываем его на прод и выполняем подготовительные работы — тут вспоминаем про Deploy Notes и консоль сервера. Подключаемся по ssh и выполняем:

  1. cd /home/symfony/application/some_route/www — переходим в директорию приложения;
  2. открываем Deploy Notes всех задач и выполняем те, что нужно ДО деплоя;
  3. git stash — убираем все видимые git-изменения (на случай, если кто-то правил прод в обход флоу);
  4. git fetch --all — получаем новые ветки и теги;
  5. git checkout release_101.0 — переключаемся на новый тег;
  6. npm install — ставим фронтовые зависимости;
  7. npm run build — собираем фронтовый билд;
  8. rm -rf /home/symfony/application/some_route/www/node_modules — чистим за сборщиком;
  9. su - some_user_who_runs_app -c '/home/symfony/application/some_route/www/bin/console doctrine:migrations:migrate -n' — запускаем миграции;
  10. su - some_user_who_runs_app -c '/home/symfony/application/some_route/www/bin/console cache:clear' — чистим кэш;
  11. открываем Deploy Notes и выполняем то, что нужно ПОСЛЕ деплоя;
  12. желательно в соседнем окне держать продовые логи: tail -f /home/symfony/application/some_route/www/var/log/prod.log — и смотреть фон ошибок. При неожиданностях сразу откатываемся на предыдущий рабочий тег (фактически это деплой предыдущего тега);
  13. зрительно проверяем основной функционал, что ничего глобально не отвалилось;
  14. наслаждаемся результатом.

На этом деплой можно считать завершённым.

На что обратить внимание

  1. старайтесь следовать правилу «1 задача = 1 миграция» — так намного проще откатывать деплой, если что-то пойдёт не так;
  2. заранее продумывайте схему отката: «деплой оторвало на середине — твои действия?»;
  3. если есть возможность использовать дополнительные инструменты контроля — используйте (о них ниже);
  4. не теряйте бдительность после деплоя — поломки проявляются не сразу. Не стоит задеплоиться и сразу уйти на обед на полтора часа, выключив телефон.

Дополнительные инструменты для деплоев

Существует множество инструментов и подходов для безопасных деплоев и заветных 99,99% аптайма. Среди них:

Автоматизация деплоев

  • Jenkins;
  • TeamCity;
  • TravisCI;
  • Deployer;
  • Ansistrano;
  • и др.

Системы мониторинга

  • Kibana;
  • Grafana;
  • Zabbix;
  • Prometheus;
  • NetData;
  • и др.

Не забывайте и про простые, но эффективные вещи из Linux — например htop, чтобы проверить потребление памяти и нагрузку на процессоры. С дополнительными инструментами не переусердствуйте и подбирайте их внимательно: их много и они очень разные.

А что дальше?

Здесь я максимально просто описал деплой простого приложения на одну ноду под небольшим трафиком. А что делать, если приложение работает, например, на 6 нодах? Тогда стоит почитать про стратегии деплоя — их много, и нужно оценивать, что подойдёт в вашем случае. Что можно погуглить:

  • Rolling — постепенный «накатываемый» деплой;
  • Recreate — повторное создание;
  • Blue/Green deployment;
  • Canary — канареечные развёртывания;
  • Dark — скрытые или A/B деплои.

Если нужно выстроить надёжный процесс деплоя или перейти к многонодовым стратегиям — помогу на консультации.