Перейти к содержанию

Операционный справочник

Этот 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
bash scripts/backup.sh
ls -lh /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 для этих сертификатов:

ssl_stapling        off;
ssl_stapling_verify off;
OCSP stapling не работает с origin-сертификатами Cloudflare и вызовет ошибку запуска nginx, если включён.

Переполнение диска

Симптомы: 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:

  1. Войдите в веб-консоль VPS-провайдера (xorek.cloud) через аккаунт root
  2. Через консоль, от имени root, восстановите резервную конфигурацию:
    cp /etc/ssh/sshd_config.backup.20260520 /etc/ssh/sshd_config
    systemctl reload ssh
    
  3. При необходимости добавьте ключ пользователя deploy:
    cat /home/deploy/.ssh/authorized_keys
    # Добавить публичный ключ при отсутствии
    

Восстановление после потери SSH-ключа

  1. Войдите в веб-консоль xorek.cloud
  2. Подключитесь к серверу от имени root через веб-консоль
  3. Добавьте новый публичный ключ в /home/deploy/.ssh/authorized_keys
  4. Проверьте права: 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

Перезапустите затронутый сервис:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --force-recreate admin-api

Брандмауэр (UFW)

Открытые порты: 22 (SSH), 80 (HTTP→HTTPS редирект), 443 (HTTPS). Все остальные порты запрещены.

sudo ufw status verbose

Команды 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 дней.