diff --git a/backend/Dockerfile b/backend/Dockerfile index 002a9ab..14210df 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -45,5 +45,12 @@ HEALTHCHECK --interval=10s --timeout=5s --retries=10 --start-period=15s \ # # `sh -c` нужен ради подстановки переменной (exec-форма её не делает), # `exec` — чтобы uvicorn получил PID 1 и корректно принимал SIGTERM. +# +# `--no-sync`: окружение уже собрано выше (`uv sync --frozen --no-dev`), и +# пересобирать его в рантайме незачем. Без флага `uv run` перед каждым +# запуском заново синхронизирует venv И ПОДТЯГИВАЕТ dev-группу (ruff, mypy, +# pytest — ~30 МБ загрузок на каждый старт контейнера, dev-инструменты в +# проде и раздутый venv). То же касается healthcheck'ов в +# deploy/docker-compose.yml, которые дёргают `uv run` каждые 15 секунд. CMD ["sh", "-c", \ - "exec uv run uvicorn main:create_app --factory --host 0.0.0.0 --port 8000 --workers ${UVICORN_WORKERS:-2}"] + "exec uv run --no-sync uvicorn main:create_app --factory --host 0.0.0.0 --port 8000 --workers ${UVICORN_WORKERS:-2}"] diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index b286be9..67edf21 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -125,7 +125,13 @@ services: context: ../backend dockerfile: Dockerfile restart: unless-stopped - command: ["uv", "run", "celery", "-A", "workers.celery_app", "worker", "-B", + # `--no-sync` во ВСЕХ вызовах `uv run` в этом файле: окружение собрано на + # этапе build образа (`uv sync --frozen --no-dev`, backend/Dockerfile), а + # без флага `uv run` синхронизирует venv заново при каждом запуске — и + # тянет dev-группу (ruff, mypy, pytest), которой в проде делать нечего. + # Для healthcheck'ов это особенно дорого: они дёргаются каждые 15 секунд + # всю жизнь контейнера. + command: ["uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "worker", "-B", "-Q", "celery,summarize,notify", "--loglevel=info"] env_file: - ../.env @@ -152,7 +158,7 @@ services: # нет HTTP-сервера на 8000. Проверяем воркер через `celery ... inspect # ping`, как рекомендует документация Celery. healthcheck: - test: ["CMD", "uv", "run", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5"] + test: ["CMD", "uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5"] interval: 15s timeout: 10s retries: 5 @@ -186,7 +192,7 @@ services: build: context: ../backend dockerfile: Dockerfile - entrypoint: ["uv", "run", "python", "/download-model.py"] + entrypoint: ["uv", "run", "--no-sync", "python", "/download-model.py"] environment: WHISPER_MODEL: ${WHISPER_MODEL:-small} WHISPER_MODELS_ROOT: /models/whisper @@ -221,7 +227,7 @@ services: # `worker`, и `celery inspect ping` без `--destination` опросит ВЕСЬ # кластер — упавший worker-transcriber остался бы "healthy", потому что # ответил бы базовый worker. - command: ["uv", "run", "celery", "-A", "workers.celery_app", "worker", + command: ["uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "worker", "-Q", "transcription", "--pool=solo", "--concurrency=1", "--hostname=worker-transcriber@localhost", "--loglevel=info"] env_file: @@ -252,7 +258,7 @@ services: # HTTP-эндпоинта нет — пинг celery, но именно этого узла (см. --hostname # в command выше), а не первого ответившего в общем кластере. healthcheck: - test: ["CMD", "uv", "run", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5", "--destination", "worker-transcriber@localhost"] + test: ["CMD", "uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5", "--destination", "worker-transcriber@localhost"] interval: 15s timeout: 10s retries: 5 @@ -277,7 +283,7 @@ services: args: WITH_GPU_EXTRA: "true" restart: unless-stopped - command: ["uv", "run", "celery", "-A", "workers.celery_app", "worker", + command: ["uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "worker", "-Q", "transcription", "--pool=solo", "--concurrency=1", "--hostname=worker-transcriber-gpu@localhost", "--loglevel=info"] env_file: @@ -310,7 +316,7 @@ services: whisper-model-init: condition: service_completed_successfully healthcheck: - test: ["CMD", "uv", "run", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5", "--destination", "worker-transcriber-gpu@localhost"] + test: ["CMD", "uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "inspect", "ping", "--timeout", "5", "--destination", "worker-transcriber-gpu@localhost"] interval: 15s timeout: 10s retries: 5 diff --git a/docs/deploy/DEPLOYMENT.md b/docs/deploy/DEPLOYMENT.md index 249c42f..a53a3be 100644 --- a/docs/deploy/DEPLOYMENT.md +++ b/docs/deploy/DEPLOYMENT.md @@ -400,7 +400,7 @@ docker exec vidconf-postgres-1 pg_dump -U vidconf vidconf | gzip > db-$(date +%F ```bash gunzip -c db-2026-07-25.sql.gz | docker exec -i vidconf-postgres-1 psql -U vidconf vidconf # Догнать миграции, если бэкап снят на более старой версии кода: -docker compose -f deploy/docker-compose.yml --env-file .env run --rm backend uv run alembic upgrade head +docker compose -f deploy/docker-compose.yml --env-file .env run --rm backend uv run --no-sync alembic upgrade head docker compose -f deploy/docker-compose.yml --env-file .env restart backend worker ``` diff --git a/docs/deploy/install.md b/docs/deploy/install.md index 9dc6298..9d7a4f9 100644 --- a/docs/deploy/install.md +++ b/docs/deploy/install.md @@ -63,7 +63,7 @@ CPU-only — отдельного GPU-варианта профилей для Поведение: - **Дефолт (Enter):** применяет матрицу выбранного пресета к настройкам БД - (defs: `docker compose exec -T backend uv run python -m scripts.apply_preset_settings --force`) + (defs: `docker compose exec -T backend uv run --no-sync python -m scripts.apply_preset_settings --force`) - **Отказ (`n`):** сохраняет ручные правки админа; переменные `BOOTSTRAP_*` в `.env` обновляются, но скрипт применения настроек НЕ запускается - **Флаг `--yes`:** автоматически применяет пресет без вопроса (для CI/CD) @@ -111,7 +111,7 @@ Grafana/Prometheus не поднимаются автоматически — с собирает фронтенд-SPA и вкомпилирует статику, `frontend/Dockerfile`). 4. Поднимает `postgres`/`redis` (`up -d --wait`) и применяет **до старта backend** миграции и seed одноразовыми контейнерами: - `docker compose run --rm backend uv run alembic upgrade head` + + `docker compose run --rm backend uv run --no-sync alembic upgrade head` + `... python -m scripts.seed`. Порядок критичен: `backend.lifespan` бутстрапит `instance_settings` при каждом старте приложения, поэтому на чистой БД таблицы обязаны существовать до первого запуска backend — иначе @@ -122,9 +122,8 @@ Grafana/Prometheus не поднимаются автоматически — с настройки инстанса не перетираются). 5. Поднимает остальной стек: `docker compose <--profile ...> up -d --wait` (backend, worker, nginx с фронтом + сервисы активных профилей). Команда - идемпотентна и обёрнута в ретрай (до 3 попыток): первый старт backend/worker - включает `uv run` (синхронизация окружения + компиляция байткода), и на - слабой/загруженной машине healthcheck может не успеть за отведённые + идемпотентна и обёрнута в ретрай (до 3 попыток): на слабой/загруженной + машине healthcheck может не успеть за отведённые retries — повтор лишь дожидается уже стартующих контейнеров. 6. Печатает сводку: URL фронтенда/бэкенда, учётные данные администратора, команда для `--profile monitoring`. diff --git a/docs/deploy/scaling.md b/docs/deploy/scaling.md index f03e9f3..9ea1bb6 100644 --- a/docs/deploy/scaling.md +++ b/docs/deploy/scaling.md @@ -63,7 +63,7 @@ services: dockerfile: Dockerfile restart: unless-stopped # Без -B: beat уже запущен на базовом worker (см. правило выше). - command: ["uv", "run", "celery", "-A", "workers.celery_app", "worker", + command: ["uv", "run", "--no-sync", "celery", "-A", "workers.celery_app", "worker", "-Q", "summarize", "--hostname=worker-summarize-%h@%h", "--loglevel=info"] env_file: - ../.env diff --git a/install.sh b/install.sh index 5db006e..bad8333 100755 --- a/install.sh +++ b/install.sh @@ -526,13 +526,14 @@ docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d --wait postgres r # exist" и healthcheck (--wait) никогда не проходит. `compose run` запускает # одноразовый контейнер с нужной командой, не поднимая uvicorn/lifespan. echo "[install] Применяю миграции Alembic и seed (админ/справочники) — до старта backend" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run alembic upgrade head -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run python -m scripts.seed +# `--no-sync`: окружение собрано в образе, повторная синхронизация в рантайме +# только тянула бы dev-группу (см. комментарий в backend/Dockerfile). +docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run --no-sync alembic upgrade head +docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm backend uv run --no-sync python -m scripts.seed echo "[install] docker compose up -d --wait (backend/worker/nginx и остальные сервисы профиля)" -# Первый старт backend/worker включает `uv run` (синхронизация окружения + -# компиляция байткода) — на слабой/загруженной машине healthcheck может не -# успеть пройти за отведённые retries, и `--wait` вернёт "container is +# На слабой/загруженной машине healthcheck может не успеть пройти за отведённые +# retries, и `--wait` вернёт "container is # unhealthy", хотя сервис через несколько секунд становится healthy. Команда # идемпотентна, поэтому повторяем её несколько раз: повтор лишь дожидается # уже стартующих контейнеров, ничего не пересоздавая. @@ -573,7 +574,7 @@ if [ "$FRESH_ENV" != "1" ]; then fi if [ "$APPLY_PRESET_SETTINGS" = "1" ]; then echo "[install] Применяю настройки модулей инстанса под пресет ${PRESET}" - docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T backend uv run python -m scripts.apply_preset_settings --force + docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T backend uv run --no-sync python -m scripts.apply_preset_settings --force else echo "[install] Настройки модулей инстанса НЕ изменены — сохранены ручные правки администратора" fi