За последние годы 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 доступ.
- Есть возможность изменять папку, где хостится сайт. А точнее сделать её симлинком.
Структура на сервере
Обычно здесь предъявляются следующие желания:
- Нужно версионирование, чтобы:
- Сайт начинал работать на новой версии, только после успешно завершённого деплоймента.
- При необходимости, можно было бы быстро откатиться на предыдущую версию.
- Деплоймент должен проходить по принципу 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
Визуально это выглядит так:
Деплоймент скрипт
Для совершения деплоймента можно использовать следующий скрипт
#!/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 "$GIT_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
- адрес сервера, куда будет деплоиться приложение
На этом всё. Результат получится примерно следующий: