Zero-downtime деплой на VPS без простоя
Пользователь не должен видеть 502 во время выката. Разберём проверенную схему деплоя без простоя: релизы через symlink, graceful reload приложения и проверка здоровья перед переключением трафика.
Содержание
Почему возникает даунтайм
['Наивный деплой — этоgit pull поверх работающего кода и рестарт сервиса. В момент рестарта старый процесс убит, новый ещё не принимает соединения — отсюда 502 и оборванные запросы.', 'Zero-downtime строится на двух принципах:', '- Атомарное переключение — новый релиз готовится в стороне, трафик переводится мгновенной сменой symlink.
- Graceful reload — новый процесс поднимается рядом со старым, а старый завершает текущие запросы и только потом уходит.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK и США. Оплата картой РФ и по СБП.
Арендовать VPS для деплоя без простояРелизы через symlink
['Каждый деплой кладём в отдельный каталог по времени, аcurrent — это symlink на активный релиз. Переключение атомарно.', '/var/www/app/\n├── releases/\n│ ├── 20260709-1200/\n│ └── 20260709-1330/\n├── current -> releases/20260709-1330\n└── shared/ (uploads, .env, логи)', 'Скрипт деплоя готовит новый релиз и переключает symlink одной командой ln -sfn — она заменяет ссылку атомарно, без промежуточного состояния:', '#!/bin/bash\nset -e\nREL=/var/www/app/releases/$(date +%Y%m%d-%H%M%S)\ngit clone --depth 1 git@github.com:USER/REPO.git $REL\ncd $REL\nln -s /var/www/app/shared/.env .env\nnpm ci && npm run build\nln -sfn $REL /var/www/app/current\nsystemctl reload myapp']
Graceful reload приложения
['Ключевое слово — reload, а не restart. gunicorn по сигналу HUP поднимает новых воркеров и мягко гасит старых, дав им завершить запросы.', '# gunicorn: graceful reload\nkill -HUP $(cat /run/gunicorn.pid)\n# или через systemd, если ExecReload настроен на HUP\nsystemctl reload myapp', 'В systemd-юните пропишите ExecReload и PID-файл:', '[Service]\nExecStart=/venv/bin/gunicorn app:app --pid /run/gunicorn.pid\nExecReload=/bin/kill -HUP $MAINPID\nKillMode=mixed']
nginx как буфер и healthcheck
['nginx перед приложением сглаживает переключение: если апстрим на миг недоступен, запрос ретраится. Добавьте retry-логику в upstream.', 'upstream app {\n server 127.0.0.1:8000 max_fails=3 fail_timeout=5s;\n server 127.0.0.1:8001 backup;\n}\nserver {\n location / {\n proxy_pass http://app;\n proxy_next_upstream error timeout http_502;\n }\n}', 'Перед переключением symlink проверьте, что новый релиз реально отвечает. Простой healthcheck в скрипте деплоя:', 'for i in $(seq 1 10); do\n curl -fs http://127.0.0.1:8000/health && break\n sleep 1\ndone', 'После nginx -t и systemctl reload nginx конфиг применяется без разрыва соединений. Всегда прогоняйте nginx -t перед reload: битый конфиг на reload не применится, и вы останетесь на старом рабочем — но узнать об ошибке лучше до, чем во время выката.', 'Резервный (backup) апстрим на втором порту — дешёвая подстраховка: пока основной процесс перезапускается, nginx на миг уводит трафик на запасной. Для критичных сервисов этот запасной воркер держат постоянно поднятым.']
Откат и очистка
['Симлинк-схема даёт мгновенный откат: переключитеcurrent на предыдущий релиз и сделайте reload.', 'ln -sfn /var/www/app/releases/20260709-1200 /var/www/app/current\nsystemctl reload myapp', 'Старые релизы удаляйте, оставляя последние 5, чтобы не забить диск:', 'cd /var/www/app/releases && ls -1t | tail -n +6 | xargs rm -rf']
Частые ошибки
['- restart вместо reload — процесс убивается жёстко, запросы рвутся.
- cp вместо ln -sfn — копирование не атомарно, ловите полурелиз.
- Нет healthcheck — переключились на битый релиз и уронили прод.
- Миграции БД — несовместимые схемы ломают старые воркеры. Делайте обратно-совместимые миграции в два шага.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK и США. Оплата картой РФ и по СБП.
Арендовать VPS для деплоя без простояЧастые вопросы
Zero-downtime возможен на одном сервере?
Да. symlink-релизы и graceful reload не требуют второго сервера — всё работает на одном VPS.
А если меняется схема БД?
Используйте expand/contract: сначала добавляете новые поля совместимо, деплоите код, затем отдельным шагом удаляете старое.
Reload точно не рвёт соединения?
gunicorn с graceful reload дожидается завершения текущих запросов у старых воркеров. Настройте graceful_timeout под ваши долгие запросы.