01.10.2026 Технический разбор RU

ICT: задания, которые переживают перезапуск, Windows XP в контейнере и выкладка без ручных шагов

Полгода работы над оркестратором ICT: события заданий хранятся в базе и переживают перезапуск хаба, унаследованная программа расчёта абсолютного TEC запускается в настоящей Windows XP внутри KVM-контейнера, образы собираются в CI и выкладываются сами, а доступ разграничен и записывается в журнал. В конце — что нашли ревизия кода и линтеры.

Никита Конкин

Проект Репозиторий (оркестратор)
Языковые версии: RU EN

В июньском обзоре ICT была описана как набор контейнеризованных сервисов с веб-оркестратором поверх. С тех пор в оркестраторе появился модуль карт ионосферы — ему посвящён отдельный разбор. Остальная работа ушла в то, что редко видно со стороны: чтобы платформой можно было пользоваться каждый день, не заглядывая на сервер. Это 67 коммитов с апреля по сентябрь, 1 октября они слиты в основную ветку. Веб-интерфейс называется «ИоноПоток».

Панель «ИоноПотока»: строка «Версия 6b983ac · обновлено 01 окт. 2026 г., 07:54», карточки конвертеров TEC-Suite, DAT ↔ Parquet, AbsTEC Suite и «Аналитика данных», таблица последней активности
Панель оркестратора: под заголовком — версия запущенной сборки и время её обновления. Здесь и ниже — локальный запуск с вымышленными пользователями и заданиями.

Задание, которое переживает перезапуск

Конвертер — это контейнер, который может работать часами, а хаб, который его запустил, обновляется и перезапускается. Поэтому события задания — строки лога, прогресс и завершение — теперь сначала записываются в базу, а браузер получает их оттуда через Server-Sent Events. Перезагрузка страницы показывает последние 200 строк лога вместо всего потока, а в базе у каждого задания хранятся только последние строки лога — по умолчанию 2000, — чтобы она не росла без предела.

После пересборки хаб сам находит запущенные им контейнеры. Каждый такой контейнер помечен метками — конвертер, номер задания, пользователь, — и хаб по ним снова подключается к заданию. Задание, начатое до обновления, остаётся в списке запусков, а его прогресс продолжает приходить в браузер.

Отдельно пришлось разобраться со статусами. Задание, которое остановил пользователь, записывалось как проваленное и показывалось красным, как настоящая ошибка. Теперь у него свой статус «остановлено» с приглушённым значком, а старые записи переведены в него при запуске. Попутно нашлась гонка: контейнер завершается, пока команда остановки ещё выполняется, и сообщение о завершении могло превратить остановку пользователем в «ошибку (143)». Теперь остановка записывается до вызова docker stop, а побеждает первое записанное конечное состояние.

Таблица «История задач» из шести запусков TEC-Suite, DAT ↔ Parquet и AbsTEC Suite со статусами SUCCESS, FAILED и STOPPED
История запусков: успешные, проваленное и остановленное пользователем задание — у последнего свой приглушённый статус вместо красного.

Ещё одна находка касалась потока событий. Сжатие ответов в используемой версии Starlette сжимало и text/event-stream, а компрессор придерживал события, пока не заполнится буфер. Лог работающего задания приходил рывками или только в конце: поток молчал по восемь секунд, пока контейнер печатал строку в секунду. Запросы от EventSource теперь идут без сжатия. Наконец, обращения к Docker API — список контейнеров, запуск, остановка с ожиданием до 10 секунд — вынесены из цикла событий в рабочий поток, чтобы не останавливать отрисовку страниц.

Windows XP в контейнере

Расчёт абсолютного TEC выполняет унаследованная программа absolTEC.exe — 32-битное приложение, рассчитанное на Windows XP. В июне она запускалась под Wine. Теперь есть второй способ: настоящая Windows XP, которая работает гостевой системой в KVM-контейнере на основе dockur/windows. Для программы это родная платформа.

