Деплоймент PHP

Ну например сайта на Drupal 7

Опубликовал Максим Баев, 24 апреля 2019, 09:17

За последние годы Continuous Integration и Continuous Delivery надёжно укоренились в процессах разработки приложений и стали маст-хэв стандартом. Сейчас новаторы вовсю пробуют оседлать доставку приложений завёрнутых в Docker, но об этом в следующий раз. А пока опишу процесс доставки кода до сервера с примерами, блэкджеком и шл... сниппетами.

Почему PHP?

Потому, что многие другие языки имеют уже написанные фреймворки для автоматизации этого процесса. Такой есть и для PHP, но о Deployer'е мы поговорим в следующий раз. Есть ещё возможность использовать Capistrano - гем, написанный на Ruby для Ruby on Rails. Собственно по его типу и подобию написан и Deployer. Другие, более низкоуровневые фреймворки рассматривать не будем.

А почему Drupal 7?

Для 8-ки есть более вкусные варианты, хотя этот способ тоже подойдет, если у вас весь сайт целиком хранится в репозитории. Без всяких этих ваших Composer'ов.

А почему вообще Drupal?

Просто для примера.

В этот раз попробуем простой вариант состоящий из двух частей:

  • Какой-нибудь Continuous Integraition (CI) - будет реагировать на новые коммиты и запускать процесс деплоймента
  • Bash скрипт, который будет запускаться на целевом сервере и выполнять сам деплоймент

Входящие данные

Чтобы не набрасываться на скрипт и пытаться прикрутить его к своему приложению, разберёмся, что уже должно быть.

Что деплоить:

  • Для деплоймента возьмём сайт на Drupal 7, но подойдет любое не компилируемое приложение.

Откуда деплоить:

  • Репозиторий хранится в GitLab.
  • Для запуска CI будем использовать GitLab CI.

Куда деплоить:

  • К серверу есть SSH доступ.
  • Есть возможность изменять папку, где хостится сайт. А точнее сделать её симлинком.

Структура на сервере

Обычно здесь предъявляются следующие желания:

  • Нужно версионирование, чтобы:
    1. Сайт начинал работать на новой версии, только после успешно завершённого деплоймента.
    2. При необходимости, можно было бы быстро откатиться на предыдущую версию.
    3. Деплоймент должен проходить по принципу zero downtime.
  • В файлах сайта могут лежать Секретные файлы.
  • Нужна директория с пользовательскими файлами, которая будет единственной для всех версий деплоймента.

Исходя из всего этого и предположив, что сайт находится в /var/www/mysite, нарисуем такую структуру:

/var/www/mysite
  ~current                                                    <-- Ссылка на последний успешный деплоймент/релиз
  /repo                                                       <-- Клон Git репозитория
  /releases                                                   <-- Директории релизов
    /2019.03.25_20:13:12.84a10b1
    /2019.04.03_23:56:48.5a0ba30
    /2019.04.17_01:10:36.73215a0
  /raw                                                        <-- Здесь хранятся секретные данные, которые есть только на сервере.
                                                                  Содержимое будет копироваться в каждую папку релиза с заменой.
    /sites                                                    <-- Структура Drupal'а
      /default
        ~files -> /var/www/mysite/shared/storage              <-- Ссылка на папку с пользовательскими файлами.
        ~settings.php -> /var/www/mysite/shared/settings.php  <-- Ссылка на единый конфигурационный файл.
  /shared                                                     <-- Общие файлы необходимые для всех релизов
    /storage                                                  <-- Пользовательские файлы
    settings.php

Визуально это выглядит так: deployment

Деплоймент скрипт

Для совершения деплоймента можно использовать следующий скрипт

#!/usr/bin/env bash

set -e

# Folloing variables must be defined in your secret
# variables, like Environment variables of the GitLab.
# MAX_RELEASES=3
# DEPLOY_DIR="/var/www/mysite"
# DEPLOY_BRANCH="master"
# GIT_SOURCE="https://gitlab.com/<yourname>/<yoursite>.git"


CI_COLOR="\033[0;32m"
NO_COLOR="\033[0m"

GIT_DIR="$DEPLOY_DIR/repo"
RELEASES_DIR="$DEPLOY_DIR/releases"
RAW_DIR="$DEPLOY_DIR/raw"

print_title() {
  echo "";
  print_row "$@";
}

print_row() {
  echo -e "${CI_COLOR}$@${NO_COLOR}";
}


# Start deployment process.

