Эксплуатация

djangoctl.sh

bin/djangoctl.sh — точка входа контейнеров. Он же устанавливается в образ как /usr/local/bin/djangoctl.sh.

Команда

Что делает

prepare

collectstatic --noinput --clear, compress --force, migrate --noinput

run

prepare, затем gunicorn на 0.0.0.0:8000

initialize

Загрузить начальные фикстуры

run_celery worker / run_celery beat

Воркер или планировщик Celery

develop [SOURCE_DIR]

prepare и runserver, опционально ставит проект в editable-режиме

develop_celery [SOURCE_DIR]

Воркер с -B (worker + beat в одном процессе)

Предупреждение

prepare вызывает collectstatic --clear, то есть очищает STATIC_ROOT целиком. Всё, что положено в статику в обход collectstatic, будет удалено при каждом перезапуске web.

Параметры gunicorn

Умолчания gunicorn (один синхронный воркер, таймаут 30 с) для портала не годятся: загрузка артефакта дольше полуминуты убивала бы воркер посреди запроса.

Переменная

По умолчанию

GUNICORN_WORKERS

nproc * 2 + 1 — формула самого gunicorn

GUNICORN_TIMEOUT

600

Число воркеров считается от хоста, а не вписывается в репозиторий: стенд работает на восьми, прод на шестнадцати, и одна цифра в коде не подходит ни тому, ни другому. GUNICORN_WORKERS в .env фиксирует значение там, где хост знает лучше формулы.

Важно

До правки 2026-08-26 bin/djangoctl.sh перекрывался копией из $FILES_FOLDER при каждой выкатке, и репозиторный файл на серверах не исполнялся вовсе. Копии разошлись: на стенде и на проде оказались разные варианты, а в одном из них worker и beat запускались фоном под tail -f /dev/null — при смерти любого из них контейнер оставался Up, и restart: on-failure не срабатывал.

Повседневные операции

make shell                                   # bash в контейнере web
make djshell                                 # django-admin shell
docker compose logs -f web celery            # логи
docker compose run --rm web django-admin migrate
docker compose run --rm web django-admin createsuperuser

Административный интерфейс Django — /admin/.

Здоровье контейнеров

Сервис

Проверка

Лимит памяти

web

GET /ready/: SELECT 1 и план миграций пуст → 200, иначе 503

12g

celery

celery inspect ping своего воркера раз в минуту

8g

celery-beat

файл /var/run/celery/celerybeat-schedule моложе 15 минут: beat переписывает его, когда отправляет задачу, а самая частая идёт раз в 5 минут

512m

mqtt-bridge

нет: переподключается сам, упавший процесс перезапускается

512m

db, redis

pg_isready, redis-cli ping

нет: OOM-kill Postgres обрывает все соединения; память у них регулируют shared_buffers и maxmemory

unhealthy не перезапускает контейнер: вне swarm Docker так не делает. Он виден в docker compose ps и лампой в блоке «Контейнеры» страницы «Система». Перезапускает restart: unless-stopped, когда процесс упал или убит по памяти, а также после перезагрузки хоста.

Лимиты — прод-замер docker stats с запасом в полтора раза (web 8,4 ГиБ, воркер вместе с beat 5,4 ГиБ на 1.9.2.1). Если контейнер перезапускается сам, проверьте docker inspect --format '{{.State.OOMKilled}}' <контейнер>. true — поднять mem_limit в docker-compose.yml. Задача, на которой убит воркер, возвращается в очередь (acks_late).

/ready/ отвечает без авторизации. Наружу через nginx его выставлять незачем.

Письма об ошибках

Ошибки уровня ERROR логгеров django и django.request уходят на EMAIL_ADMIN, когда DEBUG=False. Одинаковая ошибка (класс исключения и путь запроса) — одно письмо за 10 минут, поэтому crash-loop не заваливает ящик. Полный поток остаётся в docker compose logs web. Лимит хранится в кэше (Redis); если кэш недоступен, письма уходят без лимита.

Страница «Система»

/system/ — состояние инсталляции для суперпользователя (ссылка в меню «Сеть» рядом с «Настройками»). Шесть блоков, каждый обновляется сам и несёт лампочку: зелёная — норма, оранжевая — предупреждение, красная — проблема, серая — проверка не выполнилась.

