Развертывание Laravel с нулевым временем простоя

Опубликовано: 2022-03-11

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

При первом подходе мы вносим пошаговые изменения в состояние нашей системы. Например, мы обновляем файлы, изменяем свойства среды, устанавливаем дополнительное необходимое и так далее. При втором подходе мы сносим целые машины и перестраиваем систему с новыми образами и декларативными конфигурациями (например, с помощью Kubernetes).

Развертывание Laravel стало проще

В этой статье в основном рассматриваются относительно небольшие приложения, которые могут не размещаться в облаке, хотя я упомяну, как Kubernetes может значительно помочь нам в развертывании вне сценария «без облака». Мы также обсудим некоторые общие проблемы и советы по выполнению успешных обновлений, которые могут быть применимы в различных ситуациях, а не только при развертывании Laravel.

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

Версии

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

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

Изображение, показывающее объяснение семантического управления версиями.

Управление версиями релиза начинается с системы контроля версий, такой как Git. Допустим, мы подготовили релиз для развертывания, например, версию 1.0.3. Когда дело доходит до организации этих выпусков и потоков кода, существуют различные стили разработки, такие как разработка на основе магистрали и поток Git, которые вы можете выбирать или комбинировать в зависимости от предпочтений вашей команды и специфики вашего проекта. В конце концов, мы, скорее всего, получим наши выпуски, помеченные соответствующим образом в нашей основной ветке.

После фиксации мы можем создать простой тег, подобный этому:

git tag v1.0.3

И затем мы включаем теги при выполнении команды push:

git push <origin> <branch> --tags

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

Получение файлов релиза по назначению

Развертывание Laravel требует времени, даже если это просто копирование файлов. Однако, даже если это не займет слишком много времени, наша цель — добиться нулевого времени простоя .

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

На самом деле, есть различные инструменты и сервисы, которые могут помочь нам с развертыванием, такие как Envoyer.io (дизайнер Laravel.com Джек Макдейд), Capistrano, Deployer и т. д. Я еще не использовал их все в продакшене, поэтому не могу дайте рекомендации или напишите всестороннее сравнение, но позвольте мне продемонстрировать идею этих продуктов. Если некоторые (или все) из них не могут удовлетворить ваши требования, вы всегда можете создать свои собственные сценарии, чтобы автоматизировать процесс наилучшим образом, который вы считаете нужным.

Для целей этой демонстрации предположим, что наше приложение Laravel обслуживается сервером Nginx по следующему пути:

/var/www/demo/public

Во-первых, нам нужен каталог для размещения файлов выпуска каждый раз, когда мы делаем развертывание. Также нам нужна символическая ссылка, которая будет указывать на текущий рабочий релиз. В этом случае /var/www/demo будет нашей символической ссылкой. Переназначение указателя позволит нам быстро менять релизы.

Обработка файлов развертывания Laravel

В случае, если мы имеем дело с сервером Apache, нам может потребоваться разрешить следующие символические ссылки в конфигурации:

Options +FollowSymLinks

Наша структура может быть примерно такой:

 /opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2

Могут быть некоторые файлы, которые нам нужно сохранять в разных развертываниях, например, файлы журналов (если мы, конечно, не используем Logstash). В случае развертывания Laravel мы можем захотеть сохранить каталог хранилища и файл конфигурации .env. Мы можем отделить их от других файлов и вместо этого использовать их символические ссылки.

Чтобы получить наши файлы выпуска из репозитория Git, мы можем использовать команды клонирования или архивирования. Некоторые люди используют git clone, но вы не можете клонировать определенный коммит или тег. Это означает, что извлекается весь репозиторий, а затем выбирается конкретный тег. Когда репозиторий содержит много веток или большую историю, его размер значительно превышает размер архива релиза. Итак, если вам конкретно не нужен репозиторий git для производства, вы можете использовать git archive . Это позволяет нам получить только файловый архив по определенному тегу. Еще одно преимущество использования последнего заключается в том, что мы можем игнорировать некоторые файлы и папки, которые не должны присутствовать в производственной среде, например, тесты. Для этого нам достаточно установить свойство export-ignore в .gitattributes file . В контрольном списке OWASP по безопасному кодированию вы можете найти следующую рекомендацию: «Удалите тестовый код или любую функциональность, не предназначенную для производства, перед развертыванием».