Хаб и гостевая система обмениваются через общую папку. Хост записывает папку задания, сценарий-наблюдатель внутри XP забирает её, запускает программу и пишет лог и код завершения. Хост читает лог по мере записи и разбирает прогресс так же, как для Wine. Если время вышло, хост просит гостевую систему завершить именно эту копию программы.

Внутри одного запуска станции можно считать параллельно, по слотам. У каждого слота своя рабочая папка и своя копия программы: одновременные расчёты не должны делить файл настроек и папку результатов. А вот самих заданий XP одновременно выполняет не больше одного: гостевая система берёт работу из одной очереди, второе задание просто ждало бы в ней и упиралось в тайм-аут. К тому же программа называет папку результатов по первым четырём символам имени станции, и при параллельных заданиях результаты станций с общим префиксом могли бы перепутаться. Поэтому каждое задание сначала складывает результаты в собственную промежуточную папку.

Если контейнер с XP остановлен, хаб запускает его сам и предупреждает: системе нужна минута-другая на загрузку, поэтому у тайм-аута должен быть запас.

Фрагмент формы запуска AbsTEC Suite: поле Runner со значением «dockur (Windows XP VM)», «Параллельных задач», «Пропускать обработанные станции», «Прерывать после N ошибок подряд» со значением 25 и «Минимум строк данных на станцию»
Параметры AbsTEC: исполнитель «dockur (Windows XP VM)», параллельные станции, пропуск обработанных станций и остановка после серии ошибок подряд.

У пакетных запусков появились настройки на случай сбоев. Станция, на которой программа завершилась с ошибкой, пропускается, и расчёт продолжается. Но 25 ошибок подряд прерывают запуск: это уже признак системного сбоя, а не плохих данных одной станции. Прерванный пакет можно продолжить, пропуская станции, для которых результаты уже есть.

Выкладка без ручных шагов

Каждое изменение основной ветки проходит в CI тесты и линтеры, а после успешных тестов собираются образы хаба и индексатора данных. Образы публикуются с тегом коммита. На сервере их подхватывает Watchtower: по умолчанию раз в пять минут он проверяет, не вышел ли новый образ, и перезапускает помеченные контейнеры.

Когда сборки выкладываются сами, на вопрос «что сейчас запущено» должен отвечать интерфейс. Поэтому на панели теперь видны версия — тот самый коммит, которым помечен образ, — и время сборки, в часовом поясе того, кто смотрит. При автоматической выкладке время сборки и есть время последнего обновления.

На этом пути нашлась и ошибка сборки. Страницы загружают htmx, Plotly и шрифты из папки самого приложения, чтобы браузерам во внутренней сети не нужен был интернет. Но эта папка не хранилась в репозитории, и ни один шаг сборки её не заполнял, так что образы из CI отдавали страницы без них. Теперь отдельная стадия сборки скачивает эти файлы, сверяет скрипты по SHA-256 и проверяет, что стили шрифтов ссылаются только на WOFF2-файлы Google Fonts. Любая ошибка загрузки останавливает сборку, вместо того чтобы выпустить сломанный образ.

Доступ и журнал

У пользователей есть роли — администратор и оператор, — а у каждой учётной записи отдельные права: на страницы и на каждый конвертер. Новая учётная запись по умолчанию видит только обзорные страницы — аналитику, карту станций и индексированные данные; доступ к конвертерам выдаётся по одному.

Летом к этому добавился слой защиты. В строгом режиме, который включает конфигурация Docker Compose, хаб не запустится со слабым ключом подписи сессий; без него он создаст случайный временный ключ вместо известной заглушки. Администратор с паролем по умолчанию обязан сменить его при первом входе. Попытки входа ограничены: восемь за пять минут. Добавлены проверка источника запросов против CSRF и заголовки безопасности. Доступ хаба к Docker можно сузить: дополнительный файл конфигурации ставит перед сокетом Docker прокси, который пропускает только нужные группы API. А все входы, включая неудачные, создание учётных записей, смена паролей и запуск заданий пишутся в журнал, который приложение никогда не изменяет и не удаляет.

