Как стать автором
Обновить

Комментарии 14

Спасибо за статью!
Сейчас в процессе переезда на CI deploy с помощью Rancher.
В похожих статьях всегда обходят стороной проблему «zero downtime deployment» для баз данных.
Если с приложением нет особых проблем держать рабочими сразу одновременно несколько версий, то с БД все сложнее.
Приложение при выкладке может накатывать миграции. Миграции могут поменять структуру базы, а во время переключения пользователи уже сгенерируют данные в БД под старую структуру.
Есть ли хорошее решение этой проблемы?
Поддерживаю вопрос. Особенно интересно если миграция идёт например час, на большой базе.
А без контейнеров как вы это делаете?
По-моему очевидного ответа тут не будет.

Да, вопрос с миграциями в базе очень важный. Решение есть, но серебрянной пули — нет. Нужно следовать одному простому правилу — миграции должны быть всегда обратносовместимы.


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


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


Какие это правило накладывает ограничения? С первого взгляда можно только добавлять колонки и только с необязательным значением. Если менять типы колонок, то только совместимо. Ну и добавлять новый таблицы безбоязненно. По началу выглядит пугающе, мол как так — мы не можем переименовать или удалить колонку. Но фактически, это надо делать редко. И это всегда можно сделать в два релиза (сначала выкатываем код, который больше не использует не нужную колонку/таблицу, а в следующем релизе — удаляем колонку/таблицу).


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

И еще момент. Если вдруг у вас миграции блокируют базу, то тут надо менять что-то в базе или в миграциях. В миграциях можно попробовать такие инструменты как pt-online-schema-change, но лучше подумать о том, как бы уменьшить базу.


Ну и есть еще схема с Blue Green Deployment, с остановкой слейва и накатом миграции только на нем, с SQL based репликацией. Но вживую я не видел.

Как обновлять с zero-downtime пару контейнеров nginx, которые содержат статику из приложения? Понятно, что их надо спрятать за haproxy и обновлять поочередно? Но как сделать так, чтобы коннекты при этом не оборвались?

Ещё не решал такую задачу, но мне видится это, как сочетание
http://serverfault.com/questions/249316/how-can-i-remove-balanced-node-from-haproxy-via-command-line и
https://nginx.org/ru/docs/http/ngx_http_stub_status_module.html


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


Можно даже не заморачиваться с sub_status, у haproxy можно забирать по csv текущую статистику, включая и количество активных запросов к бэкэндам.

Можно просто выключить nginx «gracefully», он подождет пока все открытые коннекты отработают и выключится

Тоже вариант. Но всё равно стоит это соединить с предварительной просьбой frontend'у не слать новые запросы на этот backend. Без этого несколько запросов могут потеряться, пока frontend сам не поймёт, что этот backend больше ему недоступен.

Если вкратце — то сочетание graceful shutdown, встроенного в nginx, и правильно подобранного таймаута. Если это обычная статика (js, css, картинки) и обычные клиенты, то таймаут в 10 секунд норм. Если превалируют медленные клиенты (мобилки, хот споты) или большие объемы (видео блобы и пр.) — таймаут можно увеличивать. Подбирать таймаут по перцентиль 99 response time в Nginx (он как раз и включает время до последнего отданного байта).


Ну и важный момент, что Docker по умолчанию шлет SIGTERM при остановке контейнера, а Nginx'у нужно послать SIGQUIT. Как этого добиться — очень сильно зависит (используете ли какой-то супервизор, или сразу запускаете Nginx, используете голый Docker, или с какой-то оркестрацией).

Можно реализовать примерно так.
На haproxy настраиваете проверку «здоровья» серверов
backend app
  ...
  option httpchk GET /health_check/
  http-check expect string success
  ...


А в приложении — ответ на этот урл. Для переключения можно использовать файл-флаг.

Выше писал, в коментах. Миграции по-определению должны быть обратно совместимы, хотя бы в рамках одной версии. Это фундоментальное ограничение, пока не слышал никаких альтернатив.

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