Эксплуатация¶
djangoctl.sh¶
bin/djangoctl.sh — точка входа контейнеров. Он же устанавливается в образ как
/usr/local/bin/djangoctl.sh.
Команда |
Что делает |
|---|---|
|
|
|
|
|
Загрузить начальные фикстуры |
|
Воркер или планировщик Celery |
|
|
|
Воркер с |
Предупреждение
prepare вызывает collectstatic --clear, то есть очищает STATIC_ROOT целиком.
Всё, что положено в статику в обход collectstatic, будет удалено при каждом
перезапуске web.
Параметры gunicorn¶
Умолчания gunicorn (один синхронный воркер, таймаут 30 с) для портала не годятся: загрузка артефакта дольше полуминуты убивала бы воркер посреди запроса.
Переменная |
По умолчанию |
|---|---|
|
|
|
|
Число воркеров считается от хоста, а не вписывается в репозиторий: стенд работает
на восьми, прод на шестнадцати, и одна цифра в коде не подходит ни тому, ни
другому. 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/.
Здоровье контейнеров¶
Сервис |
Проверка |
Лимит памяти |
|---|---|---|
|
|
|
|
|
|
|
файл |
|
|
нет: переподключается сам, упавший процесс перезапускается |
|
|
|
нет: OOM-kill Postgres обрывает все соединения; память у них регулируют |
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/ — состояние инсталляции для суперпользователя (ссылка в меню «Сеть»
рядом с «Настройками»). Шесть блоков, каждый обновляется сам и несёт лампочку:
зелёная — норма, оранжевая — предупреждение, красная — проблема, серая — проверка
не выполнилась.
Блок |
Откуда данные |
Пороги |
|---|---|---|
Шапка |
|
— |
Контейнеры |
|
|
Хост |
|
CPU и память: 70/90 %; диски: 80/90 %; load average: больше числа ядер / вдвое больше |
Приложение |
|
миграции не применены — проблема; TLE старше 8 ч / 24 ч |
Celery сейчас |
|
нет воркеров — проблема; очередь 100 / 1000 |
Резервные копии БД |
|
не настроено — серая; нет копий или |
Задачи по расписанию |
|
|
Логи |
|
— |
Пороги — константы в начале 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; на стенде переменная пустая.
Что нужно на стороне облака (один раз):
Отдельный бакет без публичного доступа: дамп содержит e-mail, хэши паролей и токены станций, в бакет медиа он не кладётся.
Сервисному аккаунту с ключами
AWS_*— права на чтение и запись в этот бакет.Lifecycle-правило на бакете (например, удалять объекты старше 30 дней) — ротацию приложение не делает.
Статус и ссылки — блок «Резервные копии БД» на странице «Система»; там же
кнопка «Запустить» у backup_database делает внеплановую копию. pg_dump для
задачи ставится в образ пакетом postgresql-client-15 (Dockerfile).
Восстановление из копии¶
Взять дамп. На странице «Система» в блоке «Резервные копии БД» — 14 последних копий с кнопкой «Скачать»; ссылка действует час. Без портала — из бакета ключами
AWS_*:aws s3 cp "s3://$BACKUP_S3_BUCKET/production/2026-09-16T000000Z.dump" .
Восстановить, из каталога с
docker-compose.ymlи.env:./soniks.sh restore ./2026-09-16T000000Z.dump
Команда спрашивает подтверждение, останавливает все сервисы (
web,celery,celery-beat,mqtt-bridgeдержат соединения и продолжают писать), поднимаетdb, ждётpg_isready, подаёт дамп на stdinpg_restore --clean --if-exists --no-ownerи поднимает сервисы обратно. Файлы.sqlидут черезpsql— формат определяется по расширению. Еслиpg_restoreсообщил об ошибках, сервисы всё равно поднимаются, а команда завершается ненулевым кодом: ошибки видны в выводе, база может быть неполной.Если дамп старше кода — применить миграции:
docker compose exec web django-admin migrate
Проверить по странице «Система»: БД отвечает, число наблюдений соответствует дате дампа, задачи 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/ — сгенерированное, руками не правится.