Страница «Управление пользователями»: список из трёх операторов и администратора и форма новой учётной записи с флажками доступа к TEC-Suite, DAT ↔ Parquet и AbsTEC Suite
Пользователи: роли и права на каждый конвертер; новая учётная запись получает только обзорные страницы.
Страница «Журнал аудита»: таблица событий со временем, пользователем, действием вроде LOGIN.SUCCESS, LOGIN.FAILED, JOB.SUBMIT, объектом, IP-адресом и деталями
Журнал аудита: входы, неудачные попытки, создание учётных записей, смена пароля и запуск заданий.

Ревизия кода

Последний день сентября ушёл на ревизию. Замечания ревью, сделанного на отдельной ветке рефакторинга, перепроверены на актуальном коде, и те, что ещё были верны, перенесены. Среди них:

  • фрагменты ошибок задания выводили введённые в форму значения и текст исключения без экранирования — теперь всё экранируется в одном месте;
  • прокси к аналитическому сервису пересылал ему cookie сессии хаба — больше не пересылает;
  • у индексатора данных нет своей аутентификации, поэтому он теперь принимает только пути внутри настроенных каталогов с данными;
  • стартовое индексирование выполняло блокирующий обход файловой системы прямо в цикле событий, и пока оно шло, не отвечал даже /health. Заодно выяснилось, что задокументированное значение настройки запуска индексирования не распознавалось, и индексирование молча пропускалось. Теперь обход идёт в отдельном потоке, а оба значения работают;
  • изменение каталога сбрасывало кэш только того типа данных, который успевал заметить его первым, а изменение во время сканирования терялось. Теперь у каждого каталога есть счётчик изменений, и каждый кэш помнит, какое поколение он видел.

Линтеры нашли 296 замечаний ruff и 21 замечание ESLint; теперь они исправлены, и линтеры вместе с mypy запускаются в CI. Почти всё было механическим, но одна находка оказалась настоящей ошибкой: модуль отрисовки карт при импорте регистрирует шрифты и пишет в журнал через объект, определённый ниже по файлу. Шрифт, который не удалось зарегистрировать, вызвал бы NameError вместо записи в журнал.

Около 3700 строк скриптов и стилей переехали из шаблонов страниц в статические файлы, которые видят и линтер, и кэш браузера. Поведение не должно было измениться ни в чём, и это проверено напрямую: на всех шести затронутых страницах, на двух языках, сравнивались дерево страницы после загрузки, вычисленные стили и состояние после перебора каждого выпадающего списка и флажка. Версии до и после совпали.

Выводы

  1. Состояние задания должно жить дольше процесса, который его показывает: события сначала записываются в базу, а потом уже отдаются браузеру.
  2. «Остановлено» и «ошибка» — разные статусы. Если статус неверно называет причину, история запусков превращается в шум.
  3. Унаследованную программу надёжнее запускать на её родной платформе. KVM-контейнер делает Windows XP ещё одним сервисом, но допущения самой программы — вроде имён папок по четырём символам — придётся учесть в обвязке.
  4. При автоматической выкладке версия и время сборки на панели отвечают на вопрос «что запущено» без входа на сервер.
  5. Среди сотен механических замечаний линтеров и ревью попадаются настоящие ошибки — поэтому их стоит разбирать целиком, а не отключать.

Доступность

Исходный код оркестратора открыт; в README описаны настройки, запуск через Docker Compose и методика модуля карт.

Теги

программирование python fastapi docker devops безопасность

Другие обновления по проекту

12.10.2026 Технический разбор ICT — платформа обработки данных ионосферного зондирования

ICT: неделя анимации карт за минуту, карта станций на OpenStreetMap и что мешало браузеру

Анимация карт ионосферы за неделю строилась 18 минут и обрывалась на прокси. Теперь дни загружаются, а кадры рисуются в параллельных процессах в фоне — та же неделя за 62 секунд...

24.07.2026 Технический разбор ICT — платформа обработки данных ионосферного зондирования

ICT: от наклонного TEC к картам ионосферы — методика IonMaps

Как модуль IonMaps в ICT превращает parquet-выгрузку TEC-suite в региональные карты вертикального электронного содержания: восстановление VTEC из наклонного TEC, интерполяция (л...