Выкатка большой системы

admin

Выкатка (или deployment) новых версий Web приложений имеет ряд трудностей, т.к. необходимо быстро и одновременно выполнять группы действий на разных серверах. Процесс обычно включает в себя обновление кода (php) и статики (js/css/картинки), изменение баз данных и настроек системы.

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

Поэтому к процессу выкатки добавился целый ряд требований:

  • Минимум (лучше ноль) ручного вмешательства.
  • Максимальная скорость выполнения (секунды или минуты, но не часы или дни).
  • Возможность быстрого возврата на последнюю рабочую версию.
  • Масштабируемость, а значит независимость скорости выполнения от количества серверов.

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

Выкатка фронтендов

Фронтенды обычно выполняют две функции:

  • Раздают клиентам статические файлы (css, js, картинки).
  • Балансируют запросы к приложению и проксируют их на бекенды.

Таким образом, для обновления фронтенда необходимо загрузить новые файлы статики. После этого, выполнить минификацию css/js при необходимости.

На практике это обычно делают так:

  1. Создают дубликат папки проекта на сервере (например /production_b, если основная папка /production_a).
  2. Обновляют эту папку (/production_b) до последней версии. Удобно использовать системы контроля версий, чтобы упростить обновление. Например, Git.
  3. Выполняют необходимые манипуляции (минификация, склеивание и т.п.).
  4. Меняют папку назначения в конфигурации Nginx’a и перезапускают сервер.

Такой подход позволяет мгновенно выполнить перевод всех пользователей на новую версию.

Конфигурация Nginx’a

Представим, что мы используем такую конфигурацию 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

## Ключ для безопасности

Миграции баз данных

Изменение данных и их структуры (или миграция) – наиболее сложная задача при выкатке новых версий. Во-первых изменение структуры данных может занимать достаточно много времени. Во-вторых, ошибки в выкатке могут привести к потерям данных.

Прежде, чем построить систему миграции данных, необходимо обеспечить выполнение следующих правил:

    1. Избегайте изменений в структуре больших таблиц. По возможности, используйте дополнительные таблицы для хранения данных. Например, вместо того, чтобы иметь таблицу users с колонками [name, gender, email, password], можно иметь две таблицы:
      • user_auth [email, password]
      • user_info [name, gender]
  1. Никогда не используйте удаление колонок, таблиц или данных в миграциях. Эти операции должны выполняться отдельными процедурами под внимательным наблюдением администраторов. Миграции должны содержать только добавление колонок/таблиц/данных.
  2. Не добавляйте создание индексов в миграции кроме логически требуемых (например, уникальные ключи). Индексы должны создаваться исключительно под профиль нагрузки на рабочей базе данных. И также под пристальным наблюдением администраторов.
  3. Используйте вертикальные таблицы для случаев, когда колонки таблицы могут часто изменяться. Например, структура таблицы для разных свойств продукта:
    • product_id
    • property_name
    • property_value

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

Технически миграции обычно организуют в виде набора файлов, содержащих 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-тестов, имеет смысл использовать их часть для валидации выкатки. В случае обнаружения ошибок лучше всего автоматически откатиться на предыдущую версию, после чего чинить неисправности.

Самое важное

В качестве самого важного – описание общего процесса выкатки:

  1. Обновление файлов в специально отведенной папке (/production_b) с помощью системы контроля версий (Git, SVN и т.п.).
  2. Предварительная подготовка (минификация, разогрев кэшей, миграция данных).
  3. Переключение аудитории или ее части на новую версию (смена пути с /production_a на /production_b на Web сервере).
  4. Проверка основных компонент системы (например, код HTTP ответа от основных страниц) и возврат к предыдущей версии, если обнаружены ошибки.

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

Не торопитесь использовать навороченные системы управления выкатками с кучей интеграций всего во все. Собственные решения часто являются намного более простыми, поэтому легче в управлении и более гибкие в использовании.

Не смотря на максимальную автоматизацию, любая выкатка должна всегда происходить под контролем администраторов или разработчиков. Никогда не делайте слепые выкатки, всегда дожидайтесь “Deployment finished successfully” от своей системы.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023