Блок

Откуда данные

Пороги

Шапка

SONIKS_IMAGE из .env, network.__version__, ENVIRONMENT; дата сборки образа и старт web — из Docker API

—

Контейнеры

GET /containers/json + inspect + stats через docker-proxy

running без healthcheck или healthy — норма, starting — предупреждение, остальное — проблема

Хост

/proc/stat, /proc/meminfo, /proc/uptime, os.getloadavg(), shutil.disk_usage

CPU и память: 70/90 %; диски: 80/90 %; load average: больше числа ядер / вдвое больше

Приложение

SELECT 1, cache.set/get, план миграций, pg_database_size, LatestTleSet.last_modified, станции онлайн

миграции не применены — проблема; TLE старше 8 ч / 24 ч

Celery сейчас

inspect().ping/active/reserved, LLEN очереди в Redis

нет воркеров — проблема; очередь 100 / 1000

Резервные копии БД

list_objects_v2 бакета BACKUP_S3_BUCKET с префиксом ENVIRONMENT/, presigned-ссылки на час, запись последнего запуска backup_database из кеша

не настроено — серая; нет копий или FAILURE — проблема; свежая копия старше 26 ч — предупреждение

Задачи по расписанию

APP.conf.beat_schedule + запись последнего запуска в кеше (см. Задачи Celery)

FAILURE — проблема; просрочка больше 10 минут — предупреждение

Логи

GET /containers/{id}/logs?tail=400 по каждому контейнеру, оставляются строки с WARN, ERROR, CRITICAL, FATAL, PANIC, Traceback, Exception

—

Пороги — константы в начале network/base/system_status.py.

Кнопка «Запустить» у задачи ставит в очередь ту же обёртку с теми же аргументами, что и beat (APP.send_task), после подтверждения. Имя принимается только из расписания beat.

Важно

Блоки «Контейнеры», «Логи» и даты в шапке требуют сервис docker-proxy из docker-compose.yml: web не имеет ни docker-сокета, ни CLI, а прокси отдаёт только GET по контейнерам и образам. Без него эти блоки серые, остальная страница работает. Адрес — DOCKER_API_URL (см. Конфигурация).

Диск «/» считается по bind-монтированному /workdir/.env, то есть по корневой файловой системе хоста, а не по overlay контейнера; «media» — по MEDIA_ROOT. Если это один и тот же диск, показывается один.

Резервные копии БД

Задача backup_database (см. Задачи Celery) раз в сутки в 00:00 UTC делает pg_dump --format=custom и потоком отправляет его в S3 — объект <ENVIRONMENT>/<UTC-время>.dump в бакете BACKUP_S3_BUCKET. Дамп не пишется ни на диск, ни в память целиком, размер базы не важен. Задача регистрируется только при заданном BACKUP_S3_BUCKET; на стенде переменная пустая.

Что нужно на стороне облака (один раз):

  1. Отдельный бакет без публичного доступа: дамп содержит e-mail, хэши паролей и токены станций, в бакет медиа он не кладётся.

  2. Сервисному аккаунту с ключами AWS_* — права на чтение и запись в этот бакет.

  3. Lifecycle-правило на бакете (например, удалять объекты старше 30 дней) — ротацию приложение не делает.

Статус и ссылки — блок «Резервные копии БД» на странице «Система»; там же кнопка «Запустить» у backup_database делает внеплановую копию. pg_dump для задачи ставится в образ пакетом postgresql-client-15 (Dockerfile).

Восстановление из копии

  1. Взять дамп. На странице «Система» в блоке «Резервные копии БД» — 14 последних копий с кнопкой «Скачать»; ссылка действует час. Без портала — из бакета ключами AWS_*:

    aws s3 cp "s3://$BACKUP_S3_BUCKET/production/2026-09-16T000000Z.dump" .
    
  2. Восстановить, из каталога с docker-compose.yml и .env:

    ./soniks.sh restore ./2026-09-16T000000Z.dump
    

    Команда спрашивает подтверждение, останавливает все сервисы (web, celery, celery-beat, mqtt-bridge держат соединения и продолжают писать), поднимает db, ждёт pg_isready, подаёт дамп на stdin pg_restore --clean --if-exists --no-owner и поднимает сервисы обратно. Файлы .sql идут через psql — формат определяется по расширению. Если pg_restore сообщил об ошибках, сервисы всё равно поднимаются, а команда завершается ненулевым кодом: ошибки видны в выводе, база может быть неполной.

  3. Если дамп старше кода — применить миграции:

    docker compose exec web django-admin migrate
    
  4. Проверить по странице «Система»: БД отвечает, число наблюдений соответствует дате дампа, задачи Celery идут.