print_title "Ensure required directories are exist."
mkdir -p "$DEPLOY_DIR"
mkdir -p "$GIT_DIR"
mkdir -p "$RELEASES_DIR"
mkdir -p "$RAW_DIR"
echo "Done"

print_title "Ensure repositoruy exist."
cd "$GIT_DIR"
if [[ ! $(git status 2>/dev/null) ]]; then
    echo "Clone repository"
    cd "../" && rm -rf "$GIT_DIR" && git clone $GIT_SOURCE $GIT_DIR
    cd "$GIT_DIR"
    echo "Done"
fi;

print_title "Update repository"
git fetch --all
git reset --hard "origin/$DEPLOY_BRANCH"
echo "Done"


COMMIT=$(git log -n1 --abbrev-commit|grep ^commit|awk '{print $2}')
TIMESTAMP=$(date +%Y.%m.%d_%H:%M:%S)
RELEASE="$TIMESTAMP.$COMMIT"
RELEASE_DIR="$RELEASES_DIR/$RELEASE"


print_title "Create directory for the new release \"$RELEASE\""
mkdir -p "$RELEASE_DIR" && echo "Done"

print_title "Deploy the release \"$RELEASE\""
rsync -a --stats --inplace "$REPO_DIR/" "$RELEASE_DIR" --exclude=".git" && echo "Done"

print_title "Copy raw files"
# chmod 755 -R "$RELEASE_DIR" # This might be helpful here.
rsync -al --force --stats "$RAW_DIR/" "$RELEASE_DIR" && echo "Done"

print_title "Create symlink to the release \"$RELEASE\""
ln -sfn "$RELEASE_DIR" "$DEPLOY_DIR/current" && echo "Done"

cd $RELEASES_DIR
if [ $(ls -l | grep -c ^d) -gt $MAX_RELEASES ] ; then
    print_title "Delete deprecated versions"
    while [ $(ls -l | grep -c ^d) -gt $MAX_RELEASES ]
    do
        DEPRECATED_RELEASE=$(ls -r | tail -n 1)
        chmod 755 -R ./$DEPRECATED_RELEASE
        rm -rf ./$DEPRECATED_RELEASE
        echo "Version \"$DEPRECATED_RELEASE\" has been deleted"
    done
    echo "Done"
else
    print_title "Deprecated versions not found"
fi

print_title "The deployment completed successfully"

Для скрипта необходимо иметь объявленные переменные окружения:

  • MAX_RELEASES - количество допустимых релизов для хранения (пример 3).
  • DEPLOY_DIR - директория для развёртывания всего этого добра (пример /var/www/mysite)
  • DEPLOY_BRANCH - название ветки, которую нужно вытягивать из репозитория для деплоймента (пример master)
  • GIT_SOURCE - адрес репозитория, для клонирования. (сервер должен иметь доступ до репозитория)

Кладём этот скрипт в папку проекта scripts/deploy.sh. Создаём CI-конфигурацию в файле gitlab-ci.yml, в корне проекта:

image: php:7.1.1

stages:
  - deploy

Deploy Production:
  stage: deploy
  before_script:
    - apt-get update -yqq
    - apt-get install libmcrypt-dev openssh-client -y
    - eval $(ssh-agent -s)
    - echo "$DEPLOY_RSA" | tr -d '\r' | ssh-add - 2>/dev/null
    - mkdir -m 700 -p ~/.ssh
  script:
    - MAX_RELEASES=$MAX_RELEASES \
      DEPLOY_DIR=$DEPLOY_DIR \
      DEPLOY_BRANCH=$DEPLOY_BRANCH \
      GIT_SOURCE=$GIT_SOURCE \
      ssh -o StrictHostKeyChecking=no -p $DEPLOY_PORT $DEPLOY_USER@$DEPLOY_SERVER \
      'bash --login -s' < scripts/deploy.sh
  only:
    - master

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

Ещё один штрих - переменные окружения должны быть настроены в GitLab. Рекомендую использовать защищённые переменные, чтобы никто кроме админов не смог увидеть их.

Кроме переменных, необходимых для деплоймент-скрипта, также нужно настроить переменные ssh доступа до сервера:

  • DEPLOY_USER - имя пользователя из под которого будет деплоиться аппликация
  • DEPLOY_RSA - приватный ключ пользователя, из под которого будет производиться деплоймент
  • DEPLOY_PORT - ssh порт сервера
  • DEPLOY_SERVER - адрес сервера, куда будет деплоиться приложение

На этом всё. Результат получится примерно следующий: deployment-ci

Другие посты