Если мы получаем выпуск из системы контроля версий исходного кода, git archive и export-ignore могут помочь нам с этим требованием.

Давайте взглянем на упрощенный скрипт (он потребует лучшей обработки ошибок в продакшене):

развернуть.sh

 #!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo

Для развертывания нашего выпуска мы могли бы просто выполнить следующее:

deploy.sh v1.0.3

Примечание. В этом примере v1.0.3 — это тег git нашего выпуска.

Композитор на производстве?

Вы могли заметить, что скрипт вызывает Composer для установки зависимостей. Хотя вы видите это во многих статьях, с этим подходом могут быть некоторые проблемы. Как правило, рекомендуется создавать полную сборку приложения и продвигать эту сборку в различных средах тестирования вашей инфраструктуры. В итоге у вас будет тщательно протестированная сборка, которую можно безопасно развернуть в рабочей среде. Несмотря на то, что каждая сборка должна быть воспроизведена с нуля, это не означает, что мы должны перестраивать приложение на разных этапах. Когда мы устанавливаем composer на производство, это не совсем та же сборка, что и протестированная, и вот что может пойти не так:

  • Сетевая ошибка может прервать загрузку зависимостей.
  • Поставщик библиотеки может не всегда следовать SemVer.

Сетевую ошибку можно легко заметить. Наш скрипт даже перестал бы выполняться с ошибкой. Но критическое изменение в библиотеке может быть очень сложно определить без запуска тестов, которые вы не можете сделать в рабочей среде. При установке зависимостей Composer, npm и другие подобные инструменты полагаются на семантическое управление версиями — major.minor.patch. Если вы видите ~1.0.2 в composer.json, это означает, что необходимо установить версию 1.0.2 или последнюю версию исправления, например 1.0.4. Если вы видите ^1.0.2, это означает, что необходимо установить версию 1.0.2 или последнюю дополнительную версию или версию исправления, например 1.1.0. Мы доверяем поставщику библиотеки увеличивать основной номер при внесении каких-либо критических изменений, но иногда это требование упускается или не соблюдается. В прошлом были такие случаи. Даже если вы поместите фиксированные версии в свой composer.json, ваши зависимости могут иметь ~ и ^ в их composer.json.

Если он доступен, на мой взгляд, лучше использовать репозиторий артефактов (Nexus, JFrog и т. д.). Релизная сборка, содержащая все необходимые зависимости, будет создана один раз. Этот артефакт будет храниться в репозитории и извлекаться оттуда для различных этапов тестирования. Кроме того, это будет сборка, которую нужно развернуть в рабочей среде, а не пересобирать приложение из Git.

Обеспечение совместимости кода и базы данных

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

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

Например, предположим, что у нас есть столбец с address , и нам нужно разделить его на address1 и address2 . Чтобы все было совместимо, нам может понадобиться несколько выпусков.

  1. Добавьте два новых столбца в базу данных.
  2. Измените приложение, чтобы использовать новые поля, когда это возможно.
  3. Перенесите address данные в новые столбцы и удалите их.

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

Немного удивительности Kubernetes

Несмотря на то, что масштаб нашего приложения может не нуждаться в облаках, узлах и Kubernetes, я все же хотел бы упомянуть, как выглядит развертывание в K8s. В этом случае мы не вносим изменения в систему, а заявляем, чего мы хотели бы добиться и что должно работать на скольких репликах. Затем Kubernetes убеждается, что фактическое состояние соответствует желаемому.

Всякий раз, когда у нас есть готовая новая версия, мы создаем образ с новыми файлами в нем, помечаем образ новой версией и передаем его в K8s. Последний быстро раскрутит наше изображение внутри кластера. Он будет ждать, пока приложение не будет готово на основе предоставленной нами проверки готовности, затем незаметно перенаправит трафик на новое приложение и убьет старое. Мы можем очень легко запустить несколько версий нашего приложения, что позволит нам выполнять синее/зеленое или канареечное развертывание с помощью всего нескольких команд.

Если вам интересно, в докладе Берра Саттера «9 шагов к потрясающему с Kubernetes» есть несколько впечатляющих демонстраций.

Связанный: Полная аутентификация пользователя и контроль доступа — Учебное пособие по паспорту Laravel, Pt. 1