Операционный справочник
Этот Runbook охватывает повседневные операции, процедуры деплоя, реагирование на инциденты и восстановление для платформы TryOn SaaS.
Быстрый справочник
Сервер: 185.246.222.107
Домен: ziex-tryon.com
SSH: ssh -i ~/.ssh/id_ed25519 [email protected]
Корень приложения: /opt/tryon-saas
Резервные копии: /opt/tryon-saas-backups/
Быстрая проверка состояния¶
# Состояние платформы (выполняется откуда угодно)
curl https://api.ziex-tryon.com/health | python3 -m json.tool
# Строгая проверка готовности (БД + Redis)
curl -I https://api.ziex-tryon.com/readiness
# SSH на сервер и проверка всех Docker-сервисов
ssh -i ~/.ssh/id_ed25519 [email protected]
sudo docker ps
Ожидаемый ответ health:
{
"status": "ok",
"checks": {
"database": "ok",
"redis": "ok",
"worker_heartbeat": "ok",
"fal_api": "configured",
"storage_backend": "local",
"media_base_url_public": true
}
}
HTTP 200 недостаточно
Эндпоинт /health возвращает 200 даже при деградации отдельных проверок. Всегда инспектируйте объект checks, не только HTTP-статус.
Рутинные операции¶
Просмотр логов¶
# Следить за логами admin-api
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs -f admin-api
# Следить за логами ml-worker
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs -f ml-worker
# Все сервисы, последние 100 строк
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=100
# Поиск ошибок
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs admin-api | grep '"level":"error"'
Проверка состояния сервисов¶
# Все контейнеры
sudo docker ps
# Использование ресурсов
sudo docker stats --no-stream
# Брандмауэр UFW
sudo ufw status verbose
# fail2ban (защита от брутфорса SSH)
sudo fail2ban-client status sshd
# Использование диска
df -h /
du -sh /opt/tryon-saas-backups/
Ручное резервное копирование¶
Применение миграций базы данных¶
cd /opt/tryon-saas
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec admin-api alembic upgrade head
Миграции не запускаются автоматически
После деплоя кода, содержащего новую миграцию Alembic, необходимо вручную выполнить alembic upgrade head. create_all только создаёт таблицы для чистых установок — новые столбцы из миграций оно не добавляет.
Перезапуск одного сервиса¶
cd /opt/tryon-saas
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart admin-api
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart ml-worker
Применение изменений кода без полного деплоя¶
cd /opt/tryon-saas
git pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml build admin-api
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d admin-api
Деплой¶
Полный production-деплой (CI/CD)¶
Деплой запускается автоматически через GitHub Actions при успешном прохождении CI на ветке main. Процесс:
1. Запуск lint + тестов
2. SSH на сервер
3. git pull
4. Сборка изменённых сервисов (определяется по diff файлов)
5. Запуск up -d с упорядоченным стартом
6. Вызов scripts/health-check.sh для верификации
Ручной запуск
Вы можете повторно запустить workflow деплоя из интерфейса GitHub Actions без нового пуша.
Ручной production-деплой¶
ssh -i ~/.ssh/id_ed25519 [email protected]
cd /opt/tryon-saas
git pull
bash scripts/deploy.sh --prod
deploy.sh --prod выполняет предварительные проверки (наличие DOMAIN, TLS-сертификатов), собирает образы, запускает сервисы в порядке зависимостей и вызывает scripts/health-check.sh.
DOMAIN должен быть задан в .env
docker-compose.prod.yml использует ${DOMAIN} для MEDIA_BASE_URL. Если DOMAIN не задан, URL становится "https:///uploads", что нарушает доставку изображений. deploy.sh --prod завершается с ошибкой, если DOMAIN пуст.
Первый деплой (чистый сервер)¶
ssh -i ~/.ssh/id_ed25519 [email protected]
# Клонирование репозитория
git clone <repo-url> /opt/tryon-saas
cd /opt/tryon-saas
# Размещение TLS-сертификатов (Cloudflare Origin Certificate)
# ssl/fullchain.pem ← origin.crt
# ssl/privkey.pem ← origin.key
chmod 644 ssl/fullchain.pem
chmod 600 ssl/privkey.pem
# Генерация секретов и создание .env
bash scripts/generate-secrets.sh
cp .env.example .env
# Отредактировать .env: заполнить FAL_API_KEY, FIRST_ADMIN_EMAIL, FIRST_ADMIN_PASSWORD, DOMAIN
# Деплой
bash scripts/deploy.sh --prod
Реагирование на инциденты¶
Воркер недоступен¶
Симптомы: Задачи зависают в статусе pending. /health показывает "worker_heartbeat": "stale" или "missing". Срабатывает Telegram-алерт.
# Проверить логи воркера
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=50 ml-worker
# Проверить, работает ли контейнер
sudo docker ps | grep ml-worker
# Перезапустить воркер
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart ml-worker
# Убедиться, что heartbeat восстановился (проверить в течение 90 секунд)
curl https://api.ziex-tryon.com/health | python3 -m json.tool
Автовосстановление зависших задач
admin-api запускает фоновый sweep каждые 60 секунд. Задачи, застрявшие в статусе processing более 15 минут, автоматически переводятся в failed. Задачи в pending более 30 минут — тоже. Клиентам следует повторить упавшие задачи.
Высокий процент ошибок задач¶
Симптомы: Срабатывает Telegram-алерт. Grafana показывает рост tryon_completed_total{status="failed"}.
# Проверить последние ошибки в логах воркера
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs ml-worker | grep '"level":"error"' | tail -20
# Возможные причины:
# 1. API-ключ fal.ai невалиден или превышен rate limit
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs ml-worker | grep "fal_api_key"
# 2. Сбой сервиса fal.ai
curl https://api.ziex-tryon.com/health | python3 -m json.tool | grep fal_api
# 3. URL изображения недоступен для fal.ai (проблема MEDIA_BASE_URL)
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs admin-api | grep "media_base_url_localhost"
Устранение:
- Проблема с ключом fal.ai → обновить FAL_API_KEY в .env, пересобрать и перезапустить ml-worker
- Сбой fal.ai → подождать; задачи будут повторены при перезапуске воркера
- Проблема MEDIA_BASE_URL → убедиться, что MEDIA_BASE_URL в .env задан как публично доступный HTTPS URL
API недоступен (admin-api не отвечает)¶
Симптомы: curl https://api.ziex-tryon.com/health не работает или возвращает 502.
# Проверить контейнер admin-api
sudo docker ps | grep admin-api
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=30 admin-api
# Проверить nginx
sudo docker ps | grep nginx
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=20 nginx
# Перезапустить admin-api
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart admin-api
# Если 502 сохраняется, проверить доступность admin-api из nginx
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec nginx wget -qO- http://admin-api:8000/health
Частые причины сбоя запуска admin-api
- Отсутствует обязательная переменная окружения (например,
JWT_SECRET_KEYслишком короткий) → проверить логи на событиеstartup_failed - БД недоступна при запуске → сначала проверить контейнер postgres
- Конфликт портов → проверить, не занят ли порт 8000 другим процессом
Проблемы с базой данных¶
Симптомы: /readiness возвращает 503. /health показывает "database": "connection_failed".
# Проверить контейнер postgres
sudo docker ps | grep postgres
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=30 postgres
# Перезапустить postgres (данные хранятся на именованном Docker-томе, перезапуск безопасен)
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart postgres
# Подождать ~10 секунд, затем проверить
curl https://api.ziex-tryon.com/readiness
# Подключиться напрямую для диагностики
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec postgres psql -U postgres tryon_saas -c "\l"
Никогда не удаляйте volume postgres
База данных хранится в именованном Docker-томе. Команда docker-compose down -v уничтожит все данные. Всегда используйте docker-compose down без флага -v.
Проблемы с Redis¶
Симптомы: /readiness возвращает 503. /health показывает "redis": "connection_failed". Перестаёт работать rate limiting и постановка задач в очередь.
# Проверить контейнер redis
sudo docker ps | grep redis
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=20 redis
# Перезапустить redis (AOF-персистентность воспроизведётся при перезапуске — займёт несколько секунд)
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart redis
# Проверить
curl https://api.ziex-tryon.com/readiness
AOF-персистентность Redis
Redis настроен с AOF (append-only file). После перезапуска Redis воспроизводит AOF для восстановления состояния очереди. Это занимает несколько секунд и безопасно. Задачи, находившиеся в очереди до перезапуска, будут доступны снова.
Проблемы с TLS/SSL¶
Симптомы: HTTPS возвращает ошибку сертификата. Cloudflare показывает ошибку SSL handshake.
# Проверить логи nginx на TLS-ошибки
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs nginx | grep -i "ssl\|tls\|cert"
# Убедиться, что файлы сертификатов существуют и доступны
ls -la /opt/tryon-saas/ssl/
# Ожидается: fullchain.pem (644) и privkey.pem (600)
# Проверить срок действия сертификата
openssl x509 -in /opt/tryon-saas/ssl/fullchain.pem -noout -dates
Cloudflare Origin Certificates
Платформа использует Cloudflare Origin Certificates (не Let's Encrypt). Это 15-летние сертификаты — срок истечения не является рутинной проблемой. Однако ssl_stapling должен быть выключен в nginx.prod.conf для этих сертификатов:
Переполнение диска¶
Симптомы: admin-api не может сохранять загруженные изображения. Логи показывают OSError: No space left on device.
# Проверить использование диска
df -h /
du -sh /opt/tryon-saas-backups/
du -sh /opt/tryon-saas/uploads/
du -sh /var/lib/docker/
# Удалить старые резервные копии (хранение 30 дней — автоматизировано, но можно вручную)
ls -lht /opt/tryon-saas-backups/ | tail -n +31 | awk '{print $NF}' | xargs -I{} rm /opt/tryon-saas-backups/{}
# Запустить очистку медиафайлов (удаляет WebP-файлы старше UPLOAD_RETENTION_HOURS без активных задач)
# Сервис очистки запускается автоматически по интервалу — перезапуск admin-api ускорит выполнение
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart admin-api
# Очистить неиспользуемые Docker-слои
sudo docker system prune -f
Высокое потребление памяти¶
Симптомы: OOM kill в логах контейнера. sudo docker stats показывает сервис у предела памяти.
# Проверить использование памяти по контейнерам
sudo docker stats --no-stream
# Проверить OOM kills в журнале ядра
sudo dmesg | grep -i "oom\|kill" | tail -10
# Перезапустить затронутый сервис
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart admin-api
Ограничения ресурсов
Все сервисы имеют ограничения памяти в docker-compose.yml. Если сервис стабильно достигает своего лимита, проанализируйте паттерн использования памяти перед увеличением ограничения.
Процедуры восстановления¶
Восстановление базы данных из резервной копии¶
# Список доступных резервных копий
ls -lh /opt/tryon-saas-backups/
# Восстановление из конкретного дампа (БД будет УДАЛЕНА и пересоздана)
bash /opt/tryon-saas/scripts/restore.sh /opt/tryon-saas-backups/<dump-file>.sql.gz --drop-existing --yes
# Проверить восстановление
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec postgres psql -U postgres tryon_saas -c "SELECT count(*) FROM clients;"
Восстановление удаляет существующие данные
--drop-existing удаляет текущую базу данных перед восстановлением. Всегда проверяйте правильность выбранного файла резервной копии перед использованием этого флага.
Восстановление доступа администратора (сброс пароля)¶
Если пароль администратора утерян и нет других способов восстановления:
# Сгенерировать новый bcrypt-хэш (использовать Python на сервере)
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec admin-api python3 -c "
import bcrypt
pw = b'YourNewPassword123!'
print(bcrypt.hashpw(pw, bcrypt.gensalt(rounds=12)).decode())
"
# Обновить пароль в базе данных
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec postgres psql -U postgres tryon_saas -c \
"UPDATE users SET password_hash='\$2b\$12\$...' WHERE email='[email protected]';"
Эндпоинт forgot-password не реализован
Эндпоинт /auth/forgot-password отсутствует. Восстановление пароля администратора требует прямого доступа к БД, как показано выше. Самообслуживание через email запланировано для Фазы 2 (требует email-провайдера).
Исправление сломанной конфигурации nginx¶
# Проверить конфигурацию nginx перед применением
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec nginx nginx -t
# Если конфигурация сломана и nginx не запускается, восстановить из git
cd /opt/tryon-saas
git diff nginx/nginx.prod.conf # посмотреть что изменилось
git checkout nginx/nginx.prod.conf # откатить изменения
docker compose -f docker-compose.yml -f docker-compose.prod.yml restart nginx
Исправление сломанной конфигурации sshd¶
Если вы заблокированы из SSH из-за неверного sshd_config:
- Войдите в веб-консоль VPS-провайдера (xorek.cloud) через аккаунт root
- Через консоль, от имени root, восстановите резервную конфигурацию:
- При необходимости добавьте ключ пользователя
deploy:
Восстановление после потери SSH-ключа¶
- Войдите в веб-консоль xorek.cloud
- Подключитесь к серверу от имени root через веб-консоль
- Добавьте новый публичный ключ в
/home/deploy/.ssh/authorized_keys - Проверьте права:
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys
Справочник¶
Переменные окружения (критические)¶
| Переменная | Назначение | Где изменить |
|---|---|---|
| JWT_SECRET_KEY | Подпись всех JWT | /opt/tryon-saas/.env |
| FAL_API_KEY | Инференс fal.ai | /opt/tryon-saas/.env |
| POSTGRES_PASSWORD | Аутентификация БД | /opt/tryon-saas/.env |
| DOMAIN | Префикс MEDIA_BASE_URL | /opt/tryon-saas/.env |
| MEDIA_BASE_URL | Публичный URL загрузок, отправляемый в fal.ai | /opt/tryon-saas/.env |
| UPLOAD_RETENTION_HOURS | Срок хранения изображений | /opt/tryon-saas/.env |
| ENABLE_DOCS | Отключить /docs в production | /opt/tryon-saas/.env |
После изменения .env
Перезапустите затронутый сервис:
Брандмауэр (UFW)¶
Открытые порты: 22 (SSH), 80 (HTTP→HTTPS редирект), 443 (HTTPS). Все остальные порты запрещены.
Команды Docker Compose¶
# В production всегда использовать оба compose-файла
COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
$COMPOSE ps # статус сервисов
$COMPOSE logs -f admin-api # следить за логами
$COMPOSE restart admin-api # перезапустить один сервис
$COMPOSE up -d # запустить/обновить все
$COMPOSE exec admin-api bash # консоль внутри контейнера
$COMPOSE exec postgres psql -U postgres tryon_saas # консоль БД
Полезные однострочники¶
# Количество задач по статусам
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec postgres \
psql -U postgres tryon_saas -c "SELECT status, count(*) FROM jobs GROUP BY status;"
# Количество активных клиентов
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec postgres \
psql -U postgres tryon_saas -c "SELECT count(*) FROM clients WHERE status='active';"
# Глубина очереди Redis
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec redis \
redis-cli LLEN tryon_jobs
# Возраст heartbeat воркера (секунды с последнего обновления)
docker compose -f docker-compose.yml -f docker-compose.prod.yml exec redis \
redis-cli TTL worker_heartbeat
# Следить за структурированными логами с форматированием JSON
docker compose -f docker-compose.yml -f docker-compose.prod.yml logs -f admin-api | python3 -c "
import sys, json
for line in sys.stdin:
try:
print(json.dumps(json.loads(line.strip()), indent=2))
except:
print(line, end='')
"
Crontab для резервных копий¶
Для настройки автоматического ежедневного резервного копирования (если ещё не настроено):
crontab -e
# Добавить:
0 3 * * * cd /opt/tryon-saas && bash scripts/backup.sh >> /var/log/tryon-backup.log 2>&1
Резервные копии хранятся в /opt/tryon-saas-backups/ в виде файлов .sql.gz с временной меткой и manifest.json. Скрипт backup.sh автоматически удаляет резервные копии старше 30 дней.