Теория автоматического обновления (deploy)

Каждый разработчик сайтов и веб-приложений регулярно сталкивается с задачей обновлять свои проекты. Хорошо, если вы работаете в правильной среде и имеете все инструменты для деплоя (deploy). Но что, если нет? Давайте рассмотрим несколько способов и выберем наиболее подходящий.

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

Кратко вспомним всю картину процесса разработки. Проект (в общем случае) может иметь четыре различных среды:

dev – среда разработки, в которой всегда всё нестабильно, вводятся новые фичи и фиксятся баги;
test – среда тестирования на стороне разработчика, в которую проект помещается по достижению релиз-кандидата (см. ниже);
testprod – среда тестирования на стороне заказчика, в которую проект попадает для финальной проверки;
prod – среда реальной работы на боевом сервере (production).

В разработке проекта в какие-то моменты времени, например с периодичностью в месяц или по количеству решенных задач, назначаются точки выпуска релиз-кандидатов. Обновление проекта попадает на тестирование, затем на тестирование к заказчику, и потом на боевой сервер. Здесь я не стану раскрывать эти вопросы более подробно, так как цель поста – рассказать как физически происходит этот перенос.

Обозначим проблемы:

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

Чем больше сложностей в вашем проекте – тем он хуже по определению. Хотя есть идиоты, которые специально создают себе сложности и потом геройски их преодолевают, я надеюсь, читатель, что ты не из их числа. Упростим нашу жизнь:

Методы:

Один из методов автоматического обновления предоставляет Ruby on Rails – некоторые авторы CMS напрямую копируют их механизмы и даже выдают за свои. Суть метода в том, что каждое изменение в БД проекта фиксируется в специальном лог-файле (так называемые миграции) и потом последовательно применяется на целевом сервере. Файлы тупо копируются, но благодаря безупречной файловой организации Rails – это очень удобно. С одной стороны всё хорошо: можно последовательно накатить изменения БД, протестировать, и так же автоматически последовательно откатить обратно. С другой стороны плохо – откатить файлы уже сложнее.

Второй популярный метод – это всегда “отдавать” удалённому серверу текущее (самое новое) стабильное состояние проекта. Таким образом, при обновлении любой клиентский сервер получит проект в одном и том же самом новом состоянии. Как именно – рассмотрим ниже. Чем это хорошо? Тем что гарантированно решатся все проблемы, решенные в проекте на данный момент. Чем плохо? Тем, что неясно как откатывать назад. Разве что делать автоматический бэкап перед обновлением..

Третий метод – хранить у себя и “предлагать” удалённому серверу все промежуточные версии проекта. Подчёркиваю: не различия между ними, а сами версии – содержимое файлов и структуры БД. Примерно так обновляется Linux.

Четвертый метод – уже более экзотический – при попытке обновления спрашивать у удалённого сервера его ревизию продукта, вычислять разницу (diff) с текущей версией и “отдавать” ему только эти диффы, чтобы он их у себя применял. Слишком заумно, сложно и ненадёжно. Если вы хотите пойти по этому пути – тупо делайте на удалённом сервере svn update.

Лично мне больше нравится третий метод (и возможно второй). Давайте рассмотрим, как он реализуется:

Реализация:

Правило номер один: никогда не “зашивайте” код обновлятора в поставку клиенту. Всё что должен делать “обновлятор”, лежащий у клиента, это скачивать настоящий обновлятор с сервера разработчика и передавать ему управление. Иначе вы рискуете зашить клиентам такие функции, которые будет очень трудно поменять в будущем. Обновлятор должен уметь читать конфиг системы, подключаться к БД, работать с файлами – в общем, быть полностью автономным и способным в случае проблем восстановить систему без подключения других модулей.

Правило номер два: ведите реестр файлов, которые нужно отдавать. Не надейтесь, что можно будет отдавать “всю директорию”. Также задумайтесь о простом и надёжном формате для исключения ненужных файлов и поддиректорий (например таких как “db_config.php” или “.svn”).

Правило номер три: не надейтесь на прямые SQL-запросы. У вас обязательно должен быть ORM в том или ином виде. Храните структуру БД в файлах описания, например экспортируйте её в XML и анализируйте её на принимающей стороне.

Рассмотрим процесс обновления по ролям:

Процесс:

Клиент: запрашиваю обновление (до последней версии по-умолчанию, либо до указанной).

Сервер: вот тебе обновлятор до версии X.

Клиент: скачал, запускаю. Давай мне список файлов.

Сервер: лови.

<file>
<id>1c01e52fe8aaa6d029ee52faa7a9a7b1</id>
<name>/classes/forms/booleanformfield.php</name>
<md5hash>37950d6caceeeab66db3e080b0b590c1</md5hash>
</file>
<file>
<id>3789c789980a96d1dc38b45f9e3afb82</id>
<name>/classes/property/property.php</name>
<md5hash>72c1f5dddff4fe38d7a76451a7cd994a</md5hash>
</file>
<file>
<id>c5e552f71d3d671c48f3d0f0ba508157</id>
<name>/classes/sqlobject/sqlobject.php</name>
<md5hash>5080f4776b70aaf95385fca72d7335a3</md5hash>
</file>

Клиент: ух ты. Пройдусь по-порядку… Так, первый файл у меня есть точно с тем же хэшем, второй тоже есть но хэш не совпадает, третьего нет. Ага, давай-ка мне файлы по id 3789c789980a96d1dc38b45f9e3afb82 и c5e552f71d3d671c48f3d0f0ba508157.

Сервер: минуточку… (сканирует ту же XML-ку и находит файлы). Лови.

Клиент: поймал. Проверяю.. Стоп, у второго хэш не совпал, походу побился при передаче.

Сервер: лови ещё раз.

Клиент: ага, поймал. Теперь всё совпадает. Теперь давай базу.

Сервер: лови.

<model>
<name>products</name>
<property>
<name>title</name>
<type>text</type>
</property>
<property>
<name>weight</name>
<type>float</type>
</property>
</model>

Клиент: ага, поймал. Теперь давай тесты.

Сервер: лови.

Клиент: всё, спасибо.

На этом шаге клиент ставит заглушку на сайт, заканчивает выполнять все очереди задач (если таковые были), и применяет обновления: копирует файлы и приводит структуру БД в соответствие с указанной. Затем выполняет тесты, и если они не проходят – возвращает систему к исходному состоянию (в зависимости от реализации – поднимает из бэкапа или запрашивает с сервера исходную версию). После этого очищаются все кэши, заглушка снимается, и сайт продолжает работать.

Что ещё:

Вопросы для самостоятельного изучения:

– научитесь скачивать большие файлы по частям, чтобы избежать ограничений по памяти;
– решите, что делать со служебным контентом, который должен находиться в БД по определённым адресам;
– выберите способ, как поддерживать работу обновлятора и не иметь ограничений по таймауту сервера.