Важно

--clean удаляет объекты, которые есть в дампе, но не те, которых в нём нет: таблица или колонка, добавленная миграцией после снятия копии, останется на месте. Откат схемы назад — отдельная процедура, см. раздел «Откат» в CI/CD и деплой.

Время восстановления копии прода — (замер со стенда, вписать после первого прогона).

Запасной путь, если скрипта под рукой нет:

docker compose cp file.dump db:/tmp/
docker compose exec db pg_restore -U "$POSTGRES_DB_USER" -d "$POSTGRES_DB_NAME" \
    --clean --if-exists --no-owner /tmp/file.dump

Что процедура рабочая, проверяет тест network/base/test_backup.py: он снимает дамп настоящей задачей, разворачивает его теми же флагами в отдельную БД и сверяет число строк.

Миграции

Схема БД применяется автоматически при старте web (djangoctl.sh prepare). Вручную:

docker compose run --rm web django-admin makemigrations
docker compose run --rm web django-admin migrate

Важно

Существующие файлы в network/*/migrations/ не редактируются — только новые миграции.

Пока миграции идут, web не отвечает, а /ready/ отдаёт 503. На объёме прода счёт идёт на минуты — порядок выкатки и замеры в CI/CD и деплой, раздел «Выкатка с долгими миграциями».

Celery

Воркер и beat поднимаются сервисом celery. Разовый запуск задачи из shell:

from network.base.tasks import fetch_tle
fetch_tle.delay()

Список задач и их расписание — в Задачи Celery.

У задач есть лимиты времени (по умолчанию 10 минут мягкий, 15 жёсткий; у бэкапа и обходов каталога — свои, см. Конфигурация) и до трёх повторов на сетевую ошибку у задач, которые ходят во внешние API. На странице «Система» это видно так: RETRY в состоянии задачи — повтор идёт; FAILURE с SoftTimeLimitExceeded или TimeLimitExceeded в ошибке — задача не уложилась в лимит. Если это повторяется у одной задачи, поднимать её собственный лимит на обёртке в network/celery.py, а не глобальный; при лимите выше REDIS_VISIBILITY_TIMEOUT поднять и его.

Число процессов воркера — CELERY_WORKER_CONCURRENCY, по умолчанию 4. В отличие от GUNICORN_WORKERS оно не считается от хоста: собственное умолчание Celery — процесс на ядро, на проде это 32 процесса при лимите памяти 8g, рассчитанном на четыре, и 32 соединения с базой сверх воркеров gunicorn. Поднимать вместе с mem_limit сервиса celery и с оглядкой на max_connections Postgres. Проверка: docker compose exec celery celery -A network inspect stats → pool.max-concurrency.

Обслуживание зависимостей

Пины в requirements.txt, requirements-dev.txt и constraints.txt генерируются — править их руками нельзя. Верхнеуровневые зависимости живут в setup.cfg (install_requires и extra dev), после правки:

./contrib/refresh-requirements.sh   # или ./soniks.sh refresh

Скрипт создаёт временный virtualenv, ставит проект, снимает pip freeze, разделяет рантайм и dev, и проверяет результат через pip check.

Зависимости сборки документации живут отдельно, в docs/requirements.txt, и в setup.cfg не входят.

Фронтенд-зависимости обновляются через ./soniks.sh update (см. Фронтенд).

Схема OpenAPI

./manage.py spectacular --file soniks-network-api-client/openapi.yml --validate

То же самое делает джоба schema в CI; следующая за ней джоба api генерирует из схемы Python-клиент и интерактивный HTML-справочник. Содержимое soniks-network-api-client/ — сгенерированное, руками не правится.