Выкатка (или deployment) новых версий Web приложений имеет ряд трудностей, т.к. необходимо быстро и одновременно выполнять группы действий на разных серверах. Процесс обычно включает в себя обновление кода (php) и статики (js/css/картинки), изменение баз данных и настроек системы.
Когда-то давно, новые версии появлялись очень редко (раз в год или даже реже). Тогда происходили сложные и длительные процессы обновления, а пользователи получали сразу огромный пакет изменений. Такой трудоемкий процесс иногда уничтожал целые бизнесы.
Сейчас понятие новой версии минимизировано до малейших изменений. Динамика разработки современных приложений огромная, а выкатки обновлений могут происходит каждый день.
Поэтому к процессу выкатки добавился целый ряд требований:
Основные компоненты любой крупной Web системы – это фронтенды, бекенды, базы данных и сервера специального назначения (например, почтовые либо медиа-хранилища).
Фронтенды обычно выполняют две функции:
Таким образом, для обновления фронтенда необходимо загрузить новые файлы статики. После этого, выполнить минификацию css/js при необходимости.
На практике это обычно делают так:
Такой подход позволяет мгновенно выполнить перевод всех пользователей на новую версию.
Представим, что мы используем такую конфигурацию Web сервера:
server {
index index.html;
**root /production_a;**
}
Тогда после выкатки необходимо заменить директиву root на новый путь. Можно использовать простой php скрипт:
$config = file_get_contents('site.conf');
$current_version = strpos($config, ‘/production_a’) ? ‘a’ : ‘b’;
$new_version = $current_version == ‘a’ ? ‘b’ : ‘a’;
$config = str_replace(‘/production_’ . $current_version, ‘/production_’ . $new_version, $config);
file_put_contents(‘site.conf’, $config);
## Скрипт для последовательного переключения между папками
После обновления и подготовки кода достаточно будет вызывать этот скрипт. Он изменит текущую папку на соседнюю. Если в текущий момент рабочая папка “production_a”, выкатку делаем в “production_b” и переключаемся на нее. Если “production_b”, то выкатку делаем в “production_a”. Таким образом рабочая папка постоянно меняется, а соседняя всегда будет содержать предыдущую рабочую версию.
После изменения файла конфигурации обновляем настройки Nginx’a:
/etc/init.d/nginx reload
Выкатка бекендов во многом похожа на выкатку фронтендов.
На всех серверах необходимо обновить код во второстепенной папке (/production_b). После чего переключить конфигурацию Nginx’a на нужную папку:
server {
…
root **/production_a**;
index index.php;
location ~* .(php)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
## Выделенную папку необходимо заменить на “production_b”
PHP использует различные кэши операционного кода, чтобы экономить на повторной интерпретации файлов. Поэтому, перед тем как переключать пользователей в новую папку, полезно сделать “разогрев” кэша.
Для этого достаточно иметь отдельный хост в Nginx, например:
server {
host lambda.ruhighload.com;
root **/production_b**;
index index.php;
location ~* .(php)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
## Отдельный хост lambda.ruhighload.com для разогрева кэша новой версии
После этого открыть специальную страницу, которая просто подключит все файлы проекта (preload.php):
if ( $_GET['key'] != 12345 ) exit;
$it = new RecursiveDirectoryIterator(“/production_b”);
foreach(new RecursiveIteratorIterator($it) as $file)
{
if ( pathinfo($file, PATHINFO_EXTENSION) == ‘php’ ) include $file;
}
## Подключаем все php файлы проекта
Вызов этой страницы следует добавить в скрипт выкатки, чтобы не делать этого руками:
wget http://lambda.ruhighload.com/preload.php?key=12345
## Ключ для безопасности
Изменение данных и их структуры (или миграция) – наиболее сложная задача при выкатке новых версий. Во-первых изменение структуры данных может занимать достаточно много времени. Во-вторых, ошибки в выкатке могут привести к потерям данных.
Прежде, чем построить систему миграции данных, необходимо обеспечить выполнение следующих правил:
Такую структуру не нужно будет изменять, чтобы добавить новое свойство для продукта.
Технически миграции обычно организуют в виде набора файлов, содержащих SQL запросы, собранных в отдельной папке:
# ls highloadcomua/data/migrations/ 15.comments.add.post_id.sql 16.comments.add.content.sql 17.comments.add.user_id.sql 18.tags.add.titl.sql ...
Процесс выкатки миграций происходит с использованием отдельного сервера. На нем происходит обновление папки с миграциями (используя систему контроля версий). После этого исполняются SQL-запросы, которые появились в новых файлах миграций.
Пример скрипта, который определит новые файлы после обновления и выполнит миграции из них:
# Получаем список выполненных миграций
$executed = file(‘executed.migrations’);
# Получаем список всех миграций
$files = glob(‘data/migrations/*’);
foreach ( $files as $file )
{
if ( in_array($file, $executed) ) continue;
# Выполняем миграцию
exec(‘mysql database -u root -p12345 < ‘ . $file, $o, $r);
# Если нет ошибки, помечаем миграцию, как выполненную
if ( !$r ) $executed[] = $file;
}
file_put_contents(‘executed.migrations’, implode(“n”, $executed));
На серверах специального назначения (например, почтовые сервера) потребность вносить изменения бывает реже, чем на серверах приложения. Однако, иногда приходится делать изменения в конфигурациях. Для таких целей удобно хранить все конфигурации в репозитории. Тогда, для смены настроек, достаточно будет обновить файлы конфигураций на всех серверах.
Например для обновления конфигурации почтового сервера можно было бы использовать приблизительно такой скрипт:
cd /etc/exim4
git pull
update-exim4.conf
/etc/init.d/exim4 restart
Для более сложных задач управления конфигурациями лучше использовать Chef.
В случае, если в системе присутствуют несколько десятков или более серверов, последовательное обновление кода на всех серверах может занять много времени:
for ip in `cat servers.list`; do
echo “Updating $ip…”
ssh $ip ‘git -C /production_b pull’
done
## Последовательное обновление кода
С помощью фоновых процессов можно выполнить все эти команды параллельно:
for ip in `cat servers.list`; do
echo “Updating $ip…”
ssh $ip ‘git -C /production_b pull’ **>> /var/log/deploy.log &**
done
## Параллельное обновление кода на всех серверах в фоне
Выкатка может содержать в себе ошибки либо просто быть неудачной с точки зрения бизнеса. В любом случае, всегда необходимо иметь возможность быстро восстановить предыдущую версию системы.
Со всеми узлами в описанном процессе это сделать очень просто. Достаточно изменить рабочую директорию Web сервера с текущей (например, production_b) на предыдущую (production_a).
Для баз данных очень важно соблюдать правило – отсутствие удаления данных любого вида в миграциях. Тогда возврат к предыдущей версии не потребует обратного изменения структуры. Удаление устаревших колонок, таблиц и данных следует планировать с задержкой (например, не ранее, чем через неделю). Это даст запас времени для возможного возврата.
Крупные изменения (например, новые функции либо существенные изменения в текущих) могут иметь негативные последствия после выкатки. Это может быть связано с неудачным бизнес или техническим решением. Кроме этого в реальной среде всегда существуют определенные факторы, которые невозможно повторить в среде разработки и тестирования (например, большое количество онлайн-пользователей). Это значит, что выкатка больших изменений всегда содержит в себе риск.
Поэтому, крупные и важные изменения удобно выкатывать таким образом, чтобы они были доступны только для части аудитории. Например, только для одного процента всех пользователей. В этом случае, негативные последствия будут ограничены только небольшой частью аудитории.
На практике это можно реализовать с помощью установки избранным пользователям кук. Например, установим каждому сотому пользователю куку “tester”:
if ( session::get('id') % 100 == 1 ) **setcookie('tester', 1);**
…
Тогда в Nginx’e можно будет использовать это значение, чтобы отправлять пользователей с этой кукой в другую (новую) папку:
server {
server_name ruhighload.com;
set $rt ‘/production_a’;
**if ($http_cookie ~* ” tester=1″) {
set $rt ‘/production_b’;
}**
root **$rt**;
location ~* .(php)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME **$rt**$fastcgi_script_name;
}
}
## Если установлена кука tester, изменяем переменную $rt на другой путь
Последним шагом в процессе выкатки следует делать проверку доступности приложения. Это нужно, чтобы убедиться, что нет критических ошибок. В самом простом случае это может быть тестовая страница, которая содержит проверки самых важных компонент:
# проверяем подключение к базе данных
$time = mysql::col(‘SELET NOW()’);
if ( !$time ) echo ‘error getting time from mysql’;
# проверяем код 200 от основных страниц
$pages = [‘/’, ‘/speed’, ‘/server’];
foreach ( $pages as $page )
{
$c = curl_init(‘http://ruhighload.com’ . $page);
$result = curl_exec($c);
$status = curl_getinfo($c, CURLINFO_HTTP_CODE);
curl_close($c);
if ( $status != 200 ) echo ‘error on page ‘ . $page;
}
# еще можно проверить php_error.log на наличие ошибок
# еще можно проверить php-fpm.slow-log на появление медленных скриптов
# и т.п.
## hearbeat.php – простой скрипт для быстрой проверки важных компонент сайта
При наличии unit-тестов, имеет смысл использовать их часть для валидации выкатки. В случае обнаружения ошибок лучше всего автоматически откатиться на предыдущую версию, после чего чинить неисправности.
В качестве самого важного – описание общего процесса выкатки:
Подсистема выкатки – это такая же динамическая компонента приложения, как и любая другая. Ее постоянно необходимо дорабатывать и усовершенствовать. Хорошее правило – иметь автономную систему выкатки, которая не требует ручных операций.
Не торопитесь использовать навороченные системы управления выкатками с кучей интеграций всего во все. Собственные решения часто являются намного более простыми, поэтому легче в управлении и более гибкие в использовании.
Не смотря на максимальную автоматизацию, любая выкатка должна всегда происходить под контролем администраторов или разработчиков. Никогда не делайте слепые выкатки, всегда дожидайтесь “Deployment finished successfully” от своей системы.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…